A learning REST API for a marketplace built with Spring Boot + MyBatis + PostgreSQL.
The project implements a basic marketplace flow:
- user registration and login
- viewing users, products, and orders
- creating products
- creating orders
- updating order status
- request validation
- global error handling
- password hashing with BCrypt
- Java 17
- Spring Boot
- Spring Web
- Spring Security
- Spring Validation
- MyBatis
- PostgreSQL
- Gradle
The project is split into layers:
controller— HTTP endpointsservice— business logicmapper— database access through MyBatismodel— internal domain modelsdto— API request/response objectsexceptions— custom exceptions and global exception handlerconfig— security and infrastructure configuration
- the product exists
- there is enough stock
- the buyer has enough balance
- the buyer is not trying to buy their own product
- the buyer's balance is reduced
- the product stock is reduced
- a new order record is created
ORDEREDSHIPPEDREADY_TO_CLAIMCOMPLETED
Allowed transitions:
ORDERED -> SHIPPEDSHIPPED -> READY_TO_CLAIMREADY_TO_CLAIM -> COMPLETED
- the seller receives the order amount
- the seller's
salesvalue is increased
- the seller must exist
- the price must be greater than 0
- stock cannot be negative
- if the seller has fewer than 2 sales, the product price is deducted from the seller's balance
- if the seller has 2 or more sales, the product is created without balance deduction
- user passwords are stored as hashes using
BCryptPasswordEncoder - login checks passwords through
PasswordEncoder.matches(...) - Spring Security's default HTML login form is disabled
- API endpoints are open for simpler development and testing
Request DTOs use jakarta.validation annotations such as:
@NotBlank@NotNull@Positive@PositiveOrZero
Validation errors are returned as 400 Bad Request.
The project uses custom exceptions:
BadRequestExceptionNotFoundExceptionConflictException
A global @ControllerAdvice returns structured JSON error responses.
Example:
{
"timestamp": "2026-04-26T00:50:37.878Z",
"status": 400,
"error": "Bad Request",
"message": "amount: must be greater than 0",
"path": "/orders"
}idnamepassword_hashbalancecountrycreated_atsales
idseller_idtitledescriptionpricestockcreated_at
idbuyer_idseller_idproduct_idorder_pricedestinationstatusamountcreated_atupdated_at
Register a new user.
Request:
{
"name": "alex",
"password": "alex123",
"country": "EE"
}Response:
{
"id": 13,
"name": "alex",
"balance": 0,
"country": "EE",
"sales": 0
}Login user.
Request:
{
"name": "michael",
"password": "michael123"
}Get all users.
Get user by id.
Get all orders where the user is the buyer.
Get all products owned by the user as seller.
Get all products.
Get product by id.
Create a product.
Request:
{
"sellerId": 1,
"title": "Mechanical Keyboard",
"description": "RGB keyboard",
"price": 95.00,
"stock": 5
}Get all orders.
Get order by id.
Create an order.
Request:
{
"buyerId": 2,
"productId": 1,
"destination": "EE",
"amount": 1
}Update an order status.
Request:
{
"status": "SHIPPED"
}For Windows cmd:
docker run --name marketplace-postgres -e POSTGRES_DB=marketplace_db -e POSTGRES_USER=marketplace_user -e POSTGRES_PASSWORD=marketplace_pass -p 5432:5432 -d postgresCheck that the container is running:
docker pssrc/main/resources/application.properties:
spring.application.name=demo1
spring.datasource.url=jdbc:postgresql://localhost:5432/marketplace_db
spring.datasource.username=marketplace_user
spring.datasource.password=marketplace_pass
spring.datasource.driver-class-name=org.postgresql.Driver
spring.sql.init.mode=alwaysWith Gradle:
./gradlew bootRunor run Demo1Application directly from IntelliJ IDEA.
On startup, the application uses:
schema.sqldata.sql
schema.sql creates the tables.
data.sql fills the database with test users, products, and orders.
Example users from data.sql:
michael / michael123agatha / agatha123oliver / oliver123sophia / sophia123liam / liam123emma / emma123
- invalid request body
- missing required field
amount <= 0- invalid status transition
- user not found
- product not found
- order not found
- username already exists
- insufficient balance
- insufficient stock
- attempt to buy your own product
- unit and integration tests
- Swagger / OpenAPI
- JWT authentication
- pagination and filtering
- moving auth logic into a separate
AuthService - stricter role-based security
- Docker Compose for app and database
- CI/CD
This is a learning project, but it already includes:
- user, product, and order endpoints
- marketplace business rules
- request validation
- centralized error handling
- password hashing
- transactional updates for balance and stock