RESTful Spring Boot service for running a small library catalogue and lending workflow. The project was built for personal learning and portfolio purposes and showcases role-based access, DTO-driven APIs, and layered design.
- Manage authors and books, including filtering books by an author and tracking available quantity per title.
- Register clients, delete them with optional payload echo, and keep contact details unique.
- Track borrowing orders, query them by a client or date, and extend due dates in days, weeks or months.
- API-first stateless security with Spring Security and JWT (stored as an HttpOnly cookie).
- Dedicated admin endpoints for user and role management (ADMIN, LIBRARIAN authorities).
- Centralized exception handling, request validation, and structured logging to
logs/log.log. - Unit, slice, and MVC security tests covering controllers, services, mappers, exception handlers, and access rules.
- Java 25
- Spring Boot 4.0.3 (Web, Data JPA, Validation, Security)
- Spring Framework 7 / Jakarta EE (
jakarta.*namespaces) - Hibernate ORM 7 & PostgreSQL
- Liquibase for managed schema migrations
- JSON Web Tokens via
jjwt0.13.0 - Maven
- JUnit 5, Mockito JUnit Jupiter, Spring Test, MockMvc
src/main/java/dev/vdrenkov/biblium/
configuration/ # Security filter chain & password encoder
controller/ # REST controllers for authors, books, clients, orders, users, roles
dto/ # Response DTO records
entity/ # JPA entities and relationships
exception/, handler/ # Custom exceptions + global exception handler
jwt/ # Token utilities, filter, and user details service
mapper/ # Manual DTO mappers (with logging)
repository/ # Spring Data repositories
request/ # Validated request payloads
service/ # Business logic layer
util/ # Shared constants
src/main/resources/
application.properties
db/changelog/ # Liquibase changelogs
Biblium.postman_collection.json
logs/ # Log output destination (configured in properties)
- Layered Spring Boot service with separated controller, service, repository, mapper, and entity packages.
- Controllers define the REST contract, validate request payloads, and translate HTTP interactions into application use cases.
- Services contain the borrowing, catalogue, client-management, authentication, and role-management business rules.
- Repositories isolate persistence concerns through Spring Data JPA on top of PostgreSQL.
- Manual mappers keep DTO conversion explicit and predictable at the API boundary.
- Spring Security provides stateless JWT cookie authentication, CSRF protection, and role-based authorization.
- Liquibase manages schema changes, while Hibernate validates mappings at startup instead of mutating the database.
- Java 25
- Maven 3.9+
- PostgreSQL running locally (default config expects
localhost:5432/biblium) - Optional: Postman for API exploration (
src/main/resources/Biblium.postman_collection.json)
The default configuration lives in src/main/resources/application.properties:
spring.datasource.*points to a PostgreSQL instance.spring.jpa.hibernate.ddl-auto=validatekeeps Hibernate in schema-validation mode.- Liquibase applies the managed schema from
src/main/resources/db/changelog/db.changelog-master.yaml. spring.jpa.open-in-view=falsedisables Open Session in View for stateless API behavior.- Hibernate dialect auto-detected from the JDBC driver (no explicit
hibernate.dialectoverride configured). jwt.secretprovides the signing key for tokens (use a strong 64+ character secret for HS512) and is required at startup.jwt.cookie.secureandjwt.cookie.same-sitecontrol cookie hardening options.logging.file.name=logs/log.logroutes logs to the/logsfolder.app.bootstrap.roles=trueauto-createsADMINandLIBRARIANroles if missing.app.bootstrap.admin.username/app.bootstrap.admin.passwordoptionally create an initial admin user at startup.
JWT_SECRET and SPRING_DATASOURCE_PASSWORD are mandatory environment variables.
Do not commit real credentials. Override sensitive values with environment variables, or a profile-specific properties file instead of editing source directly:
set SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/biblium
set SPRING_DATASOURCE_USERNAME=postgres
set SPRING_DATASOURCE_PASSWORD=<your-password>
set JWT_SECRET=<64+-char-secret>For tighter production hardening, keep:
- HTTPS-only cookies (
jwt.cookie.secure=true) and restrictiveSameSiteas needed.
For local HTTP development only, you can explicitly relax the cookie secure flag:
set JWT_COOKIE_SECURE=false- Create the database (first run only):
CREATE DATABASE biblium;
- Build the project:
mvn clean package
- Start the service:
The API listens on
mvn spring-boot:run
http://localhost:8080.
POST /registerself-registers a LIBRARIAN user.GET /csrfreturns a CSRF token and sets theXSRF-TOKENcookie for subsequent state-changing requests.POST /loginauthenticates a user and issues a JWT stored in an HttpOnly cookie namedBIBLIUM_AUTH; include it on subsequent requests.POST /admins/registerlets an ADMIN create additional ADMIN or LIBRARIAN accounts without changing the caller’s current session.POST /logoutclears the JWT cookie.- CSRF is enabled with
CookieCsrfTokenRepository; sendX-XSRF-TOKENwith POST/PUT/DELETE calls using the token from/csrf(or theXSRF-TOKENcookie). - Authorities:
- ADMIN – full access to user/role management and all catalogue operations (including deletions).
- LIBRARIAN – CRUD access to authors, books, clients and orders (except DELETE and admin endpoints).
Roles (ADMIN, LIBRARIAN) are auto-bootstrapped at startup. To bootstrap the first admin automatically, set:
set APP_BOOTSTRAP_ADMIN_USERNAME=admin
set APP_BOOTSTRAP_ADMIN_PASSWORD=<strong-password>| Method | Path | Description | Access |
|---|---|---|---|
| GET | /csrf |
Get CSRF token for state-changing requests | Public |
| POST | /login |
Authenticate an existing user and receive JWT cookie | Public |
| POST | /register |
Self-register a librarian account | Public |
| POST | /admins/register |
Register a user with roles (without changing current login) | ADMIN |
| POST | /logout |
Clear JWT authentication cookie | Authenticated |
| GET | /authors |
List authors | ADMIN, LIBRARIAN |
| POST | /authors |
Create author | ADMIN, LIBRARIAN |
| GET | /authors/{id} |
Get author details | ADMIN, LIBRARIAN |
| GET | /books |
List available books | ADMIN, LIBRARIAN |
| POST | /books |
Add book to catalogue | ADMIN, LIBRARIAN |
| GET | /authors/{authorId}/books |
Books written by author | ADMIN, LIBRARIAN |
| GET | /books/{id} |
Get book details | ADMIN, LIBRARIAN |
| POST | /clients |
Register client | ADMIN, LIBRARIAN |
| GET | /clients |
List clients | ADMIN, LIBRARIAN |
| GET | /clients/{id} |
Client details | ADMIN, LIBRARIAN |
| DELETE | /clients/{id}?returnOld={bool} |
Delete client and optionally return removed payload | ADMIN |
| POST | /orders |
Create borrowing order (checks stock & decrements quantity) | ADMIN, LIBRARIAN |
| GET | /orders |
List orders | ADMIN, LIBRARIAN |
| GET | /clients/{clientId}/orders |
Orders placed by client | ADMIN, LIBRARIAN |
| GET | /orders?choice={1-6}&date={yyyy-MM-dd} |
Filter orders by issue/due date rule | ADMIN, LIBRARIAN |
| GET | /orders/{id} |
Get order details | ADMIN, LIBRARIAN |
| PUT | /orders/{id}?choice={1-3}&period={n}&returnOld={bool} |
Extend due date by days, weeks, or months | ADMIN, LIBRARIAN |
| GET | /users |
List users | ADMIN |
| GET | /users/{id} |
User details | ADMIN |
| POST | /roles |
Create role | ADMIN |
| GET | /roles |
List roles | ADMIN |
| GET | /roles/{id} |
Role details | ADMIN |
choice query parameters drive filter/extend behaviour:
- Orders by date:
1issue date equals,2issue before,3issue after,4due equals,5due before,6due after. - Extend due date:
1add days,2add weeks,3add months. - Unsupported HTTP methods return
405 Method Not Allowed.
Import the Postman collection for ready-made requests (
src/main/resources/Biblium.postman_collection.json). Run GetCsrfToken first, then use the returned
token for POST/PUT/DELETE requests together with the authentication cookie.
Run the automated test suite with:
mvn testThe suite uses JUnit 5 (Jupiter), Mockito JUnit Jupiter, and Spring’s test utilities to cover controller endpoints, service logic, DTO mappers, exception handling, and security access rules.
- Liquibase is the single source of truth for schema management.
- The initial schema lives in
src/main/resources/db/changelog/changes/001-init-schema.sql. - Hibernate validates the schema on startup instead of mutating it.
- For the cleanest first run, point the app at a fresh
bibliumdatabase and let Liquibase apply the schema.
Application logs are written to logs/log.log (rotation handled by Spring Boot/Logback defaults). Adjust
logging.file.name or log levels in application.properties as needed.
This codebase is part of a private learning portfolio. Feel free to adapt it for experimentation, but it is not licensed for commercial redistribution.