Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Food Ordering System — Spring Boot REST API

A complete, layered Spring Boot backend for a food-ordering platform: users register/login, browse restaurants and menus, place orders, and admins manage restaurants/menu items and order status.

Tech stack

Concern Technology
Language / runtime Java 17
Framework Spring Boot 3.3.4
Data access Spring Data JPA + Hibernate
Database PostgreSQL
Boilerplate reduction Lombok
Security Spring Security + JWT (stateless)
Validation Jakarta Bean Validation (@Valid)
Error handling @RestControllerAdvice global handler

Project layout

src/main/java/com/example/demo
├── config/            SecurityConfig, DataSeeder (seeds a default admin)
├── controller/        REST controllers (Auth, Restaurant, MenuItem, Order)
├── dto/
│   ├── request/       Input DTOs with bean-validation annotations
│   └── response/      Output DTOs, ApiResponse<T> envelope, ErrorResponse
├── entity/            JPA entities (User, Restaurant, MenuItem, Order, OrderItem) + enums
├── exception/         Custom exceptions + GlobalExceptionHandler
├── repository/        Spring Data JPA repositories
├── security/          JWT util, filter, UserDetails adapter, entry point / access-denied handler
└── service/           Service interfaces + impl/ (business logic, transactions)

1. Prerequisites

  • JDK 17+
  • Maven 3.9+ (or use your IDE's bundled Maven)
  • PostgreSQL running locally with a database named demo

Create the database (credentials already match application.properties):

CREATE DATABASE demo;
CREATE USER "user" WITH PASSWORD 'gI7_36rpP_aQEj5FyJp9';
GRANT ALL PRIVILEGES ON DATABASE demo TO "user";

If you already have a user/database with those exact credentials, skip this step — spring.jpa.hibernate.ddl-auto=update will auto-create all tables on first run.

2. Run it

cd food-ordering-app
mvn spring-boot:run

Or import the folder as a Maven project into IntelliJ IDEA / Eclipse / VS Code and run DemoApplication.java directly.

The app starts on http://localhost:8080.

On first boot, DataSeeder automatically creates a default admin account (see application.properties):

email:    admin@foodapp.com
password: Admin@123

Use it to log in and manage restaurants/menu items right away.

3. Authentication flow

  1. POST /api/auth/register → creates a CUSTOMER account, returns a JWT.
  2. POST /api/auth/login → returns a JWT for any existing account (customer or admin).
  3. Send the token on every protected request: Authorization: Bearer <token>

Admin-only endpoints are protected with @PreAuthorize("hasRole('ADMIN')").

4. API reference

Auth (public)

Method Endpoint Body
POST /api/auth/register { name, email, password, phone, address }
POST /api/auth/login { email, password }

Restaurants

Method Endpoint Access
GET /api/restaurants public
GET /api/restaurants/{id} public
POST /api/restaurants ADMIN
PUT /api/restaurants/{id} ADMIN
DELETE /api/restaurants/{id} ADMIN

Menu items

Method Endpoint Access
GET /api/restaurants/{restaurantId}/menu-items public
GET /api/menu-items/{id} public
POST /api/restaurants/{restaurantId}/menu-items ADMIN
PUT /api/menu-items/{id} ADMIN
DELETE /api/menu-items/{id} ADMIN

Orders

Method Endpoint Access
POST /api/orders authenticated (places order)
GET /api/orders/my authenticated (own orders)
GET /api/orders/{id} owner or ADMIN
PATCH /api/orders/{id}/cancel owner (only while PLACED)
GET /api/orders ADMIN (all orders)
PATCH /api/orders/{id}/status ADMIN (advance order status)

5. Example requests (curl)

# Register
curl -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name":"Jane Doe","email":"jane@example.com","password":"secret123","phone":"9999999999","address":"221B Baker St"}'

# Login as admin
curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"admin@foodapp.com","password":"Admin@123"}'

# Create a restaurant (use the admin token from above)
curl -X POST http://localhost:8080/api/restaurants \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -d '{"name":"Pizza Palace","description":"Best pizza in town","address":"12 Main St","phone":"1234567890"}'

# Add a menu item to restaurant id=1
curl -X POST http://localhost:8080/api/restaurants/1/menu-items \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <ADMIN_TOKEN>" \
  -d '{"name":"Margherita Pizza","description":"Classic cheese & tomato","price":9.99,"category":"Pizza"}'

