Skip to main content

Getting Started with the API

How to configure and run the Go API server locally.

Prerequisites

  • Go 1.25 or later
  • Infrastructure running (docker compose up -d)
  • Monorepo dependencies installed (pnpm install)

Configuration

The API loads configuration from environment variables. Each component defines its own config struct and parses only the variables it needs at startup.

Setting up your .env

Copy the example file:

cp apps/api/.env.example apps/api/.env

Edit apps/api/.env with your local values. The .env file is gitignored and loaded automatically on startup — real environment variables always take precedence.

Environment variables

Common

These are shared across all components via config.Base:

VariableDefaultDescription
APP_ENVdevelopmentEnvironment name (development, production)
LOG_LEVELinfoLog level (debug, info, warn, error)

Server

VariableDefaultRequiredDescription
SERVER_PORT4000NoHTTP listen port
SERVER_READ_TIMEOUT5sNoMax duration for reading request
SERVER_WRITE_TIMEOUT10sNoMax duration for writing response
SERVER_IDLE_TIMEOUT120sNoMax duration for idle connections
SERVER_SHUTDOWN_TIMEOUT30sNoGraceful shutdown deadline
SERVER_CORS_ORIGINSYesComma-separated allowed CORS origins

Authentication

JWT validation against the Identity service:

VariableDefaultRequiredDescription
AUTH_JWKS_URLYesIdentity service JWKS endpoint (e.g. https://main-identity.chompardo.dev/api/auth/jwks)
AUTH_ISSUERYesExpected token issuer (Identity service base URL)
AUTH_AUDIENCEYesExpected token audience (API service URL)

Database

VariableDefaultRequiredDescription
DB_HOSTlocalhostNoPostgreSQL host
DB_PORT5432NoPostgreSQL port
DB_NAMEYesDatabase name
DB_USERYesDatabase role
DB_PASSWORDYesDatabase password
DB_SSLMODEdisableNoSSL mode (disable, require, verify-full)
DB_MAX_CONNS10NoMax pool connections
DB_MIN_CONNS2NoMin idle connections
DB_MAX_CONN_LIFETIME1hNoMax connection lifetime
DB_MAX_CONN_IDLE_TIME30mNoMax idle time before connection is closed
DB_AUTO_MIGRATEfalseNoRun migrations on startup (use true for local dev)

Authorization (OpenFGA)

VariableDefaultRequiredDescription
FGA_API_URLhttp://localhost:5080NoOpenFGA HTTP API URL
FGA_STORE_IDNoOpenFGA store ID (ULID). Noop client if unset

See the Authorization Guide for setup and usage details.

How it works

Configuration uses caarlos0/env to parse environment variables into Go structs with struct tags:

type ServerConfig struct {
config.Base
Port int `env:"SERVER_PORT" envDefault:"4000"`
CORSOrigins []string `env:"SERVER_CORS_ORIGINS,required"`
}

At startup, config.LoadDotEnv() loads the .env file (if present), then each component calls config.MustParse[T]() to parse its own config. Missing required variables cause a panic with a clear error message.

Adding config to a module

When a module needs configuration, add a config.go in the module root:

// internal/modules/recipe/config.go
package recipe

import "github.com/marcuslindfeldt/chompardo/apps/api/internal/config"

type Config struct {
config.Base
MaxResults int `env:"RECIPE_MAX_RESULTS" envDefault:"50"`
}

Parse it in the module's Register() or initialization function:

func Register(r chi.Router) {
cfg := config.MustParse[Config]()
// use cfg...
}

Database Migrations

Before running the API for the first time (or after pulling new migration files), run migrations:

cd apps/api && make migrate

Alternatively, set DB_AUTO_MIGRATE=true in your .env to run migrations automatically on startup. This is convenient for local development but should be disabled in production — use the standalone cmd/migrate binary instead to avoid race conditions when multiple instances start simultaneously.

See the Database Guide for more details on writing migrations and queries.

Running the API

pnpm dev --filter=api

Or directly with Go:

cd apps/api
go run ./cmd/api

The server starts on http://127.0.0.1:4000 (or https://main-api.chompardo.dev via Traefik).

Verify it's running

curl http://localhost:4000/healthz
# ok

curl http://localhost:4000/openapi.yaml
# full OpenAPI spec

Code generation

The API uses an OpenAPI-first workflow. After changing any openapi.yaml spec:

pnpm generate --filter=api

This bundles all module specs and regenerates Go server code. See Add an Endpoint and Add a Module for details.