Skip to main content

Backend

Step-by-step guide to run the Upmatches API on your machine.

Prerequisites

Install all of the following before continuing.

ToolVersionPurposeInstall link
Docker DesktopLatestRuns the API, PostgreSQL, and Redishttps://www.docker.com/products/docker-desktop/
GitLatestClone the repositoryhttps://git-scm.com/downloads
note

You do not need Java or Maven installed. Everything runs inside Docker containers.

Verify installations

Open a terminal and run each command. If any command fails, revisit the install link above.

docker --version        # e.g. Docker version 28.x.x
docker compose version # e.g. Docker Compose version v2.x.x
git --version # e.g. git version 2.x.x
Windows users

Use PowerShell or Windows Terminal. The commands below work on macOS, Linux, and Windows.


1. Clone the repository

git clone <repository-url>
cd upmatches

2. Configure environment variables

Copy the example environment file and fill in the required values:

cp .env.example .env

Open .env in your editor and update the following:

# Pull the pre-built image from Docker Hub
DOCKER_IMAGE=chownrmrf/upmatches-api
IMAGE_TAG=latest

# Set your frontend origin so the API allows cross-origin requests
CORS_ALLOWED_ORIGINS=http://localhost:3002

# Infisical credentials (ask your team lead)
# Required for login flows (Singpass and Auth0) to work
INFISICAL_CLIENT_ID=<your-client-id>
INFISICAL_CLIENT_SECRET=<your-client-secret>
INFISICAL_PROJECT_ID=<your-project-id>

The remaining values in .env have sensible defaults and can be left as-is.

Local Singpass testing

For local development without calling the real Singpass staging environment, run MockPass and copy the overrides from .env.mockpass. The API repo's MOCKPASS.md file lists the test NRICs and UUIDs you can use.

Reference: environment variables

GroupKey variables
ServerSERVER_PORT, SPRING_PROFILES_ACTIVE, SWAGGER_ENABLED
DatabasePOSTGRES_DB_HOST, POSTGRES_DB_PORT, POSTGRES_DB_NAME, POSTGRES_DB_USER, POSTGRES_DB_PASS
DB poolDB_POOL_SIZE, DB_POOL_MIN_IDLE, DB_IDLE_TIMEOUT, DB_MAX_LIFETIME, DB_CONNECTION_TIMEOUT, DB_LEAK_DETECTION
Flyway / cacheFLYWAY_ENABLED, CACHE_TYPE
RedisREDIS_HOST, REDIS_PORT, REDIS_USERNAME, REDIS_PASSWORD
CORSCORS_ALLOWED_ORIGINS (semicolon-delimited)
JWTJWT_ISSUER, JWT_AUDIENCE, JWT_ACCESS_TOKEN_EXPIRY_MINUTES, JWT_REFRESH_TOKEN_EXPIRY_DAYS, FRONTEND_CALLBACK_URL, MOBILE_CALLBACK_SCHEME
SingpassSINGPASS_CLIENT_ID, SINGPASS_REDIRECT_URI, SINGPASS_ISSUER, SINGPASS_AUTHORIZATION_ENDPOINT, SINGPASS_PAR_ENDPOINT, SINGPASS_TOKEN_ENDPOINT, SINGPASS_JWKS_ENDPOINT, SINGPASS_USERINFO_ENDPOINT, SINGPASS_SCOPES, SINGPASS_AUTH_CONTEXT_TYPE, SINGPASS_AUTH_CONTEXT_MESSAGE, SINGPASS_PRIVATE_SIGNING_KEY_PATH, SINGPASS_PRIVATE_ENCRYPTION_KEY_PATH
Auth0AUTH0_DOMAIN, AUTH0_CLIENT_ID, AUTH0_CLIENT_SECRET, AUTH0_REDIRECT_URI, AUTH0_SCOPES
InfisicalINFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_PROJECT_ID, INFISICAL_ENVIRONMENT
Share linksSHARE_LINK_BASE_URL
ObservabilityTRACING_ENABLED, TRACING_OTLP_ENDPOINT, TRACING_SAMPLING_PROBABILITY

Default infrastructure credentials (already configured)

ServiceHostPortUsernamePasswordDatabase
PostgreSQLlocalhost5432postgrespostgresupmatches_dev
Redislocalhost6379(none)(none)(default)

3. Start the services

Pull the latest API image and start all services (API, PostgreSQL, Redis):

docker compose pull && docker compose up -d

Wait a few seconds, then verify all three containers are running:

docker compose ps

You should see upmatches-api, upmatches-db, and upmatches-redis with status healthy (or running for the API).

On first startup the API will:

  1. Connect to PostgreSQL and automatically apply database migrations (create tables, indexes, etc.).
  2. Connect to Redis.
  3. Fetch secrets from Infisical (if configured).

4. Verify it works

curl http://localhost:8080/actuator/health

Expected response:

{
"status": "UP"
}

Quick reference

WhatCommand
Pull latest API imagedocker compose pull
Start all servicesdocker compose up -d
Stop all servicesdocker compose down
Stop and delete all datadocker compose down -v
View logs (all services)docker compose logs -f
View API logs onlydocker compose logs -f api
Check service statusdocker compose ps
Check API healthcurl http://localhost:8080/actuator/health

API overview for frontend developers

Base URL

http://localhost:8080

Authentication

The API uses cookie-based JWT authentication (BFF pattern). After a successful login flow, the API sets access_token and refresh_token cookies. Your frontend should include credentials in requests:

fetch("http://localhost:8080/api/v1/me", {
credentials: "include", // sends cookies with the request
});

CORS

The API is configured to allow requests from http://localhost:3002 by default. If your frontend runs on a different port, update the CORS_ALLOWED_ORIGINS value in your .env file and restart the API:

docker compose restart api

Multiple origins can be separated with a semicolon (;).

Available endpoints

MethodRouteAuth requiredDescription
GET/actuator/healthNoSpring health check
GET/api/v1/public/healthNoApplication health check
GET/.well-known/jwks.jsonNoPublic signing keys (JWKS)
GET/api/v1/auth/login/singpassNoStart Singpass login flow
GET/api/v1/auth/login/auth0NoStart Auth0 login flow
GET/callback/singpassNoSingpass OAuth callback
GET/callback/auth0NoAuth0 OAuth callback
POST/api/v1/auth/refreshNoRefresh access token
POST/api/v1/auth/logoutNoLogout (clears cookies)
POST/api/v1/auth/dev/tokensNo*Mint test JWT (dev profiles only)
GET/api/v1/meYesGet current user profile
POST/api/v1/meYesComplete user profile (onboarding)
PUT/api/v1/meYesUpdate user profile
DELETE/api/v1/meYesDelete user account
GET/api/v1/me/games/joinedYesList games the caller has joined
GET/api/v1/me/games/hostedYesList games the caller has hosted
GET/api/v1/activitiesNoList activities (rate-limited)
GET/api/v1/activities/{id}NoGet activity by ID (rate-limited)
POST/api/v1/activitiesYesCreate activity
PUT/api/v1/activities/{id}YesUpdate activity
DELETE/api/v1/activities/{id}YesDelete activity
GET/api/v1/skill-levels?activityId=...YesGet skill levels for an activity
GET/api/v1/venuesNoList venues (rate-limited)
POST/api/v1/venues/importsYes (admin)Bulk import venues (multipart)
GET/api/v1/gamesNoList games (cursor, rate-limited)
GET/api/v1/games/filter-optionsNoFilter options for a date range
GET/api/v1/games/{id}NoGet a game (rate-limited)
POST/api/v1/gamesYesCreate a game
PUT/api/v1/games/{id}YesUpdate a game (organiser only)
DELETE/api/v1/games/{id}YesSoft-delete a game (organiser)
POST/api/v1/games/{id}/participantsYesJoin a game
DELETE/api/v1/games/{id}/participants/meYesLeave a game
GET/api/v1/games/{id}/participantsYesList participants
DELETE/api/v1/games/{gameId}/participants/{participantId}YesExpel participant (organiser)
DELETE/api/v1/admin/games/{id}Yes (admin)Hard-delete a game
GET/api/v1/game-bookmarksYesList the caller's bookmarks
POST/api/v1/game-bookmarksYesBookmark a game
DELETE/api/v1/game-bookmarks/{gameId}YesRemove a bookmark
POST/api/v1/share-linksYesCreate a share link
GET/api/v1/share-links/{code}NoResolve a share link (rate-limited)
GET/games/{code}NoWeb fallback HTML page
GET/api/v1/notificationsYesList the caller's notifications
GET/api/v1/notifications/unread-countYesUnread notification count
PUT/api/v1/notifications/{id}/readYesMark a notification as read
PUT/api/v1/notifications/read-allYesMark all notifications as read
DELETE/api/v1/notifications/{id}YesDelete a notification
POST/api/v1/device-tokensYesRegister a push device token
DELETE/api/v1/device-tokens?deviceToken=...YesUnregister a push device token

* /api/v1/auth/dev/tokens is gated by DEV_MINT_ENDPOINT_ENABLED=true, requires a non-prod profile, and only accepts requests originating from localhost.

Auth flow (how login works)

  1. Frontend redirects the user to GET /api/v1/auth/login/singpass (or /auth0).
  2. The API redirects the user to the identity provider (Singpass or Auth0).
  3. After login, the identity provider redirects back to the API callback URL.
  4. The API processes the callback, creates or updates the user, and redirects to http://localhost:3002/auth/callback with auth cookies set.
  5. Frontend can now call authenticated endpoints (e.g., GET /api/v1/me).
New users

If the user has not completed onboarding, the callback redirect includes ?isNewUser=true. Use this to route new users to an onboarding flow.


Troubleshooting

Port already in use

If port 8080, 5432, or 6379 is already taken by another process:

# Check what's using a port (example: 8080)
# macOS/Linux
lsof -i :8080
# Windows (PowerShell)
netstat -ano | findstr :8080

Stop the conflicting process, or change the port in your .env file (e.g., API_PORT=9090) and restart.

Docker containers not starting

# View container logs for errors
docker compose logs

API fails to connect to database

Make sure the database and Redis containers are healthy before the API starts. Docker Compose handles this automatically via health checks, but if you see connection errors:

docker compose ps

Both upmatches-db and upmatches-redis should show status healthy. If they show starting, wait a few seconds and check again.

Login flows not working

If Singpass or Auth0 login redirects fail, ensure the Infisical credentials are set correctly in your .env file. Without them, the API cannot fetch the required secrets (private keys, client secrets).

Reset everything (nuclear option)

Stop containers, delete all data volumes, and start fresh:

docker compose down -v
docker compose pull && docker compose up -d

The API will re-create the database from scratch on next startup.