base - Go Backend Template
A production-ready Go backend template with authentication, authorization, OAuth2, WebAuthn (passkeys), TOTP, email verification, and background job processing.
Features
- Authentication: Password-based login with Argon2id hashing, email verification, password reset, and account recovery
- Two-Factor Authentication: TOTP (Time-based One-Time Password) support
- Passkeys (WebAuthn): Passwordless authentication with discoverable credentials
- OAuth2/OIDC: Pluggable OAuth providers with PKCE flow
- Role-Based Access Control: Admin and user roles with pluggable permission middleware
- Session Management: PostgreSQL-backed sessions with configurable lifetimes
- Email Outbox: Asynchronous email delivery with retry and failure tracking
- Rate Limiting: Login rate limiting with PostgreSQL-backed token buckets
- Background Jobs: River-based job processing (email delivery, cleanup, inactivity checks)
- Database Migrations: Goose-based schema migrations with embedded SQL files
- Configuration: YAML config with environment variable overrides (via Koanf)
- Structured Logging: JSON logging via
log/slog
- Test Infrastructure: Testcontainers-based PostgreSQL for integration tests
Quick Start
Prerequisites
- Go 1.27.1+
- Docker (for PostgreSQL, Inbucket, sqlc codegen, and tests)
- Task (optional, for convenience commands)
Development Setup
# Start PostgreSQL and Inbucket (mail catcher)
docker compose up -d
# Run database migrations and start the server
go run ./cmd/app
# Or use Task
task run
The API is available at http://localhost:8080.
Running Tests
# Run all tests (requires Docker for Testcontainers)
task test
# Generate coverage report
task coverage
# Run linter
task lint
Project Structure
.
|-- cmd/
| |-- app/ # Application entry point
| `-- coveragefilter/ # Coverage report filter utility
|-- config/
| `-- config.yaml # Default configuration
|-- db/
| |-- migrations/ # Goose SQL migrations (embedded)
| `-- queries/ # sqlc query definitions
|-- internal/
| |-- app/ # Application bootstrap and lifecycle
| |-- auth/ # Authentication and authorization logic
| |-- cache/ # Generic in-memory cache with TTL
| |-- config/ # Configuration loading and validation
| |-- database/ # Database connection and migration runner
| |-- httpapi/ # HTTP API layer
| | |-- handlers/ # Request handlers
| | |-- jsonio/ # JSON request/response helpers
| | `-- middleware/ # HTTP middleware
| |-- mailer/ # SMTP mailer
| |-- river/ # River background job client and workers
| | `-- jobs/ # Job implementations
| |-- store/
| | |-- dbtype/ # Custom database types (JSONB)
| | `-- sqlc/ # Generated sqlc code
| |-- testutil/ # Test helpers (Testcontainers PostgreSQL)
| `-- validation/ # Request validation framework
|-- sqlc/ # sqlc Docker build files
|-- docker-compose.yml # Development services
|-- Taskfile.yml # Task runner commands
`-- GOING_PROD.md # Production deployment checklist
Configuration
Configuration is loaded from config/config.yaml and overridden by environment variables prefixed with BASE_. Environment variable names join config path segments with _ while preserving snake_case field names, so BASE_DATABASE_MAX_OPEN_CONNS overrides database.max_open_conns and BASE_OAUTH_PROVIDERS_GOOGLE_CLIENT_ID overrides oauth.providers.google.client_id. String slices accept comma-separated values such as BASE_SECURITY_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com.
| Config Key |
Env Var |
Description |
app.env |
BASE_APP_ENV |
Environment: development, test, or production |
app.log_level |
BASE_APP_LOG_LEVEL |
Log level: debug, info, warn, error |
http.address |
BASE_HTTP_ADDRESS |
Listen address (e.g., :8080, 127.0.0.1:8080) |
http.trusted_proxies |
BASE_HTTP_TRUSTED_PROXIES |
Trusted proxy IPs/CIDRs for X-Forwarded-For and X-Real-IP handling (comma-separated) |
database.url |
BASE_DATABASE_URL |
PostgreSQL connection string |
database.max_open_conns |
BASE_DATABASE_MAX_OPEN_CONNS |
Maximum open database connections |
session.secure |
BASE_SESSION_SECURE |
Set to true in production (HTTPS) |
security.allowed_origins |
BASE_SECURITY_ALLOWED_ORIGINS |
Allowed CORS origins for browser clients (comma-separated) |
security.encryption_key |
BASE_SECURITY_ENCRYPTION_KEY |
32+ char secret for DB encryption |
river.enabled |
BASE_RIVER_ENABLED |
Enable background job processing |
mailer.enabled |
BASE_MAILER_ENABLED |
Enable email sending |
See config/config.yaml for all options. See GOING_PROD.md for production configuration guidance.
API Endpoints
Public
| Method |
Path |
Description |
POST |
/api/v1/auth/register |
Register a new user |
POST |
/api/v1/auth/login |
Login with email/password (+ optional TOTP) |
GET |
/api/v1/auth/verify-email |
Verify email with token |
POST |
/api/v1/auth/verify-email/request |
Request a replacement verification email |
POST |
/api/v1/auth/password-reset/request |
Request password reset email |
POST |
/api/v1/auth/password-reset/confirm |
Confirm password reset |
POST |
/api/v1/auth/account-recovery/request |
Request account recovery |
POST |
/api/v1/auth/account-recovery/confirm |
Confirm account recovery |
GET |
/api/v1/auth/oauth/{provider}/start |
Start OAuth flow |
GET |
/api/v1/auth/oauth/{provider}/callback |
OAuth callback |
POST |
/api/v1/auth/passkeys/login/start |
Begin passkey login |
POST |
/api/v1/auth/passkeys/login/finish |
Complete passkey login |
Authenticated
| Method |
Path |
Description |
POST |
/api/v1/auth/logout |
Destroy session |
GET |
/api/v1/auth/me |
Get current user |
POST |
/api/v1/auth/passkeys/register/start |
Begin passkey registration |
POST |
/api/v1/auth/passkeys/register/finish |
Complete passkey registration |
GET |
/api/v1/auth/passkeys |
List registered passkeys |
DELETE |
/api/v1/auth/passkeys/{passkeyID} |
Delete a registered passkey |
POST |
/api/v1/auth/totp/setup |
Generate TOTP secret |
POST |
/api/v1/auth/totp/enable |
Enable TOTP |
POST |
/api/v1/auth/totp/disable |
Disable TOTP |
Admin
| Method |
Path |
Description |
GET |
/api/v1/admin/access |
Admin-only endpoint |
Health
| Method |
Path |
Description |
GET |
/health |
Liveness check |
GET |
/readiness |
Readiness check (includes DB ping) |
Libraries
Core Dependencies
Authentication & Security
Background Jobs & Messaging
Database & Migrations
Rate Limiting
Testing
Development Tools
| Tool |
Purpose |
| sqlc (v1.31.1) |
Type-safe SQL code generation (run via Docker) |
| golangci-lint (v2.13.1) |
Go linter (run via Docker) |
| Docker Compose |
Local PostgreSQL + Inbucket for development |
| Task |
Build/test automation |
Code Generation
SQL queries are defined in db/queries/ and Go code is generated with sqlc:
task db:build-sqlc # Build the sqlc Docker image
task db:run-sqlc # Generate Go code from SQL queries
Generated files in internal/store/sqlc/ are committed so a fresh checkout builds without Docker. Regenerate and commit them whenever the schema or queries change.
Architecture Notes
- Dual DB handles: The app maintains both a
database/sql handle (*sql.DB) and a pgxpool handle (*pgxpool.Pool). The sql.DB is used for sqlc-generated queries and migrations; pgxpool is used by River, SCS sessions, and the rate limiter.
- River outbox safety: The periodic email outbox job is inserted uniquely so only one outbox sweep can be queued or running at a time, which avoids overlapping email sends.
- Proxy trust boundary: Forwarded client IP headers are only honored when the immediate peer matches
http.trusted_proxies. Leave that list empty unless the app is behind a proxy you control.
- Encryption at rest: TOTP secrets and OAuth tokens stored in the database are encrypted with AES-256-GCM using the configured
security.encryption_key.
- Credential masking: Login failures intentionally return a generic "Invalid email or password" message regardless of whether the email exists, the account is locked, disabled, or unverified.
- Session revocation: Password reset and account recovery increment the user's authentication version, invalidating all previously issued sessions.
- OAuth email trust: OAuth login and account linking require the provider to assert that the returned email address is verified.
- Test isolation: Each test gets its own PostgreSQL database created from a shared Testcontainers container, ensuring full isolation without per-test container overhead.
License
MIT