# Place an order (as the customer, "jane" token)
curl -X POST http://localhost:8080/api/orders \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <CUSTOMER_TOKEN>" \
  -d '{"restaurantId":1,"deliveryAddress":"221B Baker St","items":[{"menuItemId":1,"quantity":2}]}'

6. Response shape

Success — every endpoint returns a consistent envelope:

{
  "success": true,
  "message": "Order placed successfully",
  "data": { "...": "..." },
  "timestamp": "2026-07-27T10:00:00"
}

Error — handled centrally by GlobalExceptionHandler:

{
  "timestamp": "2026-07-27T10:00:00",
  "status": 404,
  "error": "Not Found",
  "message": "Restaurant not found with id : '99'",
  "path": "/api/restaurants/99"
}

Validation errors additionally include a validationErrors map of field -> message.

7. Exception-handling concepts demonstrated

  • Custom exceptions: ResourceNotFoundException (404), DuplicateResourceException (409), BadRequestException (400), UnauthorizedActionException (403)
  • Centralized @RestControllerAdvice translating exceptions → consistent JSON
  • Bean Validation (@Valid, @NotBlank, @Email, @DecimalMin, etc.) with field-level error maps
  • Spring Security exception translation via custom AuthenticationEntryPoint (401) and AccessDeniedHandler (403), instead of Spring's default HTML/whitelabel pages
  • DataIntegrityViolationException handling for DB constraint races (e.g. duplicate email)
  • Generic fallback handler so unexpected exceptions never leak stack traces to clients

8. Notes / production hardening ideas

  • Move jwt.secret and DB credentials to environment variables / a secrets manager before deploying — they're in application.properties here only for local dev convenience.
  • Add refresh tokens / token blacklisting if you need logout-before-expiry.
  • Add pagination (Pageable) to the GET /api/restaurants, /api/orders list endpoints as data grows.
  • Add integration tests with Testcontainers (spin up a real Postgres for mvn test) — the current DemoApplicationTests is a minimal context-load smoke test and expects the configured Postgres instance to be reachable.

9. Docker Setup

This guide explains how to run the application using Docker with your existing local PostgreSQL.

Prerequisites

  • Docker Engine 20.10+
  • Docker Compose V2 (docker compose command)
  • PostgreSQL running locally on port 5432
  • Database demo exists with the correct user/credentials

Quick Start

1. Ensure PostgreSQL is Running

# Check if PostgreSQL is running
sudo systemctl status postgresql

# Start if needed
sudo systemctl start postgresql

# Create database if not exists
sudo -u postgres psql -c "CREATE DATABASE demo;"
sudo -u postgres psql -c "CREATE USER \"user\" WITH PASSWORD 'gI7_36rpP_aQEj5FyJp9';"
sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE demo TO \"user\";"

Important: The username user is a PostgreSQL reserved word, so it must be double-quoted in SQL statements. The application.properties file handles this correctly via spring.datasource.username=user.

2. Build and Start the Application

make up

Or directly:

docker compose up --build

This will:

  • Build the Spring Boot application (multi-stage Docker build with Maven)
  • Run database migrations automatically (Hibernate ddl-auto=update)
  • Seed the default admin user via DataSeeder
  • Expose the app on http://localhost:8080

3. Stop the Application

make down

Or directly:

docker compose down

How It Works

The container uses host networking (network_mode: host) to directly access your local PostgreSQL at localhost:5432. This avoids the complexity of Docker networking between containers and keeps the database on the host machine.

Dockerfile Strategy

The Dockerfile uses a multi-stage build:

Stage Base Image Purpose
build maven:3.9.9-amazoncorretto-17-alpine Compiles the application with Maven
final amazoncorretto:17-alpine Runs the compiled JAR in a lightweight runtime

This keeps the final image small (~200MB) by excluding Maven and build-time dependencies.

Key Configuration (docker-compose.yml)

services:
  app:
    build: .
    network_mode: host
    environment:
      SPRING_DATASOURCE_URL: jdbc:postgresql://localhost:5432/demo
      SPRING_DATASOURCE_USERNAME: user
      SPRING_DATASOURCE_PASSWORD: gI7_36rpP_aQEj5FyJp9

