The Bookstore API is a REST CRUD service built with Spring Boot 4.1 and Java 25, featuring AI-generated book summaries powered by Spring AI (Google Gemini). A learning project demonstrating clean layering, explicit JPA entities, and DTO-to-entity mapping without Lombok.
Backend
- Java 25
- Spring Boot 4.1.1 (Web MVC, Data JPA, Validation)
- Spring AI 2.0 —
spring-ai-starter-model-google-genai - Maven
- PostgreSQL (Neon) — Hibernate
ddl-auto: update
AI Provider
- Google Gemini API
Before running the project, configure your database and AI credentials:
-
Copy
.env.exampleand rename it to.env. -
Open
.envand fill in your values:DB_URL=jdbc:postgresql://your-host/bookstore-db?sslmode=require DB_USER=your_user DB_PASSWORD=your_password GEMINI_API_KEY=your_gemini_key
(The
.envfile is excluded from Git for security).
There are two ways to run the project: manually or with Docker.
Prerequisites: JDK 25+, Maven.
-
Clone the repository:
git clone https://github.com/dev-araujo/bookstore-api.git
-
Run the application:
mvn spring-boot:run
Or use the Makefile shortcut:
make run
⭐ The API will be available at http://localhost:8080.
Other commands: mvn -q verify (build & test), make build.
Make sure you have Docker and Docker Compose installed.
-
Clone the repository:
git clone https://github.com/dev-araujo/bookstore-api.git
-
Copy the Docker env template and fill in your
GEMINI_API_KEY:cp .env.example.docker .env.docker
(Only
GEMINI_API_KEYis required — database credentials use sensible defaults). -
Run Docker Compose to build the images and start the containers:
docker compose --env-file .env.docker up --build
⭐ The API will be available at http://localhost:8080.
The compose spins up two services: bookstore-db (PostgreSQL 17) and bookstore-api (application). The database persists through a Docker volume (pgdata).
- Layering: controllers → services → repositories → models
- Naming: tables
tb_<plural>(e.g.tb_books), entities<Name>Model, input DTOs<Name>RecordDto - No Lombok — constructors, getters, and setters are handwritten
- No field injection —
@Autowiredon fields is blocked by a guardrail hook BeanUtils.copyPropertiesused for DTO → entity mapping- UUID identifiers for all entities