Vergo
Production-ready multi-tenant SaaS backend in Go.

Vergo is a complete SaaS backend that handles the infrastructure every B2B application needs — authentication, organizations, billing, webhooks, storage, and observability — so you can focus on your product's domain logic.
What can you build with Vergo?
Vergo provides the backend foundation for any multi-tenant B2B SaaS. Fork it, add your domain logic, ship:
- Project management tool (like Linear, Asana) — orgs, members, RBAC, and audit are ready
- Developer platform (like Vercel, Railway) — API keys, webhooks, and billing are built-in
- Internal tooling platform — tenant isolation, role-based access, and file storage out of the box
- Collaboration SaaS (like Notion, Figma) — multi-org, invites, and plan gating from day one
- API-first product — full Swagger docs, API key auth, webhook delivery with retries
- Marketplace / Platform — Stripe subscriptions, usage tracking, feature gating per plan
Every feature is production-grade: hashed tokens, HMAC-signed webhooks, structured logs, distributed tracing, fuzz-tested auth. Not a tutorial project.
Architecture
+-------------------+
| Stripe / S3 |
+--------+----------+
|
Client ──> Gin Router ──> Middleware Chain ──> Handlers ──> Domain Services ──> PostgreSQL
(rate limit) (auth, tenant, (HTTP) (business logic) (sqlc queries)
RBAC, plan gate)
|
Webhook Dispatcher ──> External URLs
(background, retries)
Design decisions
| Decision |
Rationale |
| sqlc over GORM/ent |
Type-safe SQL without magic. Queries are plain .sql files, generated Go code is reviewed in CI |
| Domain services over repository pattern |
Each domain owns its queries and business rules. No shared generic repository |
| Gin as HTTP framework |
Mature, fast, great middleware ecosystem. Easy to swap if needed |
database/sql over pgx pool |
Simpler interface, works with sqlc out of the box, compatible with all Postgres tooling |
| Hash-based tokens (API keys, reset) |
Plaintext never stored. SHA-256 hashed at rest, compared by hash lookup |
| Background dispatcher for webhooks |
Decoupled delivery with exponential backoff. No external queue dependency for MVP |
Features
| Category |
What's included |
| Auth |
Signup, login, refresh token rotation, forgot/reset password, logout, logout-all |
| Multi-tenant |
Organizations, memberships (owner/admin/member), tenant middleware via X-Org-ID |
| RBAC |
Role-based access control per organization with RequireRole middleware |
| API Keys |
Programmatic access with sk_... tokens (SHA-256 hashed, optional expiry) |
| Billing |
Stripe Checkout, subscriptions, webhook handler, plan gating (free/pro/enterprise) |
| Webhooks |
CRUD endpoints, HMAC-SHA256 signing, dispatcher with exponential backoff (5 retries) |
| Storage |
S3-compatible presigned uploads/downloads with file metadata tracking |
| Audit |
Immutable audit log with actor, action, entity, metadata, and filterable queries |
| Data Layer |
PostgreSQL + sqlc type-safe generated queries across 11 migrations |
| Observability |
OpenTelemetry (traces + metrics), structured logging (slog), Jaeger, Prometheus |
| Security |
Fuzz testing (JWT + RBAC), rate limiting, graceful shutdown, SQL lint (sqlfluff) |
| DX |
Dev Container, Swagger UI, Makefile (20+ targets), hot-reload (air), Dependabot |
| CI/CD |
Build, test, vet, golangci-lint, sqlc + swagger freshness checks, Docker multi-arch push to GHCR |
Quickstart
git clone git@github.com:Ulpio/vergo.git
cd vergo
cp .env.example .env
# Start Postgres + infra
docker compose up -d
# Run API
make run
# Register a user
curl -s localhost:8080/v1/auth/signup \
-H 'Content-Type: application/json' \
-d '{"email":"demo@example.com","password":"secret123"}' | jq .
# Create an org
TOKEN=$(curl -s localhost:8080/v1/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"demo@example.com","password":"secret123"}' | jq -r .access_token)
curl -s localhost:8080/v1/orgs \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name":"Acme Corp"}' | jq .
# Create a project (with org context)
ORG_ID=$(curl -s localhost:8080/v1/context \
-H "Authorization: Bearer $TOKEN" | jq -r .org_id)
curl -s localhost:8080/v1/projects \
-H "Authorization: Bearer $TOKEN" \
-H "X-Org-ID: $ORG_ID" \
-H 'Content-Type: application/json' \
-d '{"name":"My First Project","description":"Built with Vergo"}' | jq .
Swagger UI: http://localhost:8080/swagger/index.html
Dev Container
Open in VS Code / Cursor with everything pre-installed:
- Install Dev Containers extension
- Ctrl+Shift+P > "Dev Containers: Reopen in Container"
Includes: Go 1.25, PostgreSQL 16, migrate, golangci-lint, swag, air, sqlfluff.
Docker (Production)
docker build -t vergo .
docker run -p 8080:8080 --env-file .env vergo
Pre-built images on every push to main:
docker pull ghcr.io/ulpio/vergo:main
API Reference
Full interactive docs available at /swagger/index.html when running locally.
Public
| Method |
Path |
Description |
| POST |
/v1/auth/signup |
Register |
| POST |
/v1/auth/login |
Login (returns JWT pair) |
| POST |
/v1/auth/refresh |
Rotate token pair |
| POST |
/v1/auth/logout |
Revoke refresh token |
| POST |
/v1/auth/forgot-password |
Request password reset |
| POST |
/v1/auth/reset-password |
Reset password with token |
| POST |
/v1/billing/webhook |
Stripe webhook (signature verified) |
| GET |
/healthz |
Health check |
Authenticated (Bearer JWT or API Key)
| Method |
Path |
Description |
| GET |
/v1/me |
Current user profile |
| POST |
/v1/auth/logout-all |
Revoke all sessions |
| GET/POST |
/v1/context |
Get/set active org |
| POST |
/v1/orgs |
Create organization |
| GET |
/v1/orgs/:id |
Get organization |
Tenant-scoped (requires org context)
| Method |
Path |
Minimum Role |
Description |
| POST/PATCH/DELETE |
/v1/orgs/:id/members* |
admin |
Manage members |
| DELETE |
/v1/orgs/:id |
owner |
Delete organization |
| CRUD |
/v1/projects* |
member |
Project management |
| GET |
/v1/audit |
admin |
Filterable audit log |
| CRUD |
/v1/api-keys* |
member |
API key management |
| CRUD |
/v1/webhooks/endpoints* |
member |
Webhook configuration |
| POST |
/v1/webhooks/test |
member |
Test webhook delivery |
| POST |
/v1/billing/checkout-session |
member |
Start Stripe checkout |
| GET |
/v1/billing/subscription |
member |
Current subscription |
| GET |
/v1/billing/usage |
member |
Usage vs plan limits |
| CRUD |
/v1/storage/* |
member |
File uploads/downloads |
Project Structure
cmd/api/ # Application entrypoint
internal/
auth/ # JWT, refresh tokens, password reset
domain/
user/ # Registration, login, password management
org/ # Organizations + memberships
project/ # Project CRUD
audit/ # Immutable audit trail
apikey/ # API key lifecycle
webhook/ # Endpoints + background dispatcher
billing/ # Stripe integration + plan limits
file/ # File metadata
userctx/ # Active org context
http/
handlers/ # Request/response handling
middleware/ # Auth, tenant, RBAC, rate limit, plan gate
router/ # Route registration + dependency wiring
repo/ # sqlc generated type-safe queries
pkg/ # Shared infrastructure (config, db, telemetry, logging)
storage/s3/ # S3-compatible storage client
db/
migrations/ # 11 SQL migrations
queries/ # sqlc query definitions
Configuration
All configuration via environment variables. Copy .env.example to .env and adjust:
| Variable |
Default |
Description |
APP_PORT |
8080 |
HTTP port |
APP_ENV |
dev |
dev enables Swagger UI, production sets Gin to release mode |
DB_* |
localhost |
PostgreSQL connection |
JWT_ACCESS_SECRET |
dev-access |
Access token signing key |
JWT_REFRESH_SECRET |
dev-refresh |
Refresh token signing key |
S3_BUCKET / S3_ENDPOINT |
- |
S3-compatible storage (MinIO locally) |
STRIPE_SECRET_KEY |
- |
Stripe API key for billing |
STRIPE_WEBHOOK_SECRET |
- |
Stripe webhook signature verification |
OTEL_EXPORTER_OTLP_ENDPOINT |
- |
OTLP gRPC endpoint for traces/metrics |
METRICS_PORT |
0 |
Prometheus scrape port (0 = disabled) |
RATE_LIMIT_RPS / RATE_LIMIT_BURST |
20 / 40 |
Rate limiter config |
Development
make help # Show all targets
make dev # Hot-reload with air
make test # Unit tests
make test-fuzz # Fuzz tests (JWT + RBAC)
make lint # golangci-lint
make check # vet + lint + test (CI equivalent)
make swagger # Regenerate Swagger docs
make sqlc # Regenerate sqlc queries
make docker-up # Start Postgres + infra
make docker-reset # Reset database (drop volumes)
make migrate-up # Run pending migrations
make migrate-create NAME=xyz # Create new migration
Tech Stack
| Layer |
Technology |
| Language |
Go 1.25 |
| HTTP |
Gin |
| Database |
PostgreSQL 16 |
| Query generation |
sqlc |
| Auth |
JWT (golang-jwt/v5) + bcrypt |
| Billing |
Stripe Go SDK v82 |
| Storage |
AWS SDK v2 (S3-compatible) |
| Observability |
OpenTelemetry, slog, Jaeger, Prometheus |
| CI/CD |
GitHub Actions, GHCR, Docker multi-arch |
| Code quality |
golangci-lint, sqlfluff, CodeQL, Dependabot |
License
MIT