Skip to content

Repository files navigation

jshortlink

A Spring Boot URL shortener API with JWT authentication, PostgreSQL persistence, Flyway migrations, and public redirect links.

Tech Stack

  • Java 25
  • Spring Boot 4.0.6
  • Spring Web MVC
  • Spring Security
  • Spring Data JPA
  • Flyway
  • PostgreSQL
  • Gradle Kotlin DSL

Requirements

  • JDK 25, only for running the app directly on the host
  • Docker and Docker Compose, for the containerized app and PostgreSQL
  • OpenSSL, only if you need to regenerate JWT keys

Configuration

The default configuration is in src/main/resources/application.yaml.

Variable Default Description
DB_URL jdbc:postgresql://database:5432/jshortlinkdb PostgreSQL JDBC URL
DB_USERNAME admin Database username
DB_PASSWORD admin Database password
jwt.private-key classpath:jwt/private.pem RSA private key used to sign access tokens
jwt.public-key classpath:jwt/public.pem RSA public key used to verify access tokens
jwt.token.expiration 3600000 Access token lifetime in milliseconds

JWT key files are expected at:

src/main/resources/jwt/private.pem
src/main/resources/jwt/public.pem

If they are missing, generate a local key pair:

mkdir -p src/main/resources/jwt
openssl genrsa -out src/main/resources/jwt/private.pem 2048
openssl rsa -in src/main/resources/jwt/private.pem -pubout -out src/main/resources/jwt/public.pem

Run with Docker

Start the API and PostgreSQL:

docker compose up --build

Run in the background:

docker compose up -d --build

The Docker Compose setup starts:

  • server, built from docker/Dockerfile target dev
  • database, using postgres:18
  • a named PostgreSQL data volume, pg_volume
  • a named Gradle cache volume, gradle_cache

The development container mounts the project into /app, runs ./gradlew bootRun, and keeps ./gradlew -t classes running so Spring Boot DevTools can restart the app after source changes.

The API is available at:

http://localhost:8080

Useful Docker commands:

docker compose logs -f server
docker compose down
docker compose down -v

docker compose down -v removes the PostgreSQL and Gradle cache volumes.

Build the production image:

docker build -f docker/Dockerfile --target prod -t jshortlink:latest .

Run the production image against a PostgreSQL instance on the host:

docker run --rm -p 8080:8080 \
  --add-host=host.docker.internal:host-gateway \
  -e DB_URL=jdbc:postgresql://host.docker.internal:5432/jshortlinkdb \
  -e DB_USERNAME=admin \
  -e DB_PASSWORD=admin \
  jshortlink:latest

Run Locally

Start PostgreSQL:

docker compose up -d database

Run the application:

DB_URL=jdbc:postgresql://localhost:5432/jshortlinkdb ./gradlew bootRun

The API starts on the default Spring Boot port:

http://localhost:8080

Run tests:

./gradlew test

Database

Flyway migrations are stored in src/main/resources/db/migrations.

Current migrations create:

  • user
  • role
  • user_role
  • link

Registration assigns the USER role, but the current migrations do not seed roles. Before registering users, insert the default roles:

docker compose exec database psql -U admin -d jshortlinkdb -c "INSERT INTO \"role\" (name, description) SELECT 'USER', 'Default user' WHERE NOT EXISTS (SELECT 1 FROM \"role\" WHERE name = 'USER');"
docker compose exec database psql -U admin -d jshortlinkdb -c "INSERT INTO \"role\" (name, description) SELECT 'ADMIN', 'Admin user' WHERE NOT EXISTS (SELECT 1 FROM \"role\" WHERE name = 'ADMIN');"

API

Public endpoints:

Method Path Description
GET /health Health check
POST /api/auth/register Register a user
POST /api/auth/login Login and receive an access token
GET /pub/link/{shortCode} Redirect to the original URL

Protected endpoints require:

Authorization: Bearer <accessToken>
Method Path Description
GET /api/me Get the current authenticated user
GET /api/link Search current user's links
POST /api/link Create a shortened link

Example Requests

Register:

curl -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "fullName": "Demo User",
    "username": "demo",
    "password": "password"
  }'

Login:

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

Create a link:

curl -X POST http://localhost:8080/api/link \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <accessToken>" \
  -d '{
    "originalUrl": "https://example.com/docs"
  }'

Search links:

curl "http://localhost:8080/api/link?page=0&pageSize=10&sort=CREATED_AT&order=DESC" \
  -H "Authorization: Bearer <accessToken>"

Open a public short link:

curl -i http://localhost:8080/pub/link/<shortCode>

Link Validation

POST /api/link validates originalUrl with the application link regex:

^(https?://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(/[^\s]*)?$

Examples that pass:

  • https://example.com
  • http://example.com/path
  • example.com/docs

Examples that fail:

  • asdsad
  • localhost
  • ftp://example.com

About

Support reduce the length of link

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages