Skip to content

AlesixDev/example-api-spring

Repository files navigation

Spring Boot REST API with PostgreSQL and Redis

A complete REST API implementation using Spring Boot 3.x, PostgreSQL, and Redis for caching, fully containerized with Docker.

Features

  • ✅ RESTful API with CRUD operations
  • ✅ API Key authentication via header (X-API-Key)
  • ✅ PostgreSQL database with JPA/Hibernate
  • ✅ Redis caching with 5-minute TTL
  • ✅ HikariCP connection pooling
  • ✅ Health check endpoint for monitoring
  • ✅ Complete Docker setup with docker-compose
  • ✅ Multi-stage Dockerfile for optimized builds
  • ✅ Automatic API key generation on first run

Tech Stack

  • Backend: Java 21 with Spring Boot 3.5.6
  • Database: PostgreSQL 15
  • Cache: Redis 7
  • Build Tool: Maven
  • Containerization: Docker & Docker Compose

Prerequisites

  • Docker and Docker Compose installed
  • Git (to clone the repository)

Quick Start

1. Clone the repository (if applicable)

git clone https://github.com/AlesixDev/example-api-spring
cd example-api-spring

2. Start all services with Docker Compose

docker-compose up --build

This command will:

  • Build the Spring Boot application
  • Start PostgreSQL database
  • Start Redis cache
  • Start the API application

3. Get the API Key

On the first run, the application automatically generates an API key. Check the logs:

docker-compose logs api | grep "API KEY GENERATED"

You should see output like:

================================================================================
NEW API KEY GENERATED: 12345678-90ab-cdef-1234-567890abcdef
Please save this API key. Use it in the X-API-Key header for all requests.
================================================================================

Save this API key - you'll need it for all API requests.

API Endpoints

Base URL

http://localhost:8080

1. Health Check

No authentication required

GET /healthcheck

Example:

![img.png](img.png)curl http://localhost:8080/healthcheck

Response (200 OK):

{
  "status": "UP",
  "timestamp": "2025-10-03T10:30:00",
  "components": {
    "database": {
      "status": "UP",
      "details": {
        "database": "PostgreSQL",
        "validationQuery": "SELECT 1",
        "responseTime": "5ms"
      }
    },
    "redis": {
      "status": "UP",
      "details": {
        "responseTime": "2ms"
      }
    }
  }
}

2. Create Record

Requires API Key

POST /data

Headers:

  • X-API-Key: [your-api-key]
  • Content-Type: application/json

Request Body:

{
  "name": "Test Record",
  "description": "This is a test description"
}

Example:

curl -X POST http://localhost:8080/data \
  -H "X-API-Key: 12345678-90ab-cdef-1234-567890abcdef" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Test Record",
    "description": "This is a test description"
  }'

Response (201 Created):

{
  "id": 1,
  "name": "Test Record",
  "description": "This is a test description",
  "created_at": "2025-10-03T10:30:00",
  "updated_at": "2025-10-03T10:30:00"
}

3. Get All Records

Requires API Key

GET /data?sort=id&direction=ASC

Headers:

  • X-API-Key: [your-api-key]

Query Parameters:

  • sort (optional): Field to sort by (default: id)
  • direction (optional): ASC or DESC (default: ASC)

Example:

curl -X GET "http://localhost:8080/data?sort=name&direction=DESC" \
  -H "X-API-Key: 12345678-90ab-cdef-1234-567890abcdef"

Response (200 OK):

{
  "data": [
    {
      "id": 1,
      "name": "Test Record",
      "description": "This is a test description",
      "created_at": "2025-10-03T10:30:00",
      "updated_at": "2025-10-03T10:30:00"
    }
  ],
  "total": 1
}

Caching: Results are cached in Redis for 5 minutes. Subsequent requests return cached data.


4. Get Record by ID

Requires API Key

GET /data/{id}

Headers:

  • X-API-Key: [your-api-key]

Example:

curl -X GET http://localhost:8080/data/1 \
  -H "X-API-Key: 12345678-90ab-cdef-1234-567890abcdef"

Response (200 OK):

{
  "id": 1,
  "name": "Test Record",
  "description": "This is a test description",
  "created_at": "2025-10-03T10:30:00",
  "updated_at": "2025-10-03T10:30:00"
}

Response (404 Not Found):

{
  "error": "Not Found",
  "message": "Registro con ID 999 no encontrado",
  "timestamp": "2025-10-03T10:30:00",
  "path": "/data/999"
}

Caching: Individual records are cached by ID.


Error Responses

401 Unauthorized (Missing or Invalid API Key)

{
  "error": "Unauthorized",
  "message": "API Key faltante o inválida"
}

400 Bad Request (Validation Error)

{
  "error": "Bad Request",
  "message": "Name is required",
  "timestamp": "2025-10-03T10:30:00",
  "path": "/data"
}

404 Not Found

{
  "error": "Not Found",
  "message": "Registro con ID 999 no encontrado",
  "timestamp": "2025-10-03T10:30:00",
  "path": "/data/999"
}

500 Internal Server Error

{
  "error": "Internal Server Error",
  "message": "An unexpected error occurred",
  "timestamp": "2025-10-03T10:30:00",
  "path": "/data"
}

503 Service Unavailable (Health Check Failed)

{
  "status": "DOWN",
  "timestamp": "2025-10-03T10:30:00",
  "components": {
    "database": {
      "status": "DOWN",
      "error": "Connection refused"
    },
    "redis": {
      "status": "DOWN",
      "error": "Connection timeout"
    }
  }
}

Architecture Details

Database Schema

Table: test_table

CREATE TABLE test_table (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    description TEXT,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Table: api_keys

CREATE TABLE api_keys (
    id BIGSERIAL PRIMARY KEY,
    key_value VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    is_active BOOLEAN DEFAULT TRUE
);

Connection Pooling (HikariCP)

  • Maximum pool size: 10 connections
  • Minimum idle: 5 connections
  • Connection timeout: 30 seconds
  • Idle timeout: 10 minutes
  • Max lifetime: 30 minutes

Connections are kept open and reused for optimal performance.

Redis Caching

  • Cache Type: Redis with Lettuce client
  • TTL (Time To Live): 5 minutes (300 seconds)
  • Serialization: JSON with Jackson
  • Cache Keys:
    • List queries: testTableList::{sort}_{direction}
    • Individual records: testTableById::{id}
  • Cache Invalidation: Automatically clears list cache when new records are created

Authentication

  • Method: API Key in HTTP header
  • Header Name: X-API-Key
  • Generation: Automatic UUID generation on application startup
  • Validation: Interceptor checks all requests (except /healthcheck)

Development Commands

View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f api
docker-compose logs -f postgres
docker-compose logs -f redis

Stop Services

docker-compose down

Stop and Remove Volumes (Clean Start)

docker-compose down -v

Rebuild Application

docker-compose up --build

Access Database

docker exec -it api-postgres psql -U apiuser -d testdb

Access Redis CLI

docker exec -it api-redis redis-cli

Check Redis Cache

docker exec -it api-redis redis-cli
> KEYS *
> GET "testTableList::id_ASC"

Project Structure

example-api-spring/
├── src/
│   ├── main/
│   │   ├── java/dev/alesixdev/example_api_spring/
│   │   │   ├── config/           # Configuration classes
│   │   │   │   ├── ApiKeyInterceptor.java
│   │   │   │   ├── RedisCacheConfig.java
│   │   │   │   └── WebConfig.java
│   │   │   ├── controller/       # REST controllers
│   │   │   │   ├── HealthCheckController.java
│   │   │   │   └── TestTableController.java
│   │   │   ├── dto/              # Data Transfer Objects
│   │   │   │   ├── ErrorResponse.java
│   │   │   │   ├── HealthCheckResponse.java
│   │   │   │   ├── TestTableListResponse.java
│   │   │   │   ├── TestTableRequest.java
│   │   │   │   └── TestTableResponse.java
│   │   │   ├── entity/           # JPA Entities
│   │   │   │   ├── ApiKey.java
│   │   │   │   └── TestTable.java
│   │   │   ├── exception/        # Exception handlers
│   │   │   │   └── GlobalExceptionHandler.java
│   │   │   ├── repository/       # JPA Repositories
│   │   │   │   ├── ApiKeyRepository.java
│   │   │   │   └── TestTableRepository.java
│   │   │   ├── service/          # Business logic
│   │   │   │   ├── ApiKeyService.java
│   │   │   │   ├── HealthCheckService.java
│   │   │   │   └── TestTableService.java
│   │   │   └── ExampleApiSpringApplication.java
│   │   └── resources/
│   │       └── application.properties
│   └── test/
├── Dockerfile
├── docker-compose.yml
├── init.sql
├── pom.xml
└── README.md

Configuration

Environment Variables

Docker Compose sets these automatically:

Variable Default Description
SPRING_DATASOURCE_URL jdbc:postgresql://postgres:5432/testdb PostgreSQL connection URL
SPRING_DATASOURCE_USERNAME apiuser Database username
SPRING_DATASOURCE_PASSWORD apipass Database password
SPRING_REDIS_HOST redis Redis host
SPRING_REDIS_PORT 6379 Redis port

Application Properties

See src/main/resources/application.properties for all configuration options.


Testing the API

Full Test Sequence

  1. Check health:
curl http://localhost:8080/healthcheck
  1. Create a record:
curl -X POST http://localhost:8080/data \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"First Record","description":"My first test"}'
  1. Get all records (first call - from database):
curl -X GET http://localhost:8080/data \
  -H "X-API-Key: YOUR_API_KEY"
  1. Get all records again (from Redis cache):
curl -X GET http://localhost:8080/data \
  -H "X-API-Key: YOUR_API_KEY"
  1. Get specific record:
curl -X GET http://localhost:8080/data/1 \
  -H "X-API-Key: YOUR_API_KEY"

Troubleshooting

Issue: Cannot connect to database

Solution: Ensure PostgreSQL container is healthy:

docker-compose ps
docker-compose logs postgres

Issue: Cannot connect to Redis

Solution: Check Redis container status:

docker-compose ps
docker-compose logs redis

Issue: API returns 401 Unauthorized

Solution: Verify you're using the correct API key from the logs:

docker-compose logs api | grep "API KEY"

Issue: Changes not reflected

Solution: Rebuild the application:

docker-compose down
docker-compose up --build

Issue: Port already in use

Solution: Change ports in docker-compose.yml or stop the conflicting service.


License

This project is provided as-is for educational and demonstration purposes.


Support

For issues, questions, or contributions, please refer to the project repository or documentation.

About

Sample data management API created in SpringBoot with Claude

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published