Skip to content

krisnaajiep/springboot-expense-tracker-api

Repository files navigation

Spring Boot Expense Tracker API

Current Version Framework Redis

Simple Expense Tracker RESTful API built with Spring Boot to allow users to create, read, update, and delete expenses.

Table of Contents

General Information

Spring Boot Expense Tracker API is a simple RESTful API that allows users to manage their expenses. It supports pagination, sorting, and filtering by date range. This API uses JWT for authentication and Redis for caching. This project is designed to explore and practice working with the Java programming language, data modeling, and user authentication in Spring Boot.

Technologies Used

  • Java 21.0.6 LTS
  • Maven 3.9.11
  • H2 Database 2.3.232
  • Microsoft SQL Server 2022
  • Spring Boot 3.4.12
  • Lombok 1.18.42
  • JJWT 0.12.6
  • Redis 8.0.3
  • Flyway 10.20.1
  • Docker 29.2.0

Features

  • User Registration: Register a new user using the POST method.
  • User Login: Authenticate the user using the POST method.
  • Create a new expense: Create a new expense using the POST method.
  • Update an existing expense: Update an existing expense using the PUT method.
  • Delete an existing expense: Delete an existing expense using the DELETE method.
  • List and filter all past expenses: Get the list of expenses with pagination and filtering by date range using the GET method.
  • List all expenses categories: Get the list of expense categories using the GET method.
  • Refresh Token: Get a new access token using the POST method.
  • Revoke Tokens: Invalidates all refresh tokens for the authenticated user using the POST method.
  • Expenses Caching: Speeds up repeated requests for filtered/paginated expenses using Redis with 60-minute TTL.
  • Brute-force Protection: Automatically jail IP addresses after repeated failed login attempts.

Database Migration

Database migration is managed using Flyway.

  • The migration scripts are located in the src/main/resources/db/migration directory.
  • The initial migration script is defined in V1__Create_initial_schema.sql.
  • Migrations are automatically executed on application startup

Before running the application, make sure the database exists.

Setup

To run this API, you’ll need:

  • Java: Version 21 or higher
  • Microsoft SQL Server 2022 or higher
  • Redis: Version 8 or higher
  • Docker: Version 29 or higher (optional)

How to install:

  1. Clone the repository

    git clone https://github.com/krisnaajiep/springboot-expense-tracker-api.git
  2. Change the current working directory

    cd springboot-expense-tracker-api
    
  3. Copy and rename .env.example

    cp .env.example .env
  4. Set environment variables in .env for database and JWT secret configuration

    DATABASE_NAME=ExpenseTrackerAPI
    
    SPRING_DATASOURCE_URL=jdbc:sqlserver://localhost:1433;databaseName=${DATABASE_NAME};encrypt=true;trustServerCertificate=true
    SPRING_DATASOURCE_USERNAME=<database_username>
    SPRING_DATASOURCE_PASSWORD=<database_password>
    
    JWT_SECRET=<your_strong_secret>
    
    SPRING_DATA_REDIS_HOST=localhost
    SPRING_DATA_REDIS_PORT=6379
    SPRING_DATA_REDIS_USERNAME=<redis_username>
    SPRING_DATA_REDIS_PASSWORD=<redis_password>
    SPRING_DATA_REDIS_DATABASE=<redis_database_index>
  5. Start the application.

    • Using Docker Compose:
      • Copy and rename .env to .env.prod
        cp .env .env.prod
      • Change the SPRING_DATASOURCE_URL and SPRING_DATA_REDIS_HOST in .env.prod to use service names defined in compose.yaml
        SPRING_DATASOURCE_URL=jdbc:sqlserver://db:1433;databaseName=${DATABASE_NAME};encrypt=true;trustServerCertificate=true
        SPRING_DATA_REDIS_HOST=redis
      • Run the following command to start the services:
        docker compose up
    • Using local installations:
      • Start Microsoft SQL Server and Redis server.
      • Create the database specified in DATABASE_NAME environment variable.
      • Build the project
        ./mvnw clean package -DskipTests
      • Run the JAR file
        java -jar target/expense-tracker-api-3.0.0.jar

Usage

Example Request

  1. Register

    POST http://localhost:8080/register
    Content-Type: application/json
    
    {
      "name": "John Doe",
      "email": "john@doe.com",
      "password": "MyPass_1234"
    }
  2. Create a new expense

    POST http://localhost:8080/expenses
    Content-Type: application/json
    Authorization: Bearer <your_access_token>
    
    {
        "description": "Purchase of new computer",
        "amount": "800",
        "date": "2025-06-30",
        "categoryId": 1
    }

Expense Category

Expense categories are stored in a separate table (ExpenseCategory). Each expense references a category using a foreign key (CategoryID). Categories must exist before they can be assigned to an expense.

The following expense categories are automatically created during database migration:

  • Others
  • Health
  • Clothing
  • Utilities
  • Electronics
  • Leisure
  • Groceries

How to get all expense categories:

GET http://localhost:8080/categories
Authorization: Bearer <your_access_token>

API Documentation

Authentication

This API uses Bearer Token for authentication. You can generate an access token by registering a new user or login.

You must include an access token in each request to the API with the Authorization request header.

Authentication error response

If an API key is missing, malformed, or invalid, you will receive a 401 Unauthorized HTTP status.

{
  "message": "Unauthorized"
}

Brute-force Login Protection

This API implements brute-force protection by tracking failed login attempts per IP address using Redis.

  • After 5 consecutive failed login attempts, the IP address will be temporarily jailed.
  • While jailed, the IP cannot make further login attempts for 10 minutes.
  • The jailed IP will receive a 429 Too Many Requests HTTP status.
  • A Retry-After response header (in milliseconds) is included to indicate how long the client must wait before retrying.

Example error response when jailed:

{
  "message": "Too many failed login attempts. Please try again later."
}

Caching

This API uses a Cache-Aside strategy to cache expense listing responses.

  • Responses to GET /expenses are cached in Redis using a combination of:
    • User ID
    • Date filter (e.g. PAST_MONTH)
    • Page number, page size, sort order
  • Cache is stored for 60 minutes (TTL).
  • Whenever an expense is created, updated, or deleted, all related cache entries are automatically evicted.
  • Cache keys follow the pattern: expenses::userId=<long>:filter=<ExpenseFilter>&from=<LocalDate>&to=<LocalDate>&page=<int>&size=<int>&sort=<Sort>

See: Cache-Aside pattern

HTTP Caching

The GET /expenses endpoint also supports HTTP caching using Cache-Control and ETag headers.

  • Cache-Control: no-cache, private, must-revalidate
  • ETag: Generated from the MD5 hash of the response body.

Rate and Usage Limits

API access rate limits apply on a per-IP address basis in unit time. The limit is 10 requests per 10 seconds. If you exceed either limit, your request will return an HTTP 429 Too Many Requests status code.

Each API response returns the following set of headers to help you identify your use status:

Header Description
X-RateLimit-Limit The maximum number of requests that the consumer is permitted to make per minute.
X-RateLimit-Remaining The number of requests remaining in the current rate limit window.
X-RateLimit-Reset The time at which the current rate limit window resets in UTC epoch seconds.

HTTP Response Codes

The API returns the following status codes depending on the success or failure of the request.

Status Code Description
200 OK The request was processed successfully.
201 Created The new resource was created successfully.
400 Bad Request The server could not understand the request due to invalid syntax.
401 Unauthorized Authentication is required or the access token is invalid.
403 Forbidden Access to the requested resource is forbidden.
404 Not Found The requested resource was not found.
405 Method Not Allowed The HTTP method is not supported for the requested resource.
409 Conflict Indicates a conflict between the request and the current state of a resource on a web server.
415 Unsupported Media Type The media format of the requested data is not supported by the server.
429 Too Many Request The client has sent too many requests in a given amount of time (rate limiting).
500 Internal Server Error An unexpected server error occurred.
503 Service Unavailable The server is temporarily unable to handle the request, usually due to maintenance or overload.

Project Status

Project is: complete.

CI
CD

Acknowledgements

This project was inspired by roadmap.sh.

License

This project is licensed under the MIT License—see the LICENSE file for details.

About

An Expense Tracker RESTful API built with Spring Boot that allow users to create, read, update, and delete expenses.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages