Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Product API

A RESTful CRUD API for managing Products and their nested Items, built with Java 17 and Spring Boot. Produced for the Zest India IT Services Java Backend Developer hiring assignment.

Tech Stack

Concern Choice
Language / Runtime Java 17
Framework Spring Boot 3.3
Persistence Spring Data JPA (Hibernate)
Database PostgreSQL (MySQL also supported), H2 for tests
Security Spring Security, JWT access tokens + rotating refresh tokens
API docs springdoc-openapi (Swagger UI)
Testing JUnit 5, Mockito, Spring Boot Test, H2
Containerization Docker, Docker Compose
Build Maven

Architecture

The codebase follows a classic layered / clean architecture, one package per concern:

com.zestindia.productapi
├── controller     REST endpoints (thin: validation + delegation only)
├── service        Business logic, interfaces + impl subpackage
├── repository     Spring Data JPA repositories
├── entity         JPA entities (Product, Item, User, RefreshToken)
├── dto            Request/response DTOs, kept separate from entities
├── mapper         MapStruct entity <-> DTO mappers
├── security       JWT utilities, filter, UserDetailsService
├── config         Security, OpenAPI, async executor, dev data seeder
└── exception      Custom exceptions + centralized @RestControllerAdvice

Why this shape: controllers never touch repositories directly, entities never leak past the service layer (DTOs only cross the controller boundary), and cross-cutting concerns (error handling, security, async execution) live in their own packages so they can be reasoned about independently of business logic.

Data model

product (id, product_name, created_by, created_on, modified_by, modified_on)
item    (id, product_id -> product.id, quantity)

Product 1---N Item, matching the schema given in the assignment. Indexes are defined on product.product_name (search) and item.product_id (the FK join path used by the nested /items endpoint).

API Design

  • Versioned, resource-oriented routes under /api/v1/.
  • Item is modeled as a sub-resource of Product: /api/v1/products/{id}/items.
  • Collection endpoints (GET /products, GET /products/{id}/items) are paginated via Spring's standard ?page=&size=&sort= query params and return a consistent PageResponse<T> envelope.
  • All errors return a consistent JSON shape (ErrorResponse): timestamp, status, error, message, path, and optional field-level details for validation failures.

Security

  • Stateless JWT authentication. POST /api/v1/auth/login and /register return a short-lived access token (15 min default) and an opaque, DB-backed refresh token (7 days default).
  • Refresh tokens are rotated on every use (POST /api/v1/auth/refresh): the old token is deleted and a new pair issued, so a stolen refresh token that gets replayed after the legitimate client refreshes will fail (single active refresh token per user).
  • Role-based authorization: ROLE_USER can create/update products; only ROLE_ADMIN can delete. Reads are public. A default admin / Admin@123 user is seeded on startup outside the test profile — change this immediately in any real deployment.
  • Passwords are hashed with BCrypt. CORS is configured centrally; HTTPS termination is expected to happen at the load balancer / ingress in production (not handled in-process).

Async processing

Security/audit events (register, login, refresh) are logged through AsyncAuditService, annotated with @Async, so they never add latency to the request path — a small, honest example of "async processing where applicable" rather than forcing async onto the core CRUD path where it isn't warranted.

Running locally with Docker (recommended)

docker compose up --build

This starts PostgreSQL and the API together. The API will be available at http://localhost:8080, Swagger UI at http://localhost:8080/swagger-ui.html.

Running locally without Docker

  1. Start a PostgreSQL instance and create a productdb database (or point DB_URL at MySQL).

  2. Copy .env.example to .env and adjust credentials if needed, or export the same variables.

  3. Build and run:

    mvn clean package
    java -jar target/product-api-1.0.0.jar

    or, for iterative development:

    mvn spring-boot:run

Authentication flow

# Register
curl -X POST http://localhost:8080/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"Password123"}'

# Login
curl -X POST http://localhost:8080/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"Password123"}'

# Use the returned accessToken as a Bearer token
curl -X POST http://localhost:8080/api/v1/products \
  -H "Authorization: Bearer <accessToken>" \
  -H "Content-Type: application/json" \
  -d '{"productName":"Wireless Mouse"}'

# Refresh (rotates the refresh token)
curl -X POST http://localhost:8080/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refreshToken":"<refreshToken>"}'

API Endpoints

Method Path Auth Description
POST /api/v1/auth/register Public Create a user account
POST /api/v1/auth/login Public Obtain access + refresh tokens
POST /api/v1/auth/refresh Public Rotate refresh token
POST /api/v1/auth/logout Public Revoke a refresh token
GET /api/v1/products Public Paginated product list, ?name= filter
GET /api/v1/products/{id} Public Get one product
POST /api/v1/products USER or ADMIN Create a product
PUT /api/v1/products/{id} USER or ADMIN Update a product
DELETE /api/v1/products/{id} ADMIN only Delete a product
GET /api/v1/products/{id}/items Public Paginated items for a product
POST /api/v1/products/{id}/items USER or ADMIN Add an item to a product

Full interactive documentation: GET /swagger-ui.html (OpenAPI JSON at /v3/api-docs).

Testing

mvn test
  • Unit tests (service package): ProductServiceImplTest, ItemServiceImplTest — mock the repository/mapper layers with Mockito and assert business logic and exception paths in isolation.
  • Controller slice tests (controller package): ProductControllerTest@WebMvcTest + MockMvc, verifying HTTP status codes, validation, and role-based access without a real DB.
  • Integration tests (integration package): ProductIntegrationTest — full @SpringBootTest against an in-memory H2 database (test profile), exercising registration, login, the full product/item CRUD lifecycle, pagination, and refresh-token rotation end to end through the real Spring Security filter chain.

Configuration reference

All configuration is environment-variable driven (see application.yml / .env.example), so the same image runs unmodified across dev, docker-compose, and production — only the environment changes.

Notes / assumptions

  • Both PostgreSQL and MySQL drivers are bundled; the active one is selected via DB_DRIVER / DB_URL / JPA_DIALECT. docker-compose.yml wires up PostgreSQL by default.
  • ddl-auto: update is used for convenience in this assignment; a production setup would use a migration tool (Flyway/Liquibase) instead.
  • The submission repository does not commit real secrets — JWT_SECRET in application.yml has a placeholder default and should be overridden via environment variable in any shared environment.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages