Skip to main content
This guide covers everything you need for productive local development with the ADMA URL Shortener.

Architecture Overview

The local development environment uses Docker Compose to orchestrate three services:

Environment Configuration

Backend Variables

The backend reads configuration from environment variables defined in docker-compose.yml. You can override any of these by creating a .env file in the project root:
services:
  backend:
    environment:
      DB_HOST: postgres
      DB_PORT: 5432
      DB_NAME: urlshortener
      DB_USERNAME: postgres
      DB_PASSWORD: changeme
      JWT_SECRET: changeme-this-must-be-at-least-32-chars-long!!
      JWT_EXPIRATION_MS: 86400000  # 24 hours
      SERVER_PORT: 8080
      APP_BASE_URL: http://localhost:8080
      CORS_ALLOWED_ORIGINS: http://localhost,http://localhost:80,http://localhost:3000,http://localhost:5173,http://frontend:80
After creating or modifying .env, restart services: docker compose up -d

Backend Environment Variables Reference

VariableRequiredDefaultDescription
DB_HOSTYespostgresPostgreSQL hostname (use service name in Docker Compose)
DB_PORTYes5432PostgreSQL port
DB_NAMEYesurlshortenerDatabase name
DB_USERNAMEYespostgresDatabase user
DB_PASSWORDYeschangemeDatabase password
JWT_SECRETYes(default provided)JWT signing key (min. 32 characters)
JWT_EXPIRATION_MSNo86400000JWT token lifetime in milliseconds (24h default)
SERVER_PORTNo8080Backend server port
APP_BASE_URLYeshttp://localhost:8080Base URL for generating short links
CORS_ALLOWED_ORIGINSYes(localhost variants)Comma-separated list of allowed origins
Security: The default JWT_SECRET is for development only. Generate a secure secret for production:
openssl rand -base64 48

Frontend Variables

The frontend uses Vite, which embeds environment variables at build time. The backend API URL is configured via VITE_API_BASE_URL:
services:
  frontend:
    build:
      context: ./frontend
      args:
        VITE_API_BASE_URL: ${VITE_API_BASE_URL:-http://localhost:8080}
Important: VITE_API_BASE_URL is baked into the static JavaScript bundle during build. If you change it, you must rebuild the frontend:
docker compose up -d --build frontend

Hot Reload & Development Workflow

Backend Hot Reload

The backend Dockerfile uses a multi-stage build that compiles the full JAR. For faster development:
  1. Stop the Docker backend: docker compose stop backend
  2. Run the backend directly with Gradle:
cd backend
./gradlew bootRun
DevTools will automatically restart the app when you change Java files.
Make sure PostgreSQL is still running: docker compose up -d postgres

Option 2: Volume Mount (Advanced)

Mount your local source code into the container for quicker rebuilds:
docker-compose.override.yml
services:
  backend:
    volumes:
      - ./backend/src:/app/src:ro
    command: gradle bootRun --continuous

Frontend Hot Reload

For frontend development with instant refresh:
  1. Stop the Docker frontend: docker compose stop frontend
  2. Run Vite dev server locally:
cd frontend
bun install
bun run dev
The dev server runs at http://localhost:5173 with HMR (Hot Module Replacement).
Make sure your .env.local file has:
VITE_API_BASE_URL=http://localhost:8080

Debugging

Backend Debugging with IntelliJ IDEA

1

Enable debug port in docker-compose

Create docker-compose.override.yml:
services:
  backend:
    environment:
      JAVA_TOOL_OPTIONS: -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
    ports:
      - "5005:5005"
Restart: docker compose up -d backend
2

Configure IntelliJ Remote Debug

  1. Go to RunEdit Configurations
  2. Click +Remote JVM Debug
  3. Set Host: localhost, Port: 5005
  4. Click OK
3

Start debugging

Set breakpoints in your Java code and click the Debug button. IntelliJ will attach to the running container.

Backend Debugging with VS Code

Add to .vscode/launch.json:
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Attach to Backend",
      "request": "attach",
      "hostName": "localhost",
      "port": 5005
    }
  ]
}

Frontend Debugging

Use browser DevTools or VS Code’s built-in debugger:
.vscode/launch.json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "chrome",
      "request": "launch",
      "name": "Launch Chrome",
      "url": "http://localhost:5173",
      "webRoot": "${workspaceFolder}/frontend/src"
    }
  ]
}

Database Access

Connect with psql

docker compose exec postgres psql -U postgres -d urlshortener
Useful commands:
-- List all tables
\dt

-- Show table schema
\d short_url

-- Query recent links
SELECT short_code, original_url, created_at FROM short_url ORDER BY created_at DESC LIMIT 10;

-- Count active links
SELECT link_status, COUNT(*) FROM short_url GROUP BY link_status;

Connect with GUI Tools

Use any PostgreSQL client with these credentials:
SettingValue
Hostlocalhost
Port5432
Databaseurlshortener
Usernamepostgres
Passwordchangeme
Popular clients: pgAdmin, DBeaver, TablePlus, Postico

Reset Database

To start fresh with a clean database:
# Delete volume and recreate
docker compose down -v
docker compose up -d
This will permanently delete all data including users, links, and analytics!

Logs & Monitoring

View Logs

docker compose logs -f

Application Logs

Backend logging is configured in application.yml:
logging:
  level:
    root: INFO
    adma.sa2_sa3.backend: DEBUG  # Your application logs
Change to TRACE for even more verbose output during debugging.

Testing

Backend Tests

cd backend
./gradlew test

Frontend Tests

cd frontend
bun test          # Run once
bun test --watch  # Watch mode

Common Development Tasks

Add a Database Migration

The app uses Hibernate’s ddl-auto: update in development, which automatically creates/updates tables. For production, you should use Flyway or Liquibase.
Table schema is defined in Java entities at backend/src/main/java/adma/sa2_sa3/backend/domain/

Change Backend Port

Edit docker-compose.yml:
services:
  backend:
    environment:
      SERVER_PORT: 9000
    ports:
      - "9000:9000"  # Host:Container
Don’t forget to update VITE_API_BASE_URL and rebuild the frontend!

Enable CORS for New Origin

Add to CORS_ALLOWED_ORIGINS in docker-compose.yml:
environment:
  CORS_ALLOWED_ORIGINS: http://localhost,http://localhost:3000,http://192.168.1.100:3000

Performance Optimization

Spring Boot JVM Settings

The backend Dockerfile includes optimized JVM flags:
ENTRYPOINT ["java", \
  "-XX:+UseContainerSupport", \
  "-XX:MaxRAMPercentage=75.0", \
  "-Djava.security.egd=file:/dev/./urandom", \
  "-jar", "app.jar"]
  • UseContainerSupport: Detect container memory limits
  • MaxRAMPercentage=75.0: Use 75% of available container memory
  • java.security.egd: Faster random number generation

PostgreSQL Connection Pool

Adjust HikariCP settings in application.yml:
spring:
  datasource:
    hikari:
      maximum-pool-size: 10  # Max connections
      minimum-idle: 2        # Keep 2 connections ready
      connection-timeout: 30000  # 30 seconds

Troubleshooting

PostgreSQL may not be ready when the backend starts. The compose file includes a depends_on health check, but you can also manually check:
# Check postgres health
docker compose ps postgres

# View postgres logs
docker compose logs postgres

# Restart backend after postgres is ready
docker compose restart backend
Check the browser console for errors. Common causes:
  1. VITE_API_BASE_URL mismatch: The frontend was built with wrong API URL
    • Verify in browser DevTools → Network tab
    • Rebuild: docker compose up -d --build frontend
  2. CORS errors: Backend doesn’t allow the frontend origin
    • Add to CORS_ALLOWED_ORIGINS in docker-compose.yml
    • Restart backend: docker compose restart backend
Docker may be using cached layers:
# Force rebuild without cache
docker compose build --no-cache backend
docker compose up -d backend
For frontend, also clear browser cache (Cmd+Shift+R / Ctrl+Shift+R).
Find and stop the conflicting process:
# macOS/Linux
lsof -ti:8080 | xargs kill -9

# Windows
netstat -ano | findstr :8080
taskkill /PID <PID> /F
Or change the port in docker-compose.yml.
The Gradle wrapper needs execute permissions:
cd backend
chmod +x gradlew

Next Steps

Deploy to AWS

Deploy to production with ECS Fargate, RDS, and ALB

Architecture

Learn about the system design and business rules

Build docs developers (and LLMs) love