Environment variables override the values in application.properties, making the container portable across environments.

Docker Architecture

┌─────────────────────────────────────────────────┐
│                  Host Machine                    │
│                                                   │
│   ┌─────────────┐          ┌──────────────┐      │
│   │   Browser   │          │  PostgreSQL  │      │
│   │             │          │  :5432       │      │
│   └──────┬──────┘          └──────▲───────┘      │
│          │                         │              │
│          │ HTTP                   │ TCP          │
│          │                         │              │
│          ▼                         │              │
│   ┌─────────────────────────────────┐            │
│   │   Docker Container (Spring)    │            │
│   │   network_mode: host           │            │
│   │   :8080                        │────────────┘
│   └─────────────────────────────────┘            │
└─────────────────────────────────────────────────┘

Access the Application

Resource URL
API Base URL http://localhost:8080/api
Health Check http://localhost:8080/actuator/health
Admin Login POST /api/auth/login with admin@foodapp.com / Admin@123

Docker Compose Reference

The docker-compose.yml defines a single service:

version: '3.8'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    network_mode: host
    environment:
      - SPRING_PROFILES_ACTIVE=default
      - SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/demo
      - SPRING_DATASOURCE_USERNAME=user
      - SPRING_DATASOURCE_PASSWORD=gI7_36rpP_aQEj5FyJp9
      - JWT_SECRET=your-production-secret-key-change-me-32-chars-min
      - JWT_EXPIRATION_MS=86400000

Environment Variables

Variable Description Default (application.properties)
SPRING_DATASOURCE_URL PostgreSQL JDBC URL jdbc:postgresql://localhost:5432/demo
SPRING_DATASOURCE_USERNAME DB username user
SPRING_DATASOURCE_PASSWORD DB password gI7_36rpP_aQEj5FyJp9
JWT_SECRET HMAC-SHA256 signing key (min 32 chars) (set in properties)
JWT_EXPIRATION_MS Token lifetime in milliseconds 86400000 (24h)

Quick Commands

make up       # Build and start
make down     # Stop and remove container
make build    # Build Docker image only
make clean    # Build without cache (fresh build)
docker compose logs -f app   # Follow application logs
docker compose exec app sh   # Shell into running container

Makefile Reference

The Makefile provides the following targets:

Target Command Description
up docker compose up --build -d Build and start in detached mode
down docker compose down Stop and remove the container
build docker compose build Build image only (no start)
clean docker compose build --no-cache Rebuild from scratch

Troubleshooting

1. PostgreSQL Connection Refused

# Verify PostgreSQL is running
sudo systemctl status postgresql

# Check if it's listening on the default port
ss -tlnp | grep 5432

# Test connection from inside the container
docker compose run --rm app sh -c "nc -zv localhost 5432"

2. "Role 'user' does not exist"

# Create the user role (note the double quotes for the reserved word)
sudo -u postgres psql -c "CREATE ROLE \"user\" WITH LOGIN PASSWORD 'gI7_36rpP_aQEj5FyJp9';"

3. Container Exits Immediately

# Check logs
docker compose logs app

Common causes:

  • PostgreSQL not running / unreachable
  • Invalid database credentials
  • Port 8080 already in use on the host

4. Permission Denied for docker compose

# Add your user to the docker group
sudo usermod -aG docker $USER
# Log out and back in, or run:
newgrp docker

Security Considerations

⚠️ Production Warning The credentials in application.properties and docker-compose.yml are for local development only. Before deploying:

  1. Change all default credentials — use environment variables or a secrets manager
  2. Use a strong JWT secret — at least 32 characters, cryptographically random:
    openssl rand -base64 32
  3. Set environment variables in production:
    export SPRING_DATASOURCE_PASSWORD='<strong-password>'
    export JWT_SECRET='<random-64-char-string>'
  4. Disable Docker host networking in production — use a Docker network or Kubernetes pod networking instead
  5. Add SSL/TLS via a reverse proxy (nginx, Traefik, or Spring Cloud Gateway)
  6. Use a non-root user inside the container (the Dockerfile already creates one via the USER directive)

About

A Spring Boot 3.3 backend for food ordering. Features JWT authentication, role-based authorization (CUSTOMER/ADMIN), restaurant & menu CRUD, order placement & tracking, input validation, centralized error handling, and Docker support with PostgreSQL.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages