This application allows users to borrow and return books through a shared platform. Users can manage book availability, track borrowed books, and return them, facilitating an efficient book-sharing system.
The project is implemented with:
- Spring Boot
- Spring MVC
- Spring Data JPA
- MySQL
- Docker
- Sphinx documentation
- GitHub Actions
- Aiven cloud MySQL
- Render cloud deployment
The application follows the MVC (Model-View-Controller) pattern:
- Model — JPA entities and database persistence
- View — static frontend in HTML, JavaScript, and CSS
- Controller — REST endpoints handling HTTP requests
- Service — business logic
- Repository — Spring Data JPA access to MySQL.
| Path | Purpose |
|---|---|
src/main/java/com/example/restapi/ |
Spring Boot application entry point |
src/main/java/com/example/restapi/controller/ |
REST controllers |
src/main/java/com/example/restapi/service/ |
Business logic |
src/main/java/com/example/restapi/repository/ |
Spring Data JPA repositories |
src/main/java/com/example/restapi/model/ |
JPA entity classes |
src/main/resources/static/ |
HTML, CSS, and JavaScript frontend |
src/main/resources/application.properties |
Default local configuration |
Dockerfile |
Container image definition |
docker-compose.yml |
Local development environment with app + MySQL |
API documentation is generated automatically using Springdoc OpenAPI.
For a detailed technical explanation, see:
HOWTO_SPRINGBOOT.md
Create the local database:
mysql -u root -p < src/main/resources/dbsetup.sqlCheck or edit the default local database configuration in:
src/main/resources/application.properties
Run the application:
mvn spring-boot:runor skip tests for a faster start:
mvn -DskipTests spring-boot:runAccess points:
| Service | URL |
|---|---|
| Frontend UI | http://localhost:8080 |
| Swagger UI | http://localhost:8080/swagger-ui.html |
| OpenAPI JSON | http://localhost:8080/v3/api-docs |
This project includes a docker-compose.yml file that starts:
- a MySQL container
- the Spring Boot application container
Start the stack:
docker-compose upRebuild after code changes:
docker-compose up --buildStop the stack:
docker-compose downStop the stack and delete the MySQL volume:
docker-compose down -vSee:
docker_essentials_with_compose.md
for more Docker details.
The application supports the following configuration sources:
- real operating-system environment variables
.envfile, if present- fallback values from
application.properties
This allows the same codebase to run locally, in Docker, and in Render.
The application entry point loads .env values when a .env file exists in the project root.
The expected local .env file is:
.env
Example:
SPRING_DATASOURCE_URL=jdbc:mysql://mysql-d49e810-dipina-2a6e.h.aivencloud.com:16552/defaultdb?sslMode=REQUIRED
SPRING_DATASOURCE_USERNAME=avnadmin
SPRING_DATASOURCE_PASSWORD=YOUR_AIVEN_PASSWORD
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect
SPRING_JPA_HIBERNATE_DDL_AUTO=update
SPRING_JPA_SHOW_SQL=trueDo not commit .env.
Add this to .gitignore:
.envA safe template may be committed as:
.env.example
Example .env.example:
SPRING_DATASOURCE_URL=jdbc:mysql://HOST:PORT/DATABASE?sslMode=REQUIRED
SPRING_DATASOURCE_USERNAME=USERNAME
SPRING_DATASOURCE_PASSWORD=PASSWORD
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect
SPRING_JPA_HIBERNATE_DDL_AUTO=update
SPRING_JPA_SHOW_SQL=trueThe application must load .env before Spring Boot starts.
The application entry point should look like this:
package com.example.restapi;
import io.github.cdimascio.dotenv.Dotenv;
import java.nio.file.Files;
import java.nio.file.Path;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class RestApiApplication {
public static void main(String[] args) {
loadDotEnvIfPresent();
SpringApplication.run(RestApiApplication.class, args);
}
private static void loadDotEnvIfPresent() {
Path dotenvPath = Path.of(".env");
if (!Files.exists(dotenvPath)) {
return;
}
Dotenv dotenv = Dotenv.configure()
.directory(".")
.ignoreIfMalformed()
.ignoreIfMissing()
.load();
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_DATASOURCE_URL", "spring.datasource.url");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_DATASOURCE_USERNAME", "spring.datasource.username");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_DATASOURCE_PASSWORD", "spring.datasource.password");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_DATASOURCE_DRIVER_CLASS_NAME", "spring.datasource.driver-class-name");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_JPA_DATABASE_PLATFORM", "spring.jpa.database-platform");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_JPA_HIBERNATE_DDL_AUTO", "spring.jpa.hibernate.ddl-auto");
setSpringPropertyFromDotEnvIfNoRealEnv(dotenv, "SPRING_JPA_SHOW_SQL", "spring.jpa.show-sql");
}
private static void setSpringPropertyFromDotEnvIfNoRealEnv(
Dotenv dotenv,
String envName,
String springPropertyName
) {
String realEnvironmentValue = System.getenv(envName);
if (realEnvironmentValue != null && !realEnvironmentValue.isBlank()) {
System.setProperty(springPropertyName, realEnvironmentValue);
return;
}
String dotenvValue = dotenv.get(envName);
if (dotenvValue != null && !dotenvValue.isBlank()) {
System.setProperty(springPropertyName, dotenvValue);
}
}
}The required Maven dependency is:
<dependency>
<groupId>io.github.cdimascio</groupId>
<artifactId>dotenv-java</artifactId>
<version>3.2.0</version>
</dependency>The frontend JavaScript must not use a hardcoded localhost API URL when deployed to the cloud.
Incorrect for cloud deployment:
const apiBaseUrl = "http://localhost:8080/api/library";Correct:
const apiBaseUrl = "/api/library";With the relative URL, the browser calls the API on the same host that served the frontend.
Local deployment:
http://localhost:8080/api/library
Render deployment:
https://springbootlibrarysphinx.onrender.com/api/library
This avoids CORS and loopback-address errors such as:
Access to fetch at 'http://localhost:8080/api/library/users/login'
from origin 'https://springbootlibrarysphinx.onrender.com'
has been blocked by CORS policy
Aiven is a cloud platform for managed open-source data services. In this project it is used to host a managed MySQL database in the cloud.
Using Aiven avoids the need to run a MySQL container in Render. This is important because free container platforms are usually designed to run the web application, not to provide durable database storage inside the same container.
Recommended cloud architecture:
Browser
|
v
Render Web Service
|
v
Spring Boot Docker Container
|
v
Aiven Managed MySQL Database
Aiven provides:
- a managed MySQL service
- public connection credentials
- SSL-enabled database access
- a free tier suitable for demos, prototypes, and coursework
- database persistence independent of the Render application container
- Create an Aiven account.
- Open the Aiven Console.
- Create or select an Aiven project.
- Click Create service.
- Select MySQL.
- Select the free tier or another suitable plan.
- Create the service.
- Wait until the service status is running.
- Open the service overview.
- Copy the connection parameters:
HOST
PORT
DATABASE
USER
PASSWORD
For example:
HOST=mysql-d49e810-dipina-2a6e.h.aivencloud.com
PORT=16552
DATABASE=defaultdb
USER=avnadmin
PASSWORD=YOUR_AIVEN_PASSWORD
Use this JDBC URL pattern:
SPRING_DATASOURCE_URL=jdbc:mysql://HOST:PORT/DATABASE?sslMode=REQUIREDExample:
SPRING_DATASOURCE_URL=jdbc:mysql://mysql-d49e810-dipina-2a6e.h.aivencloud.com:16552/defaultdb?sslMode=REQUIRED
SPRING_DATASOURCE_USERNAME=avnadmin
SPRING_DATASOURCE_PASSWORD=YOUR_AIVEN_PASSWORD
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect
SPRING_JPA_HIBERNATE_DDL_AUTO=update
SPRING_JPA_SHOW_SQL=trueUse:
sslMode=REQUIRED
inside the JDBC URL.
Do not use the MySQL CLI option spelling inside the JDBC URL:
ssl-mode=REQUIRED
That form is for the mysql command-line client, not for JDBC.
Use:
mysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdbThen enter the password when prompted.
To test the connection with a query:
mysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdb -e "SELECT 1;"To show databases:
mysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdb -e "SHOW DATABASES;"mysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdbmysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdbDo not put the database password directly in the command line unless strictly necessary. Passing the password inline may expose it in shell history or process listings.
Prefer:
-pand type the password interactively.
Render is a cloud platform that can build and run web services directly from a GitHub repository. This project can be deployed to Render using its existing Dockerfile.
The local docker-compose.yml is useful for local development, but Render should deploy only the Spring Boot container. The database should be external, for example on Aiven.
- Push the repository to GitHub.
- Create a Render account.
- Click New +.
- Select Web Service.
- Connect the GitHub repository.
- Select this repository.
- Configure the service:
- Runtime: Docker
- Dockerfile path:
Dockerfile - Branch:
main - Instance type: Free, if available
- Add the environment variables for Aiven:
SPRING_DATASOURCE_URL=jdbc:mysql://mysql-d49e810-dipina-2a6e.h.aivencloud.com:16552/defaultdb?sslMode=REQUIRED
SPRING_DATASOURCE_USERNAME=avnadmin
SPRING_DATASOURCE_PASSWORD=YOUR_AIVEN_PASSWORD
SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.cj.jdbc.Driver
SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.MySQLDialect
SPRING_JPA_HIBERNATE_DDL_AUTO=update
SPRING_JPA_SHOW_SQL=true
- Create the service.
- Wait for Render to build and deploy the container.
After deployment, the app should be available at a URL similar to:
https://springbootlibrarysphinx.onrender.com
Access points:
| Service | URL |
|---|---|
| Frontend UI | https://springbootlibrarysphinx.onrender.com |
| Swagger UI | https://springbootlibrarysphinx.onrender.com/swagger-ui.html |
| OpenAPI JSON | https://springbootlibrarysphinx.onrender.com/v3/api-docs |
Render injects configuration as environment variables.
The Java application must therefore resolve:
SPRING_DATASOURCE_URL
SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD
SPRING_JPA_HIBERNATE_DDL_AUTO
SPRING_JPA_SHOW_SQL
The .env support described earlier is useful locally, but in Render the real Render environment variables should take priority.
Change:
const apiBaseUrl = "http://localhost:8080/api/library";to:
const apiBaseUrl = "/api/library";This ensures that the frontend calls the backend on the same Render domain.
The Render service should use the Dockerfile, not docker-compose.yml.
Use Aiven MySQL externally.
This repository already includes workflows for documentation and reports.
| Workflow | File | Purpose |
|---|---|---|
| Sphinx docs | .github/workflows/sphinx-docs.yml |
Builds and deploys Sphinx HTML + PDF to GitHub Pages |
| Maven site | .github/workflows/maven-site-integration.yml |
Runs tests, Maven Site, Doxygen, and reports |
Required GitHub repository settings:
- Go to Settings → Actions → General.
- Enable Read and write permissions under Workflow permissions.
- Go to Settings → Pages.
- Set the source to:
- Branch:
gh-pages - Folder:
/
- Branch:
The goal is:
Push to main
|
v
Build and deploy Sphinx documentation
|
v
Run Render deployment workflow
|
v
Render rebuilds and deploys the Spring Boot Docker container
The Render deployment workflow should run only after the Sphinx documentation workflow has completed successfully.
This is achieved with the workflow_run trigger.
In Render:
- Open the Render web service.
- Go to Settings.
- Locate Deploy Hook.
- Copy the deploy hook URL.
It will look similar to:
https://api.render.com/deploy/srv-xxxxxxxxxxxxxxxxxxxx?key=yyyyyyyyyyyyyyyy
This URL triggers a new Render deployment.
In GitHub:
- Open the repository.
- Go to Settings → Secrets and variables → Actions.
- Click New repository secret.
- Create this secret:
RENDER_DEPLOY_HOOK
- Paste the Render deploy hook URL as the value.
Create a new file:
.github/workflows/deploy-render.yml
with this content:
name: Deploy to Render
on:
workflow_run:
workflows:
- Build and Deploy Sphinx Docs
types:
- completed
branches:
- main
workflow_dispatch:
jobs:
deploy-render:
name: Deploy Spring Boot application to Render
runs-on: ubuntu-latest
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: maven
- name: Run unit tests
run: mvn test
- name: Build application
run: mvn -DskipTests package
- name: Trigger Render deployment
run: |
curl --fail --request POST "${{ secrets.RENDER_DEPLOY_HOOK }}"name: Deploy to RenderThis is the name shown in the GitHub Actions UI.
on:
workflow_run:
workflows:
- Build and Deploy Sphinx Docs
types:
- completed
branches:
- mainThis means the Render workflow is triggered only after the workflow named:
Build and Deploy Sphinx Docs
has completed on the main branch.
The name must match the name: value inside:
.github/workflows/sphinx-docs.yml
workflow_dispatch:This allows the Render deployment to be launched manually from the GitHub Actions tab.
if: >
github.event_name == 'workflow_dispatch' ||
github.event.workflow_run.conclusion == 'success'This prevents a Render deployment if Sphinx failed.
Manual runs are still allowed.
- name: Run unit tests
run: mvn testThis ensures the application is tested before the deployment is triggered.
- name: Build application
run: mvn -DskipTests packageThis confirms that the application can be packaged successfully.
- name: Trigger Render deployment
run: |
curl --fail --request POST "${{ secrets.RENDER_DEPLOY_HOOK }}"This calls the Render deploy hook. Render then pulls the repository, builds the Docker image using the Dockerfile, and redeploys the service.
.github/workflows/
├── sphinx-docs.yml
├── maven-site-integration.yml
└── deploy-render.yml
curl -X POST \
https://api.github.com/repos/dipina/SpringBootLibrarySphinx/actions/workflows/sphinx-docs.yml/dispatches \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer YOUR_PAT_HERE" \
-H "X-GitHub-Api-Version: 2022-11-28" \
-d '{"ref":"main"}'When the Sphinx workflow completes successfully, the Render deployment workflow will run automatically.
mvn testmvn -Dtest=UserServiceTest,BookServiceTest testIntegration tests require MySQL to be running:
mvn -Pintegration integration-testmvn -Pperformance integration-testmvn clean test jacoco:reportOpen:
target/site/jacoco/index.html
mvn siteOpen:
target/site/index.html
cd docs-sphinx
make htmlOn Windows:
cd docs-sphinx
.\make.bat htmlOpen:
docs-sphinx/_build/html/index.html
The PDF is generated automatically in CI using WeasyPrint.
To generate it locally:
pip install weasyprint
cd docs-sphinx
make html
weasyprint _build/html/index.html _build/SpringBootLibrarySphinx.pdf --base-url _build/html/mvn test jacoco:report
mvn -Pperformance integration-test
mvn -Pperformance resources:copy-resources@copy-perf-report
mvn site
mvn post-siteOpen:
docs/site/index.html
Richly documented classes include:
BorrowingController.javaBookService.javaBorrowing.java
Symptom:
Communications link failure
Connection refused
Check whether the app is still using:
jdbc:mysql://localhost:3306/libraryapidb
Fixes:
- Ensure
.envexists in the project root. - Ensure the Java dotenv loader is present.
- Ensure environment variable names are correct.
- Ensure the Aiven JDBC URL uses
sslMode=REQUIRED. - Run:
mvn spring-boot:runfrom the project root.
Symptom:
Access to fetch at 'http://localhost:8080/api/library/users/login'
from origin 'https://springbootlibrarysphinx.onrender.com'
has been blocked by CORS policy
Fix:
const apiBaseUrl = "/api/library";Then commit and redeploy:
git add src/main/resources/static/scripts.js
git commit -m "Use relative API path for cloud deployment"
git pushAfter deployment, clear the browser cache or use an incognito window.
Test:
mysql -h mysql-d49e810-dipina-2a6e.h.aivencloud.com -P 16552 -u avnadmin -p --ssl-mode=REQUIRED defaultdb -e "SELECT 1;"Check:
- Host is correct.
- Port is correct.
- Username is correct.
- Password is correct.
- Aiven service is running.
- SSL mode is enabled.
Check:
- The secret
RENDER_DEPLOY_HOOKexists. - The deploy hook URL is correct.
- The Sphinx workflow completed successfully.
- The workflow name in
deploy-render.ymlexactly matches:
Build and Deploy Sphinx Docs
- The workflow is running on branch
main.
Check Render logs for:
- missing environment variables
- wrong Aiven database password
- wrong JDBC URL
- database service not running
- insufficient free-tier resources
Add this JVM option to the spring-boot-maven-plugin configuration if required:
-Xverify:noneThen run:
mvn spring-boot:runand connect VisualVM through the Local tab.
The following files at the repository root are leftovers and should be removed if still present:
git rm sphinx-docs.yml
git rm pom-javadoc-snippet.xmlThe valid workflow location is:
.github/workflows/