# Gofasta Documentation — Full Text > Complete Gofasta documentation as a single markdown file. Intended for AI agents that prefer one-shot context loading over per-page fetches. The individual pages are served at https://gofasta.dev/docs. Generated: 2026-05-10 --- ## /docs — Overview > The official documentation for the Gofasta Go backend toolkit and CLI. # Gofasta Documentation Gofasta is a Go backend toolkit — a CLI for scaffolding projects and generating code, backed by a library of independent, production-ready packages. Use what you need, ignore the rest. This documentation covers everything you need to build, deploy, and maintain Go backend services with Gofasta. ## Getting Started New to Gofasta? Start here: - **[Introduction](/docs/getting-started/introduction)** — What Gofasta is and why it exists - **[Installation](/docs/getting-started/installation)** — Install the CLI tool (3 options) - **[Quick Start](/docs/getting-started/quick-start)** — Create a project and generate your first resource in 5 minutes - **[Project Structure](/docs/getting-started/project-structure)** — Understand the generated directory layout ## Guides In-depth walkthroughs for common tasks: - **[REST API](/docs/guides/rest-api)** — Build and customize REST endpoints - **[GraphQL](/docs/guides/graphql)** — Add GraphQL schemas and resolvers - **[Database & Migrations](/docs/guides/database-and-migrations)** — Manage your database schema - **[Authentication](/docs/guides/authentication)** — JWT tokens and RBAC with Casbin - **[Code Generation](/docs/guides/code-generation)** — Use the CLI to generate models, services, controllers, and more - **[Background Jobs](/docs/guides/background-jobs)** — Cron scheduling and async task queues - **[Deployment](/docs/guides/deployment)** — Deploy to a VPS via SSH (Docker or binary), with systemd, nginx, and GitHub Actions ## CLI Reference Every command the Gofasta CLI provides: - **[gofasta new](/docs/cli-reference/new)** — Create a new project - **[gofasta generate](/docs/cli-reference/generate/scaffold)** — Generate code (scaffold, model, service, etc.) - **[gofasta migrate](/docs/cli-reference/migrate)** — Run database migrations - **[gofasta deploy](/docs/cli-reference/deploy)** — Deploy to a VPS via SSH - **[gofasta dev](/docs/cli-reference/dev)** — Start the development server - **[gofasta verify](/docs/cli-reference/verify)** — Run the full preflight gauntlet - **[gofasta status](/docs/cli-reference/status)** — Project drift report - **[gofasta inspect](/docs/cli-reference/inspect)** — Show a resource's full composition - **[gofasta config](/docs/cli-reference/config)** — Emit the JSON Schema for `config.yaml` - **[gofasta do](/docs/cli-reference/do)** — Run a named development workflow - **[gofasta ai](/docs/cli-reference/ai)** — Install AI coding agent configuration ## Package Library Every package is independent — import only what you need. Reference for `github.com/gofastadev/gofasta/pkg/*`: - **[Config](/docs/api-reference/config)** — Configuration loading and database setup - **[Auth](/docs/api-reference/auth)** — JWT and RBAC - **[Middleware](/docs/api-reference/middleware)** — HTTP middleware collection - **[Cache](/docs/api-reference/cache)** — In-memory and Redis caching - [View all packages →](/docs/api-reference/config) --- ## /docs/getting-started/introduction — Introduction > What is Gofasta and why use it. # Introduction Gofasta is a Go backend toolkit that provides production-ready, independent packages for building backend services. It handles the infrastructure plumbing — authentication, caching, database setup, email, logging, middleware, and more — so you can focus on writing business logic. Every package is independent: use what you need, ignore the rest. Gofasta is split into two parts: - **[gofastadev/gofasta](https://github.com/gofastadev/gofasta)** — A Go library of independent packages under `pkg/`. Each package (auth, cache, mailer, etc.) can be used on its own — there is no all-or-nothing dependency. - **[gofastadev/cli](https://github.com/gofastadev/cli)** — A globally installed CLI tool that scaffolds new projects, generates code, and deploys to remote servers. It does not import the library — it only manipulates files on disk. ## What You Get When you run `gofasta new myapp`, the CLI creates a complete, ready-to-run project with: - **REST API** with controllers, routes, and middleware (default) - **GraphQL** with schema files and auto-generated resolvers (opt-in via `--graphql` flag) - **Database** with migrations, seeders, and multi-DB support (Postgres, MySQL, SQLite, SQL Server, ClickHouse) - **Authentication** with JWT tokens and role-based access control (Casbin) - **Dependency injection** with Google Wire (compile-time, no reflection) - **Background jobs** — cron scheduling and async task queues - **Email** — SMTP, SendGrid, and Brevo with HTML templates - **Observability** — Prometheus metrics and OpenTelemetry tracing - **Docker** — Production Dockerfile and Docker Compose configuration - **Deployment** — One-command VPS deploy via `gofasta deploy` (Docker or binary over SSH), with generated systemd units and nginx reverse-proxy config - **CI/CD** — GitHub Actions workflow templates for testing, releasing, and deploying - **A one-command dev loop** — `gofasta dev` boots db + cache + queue dashboard, runs migrations, and starts Air for hot reload. Press `r` mid-session to restart the whole stack from scratch, `q` to quit, `h` for help. Supports `--all-in-docker` for full-Docker mode, `--dashboard` for a live request inspector (trace waterfalls, EXPLAIN-on-click, panic history, HAR export), and `--json` for agent-readable structured events. - **Agent-native tooling** — every command honors `--json`, every error carries a stable code + remediation hint + docs link, and `gofasta ai claude|cursor|codex|aider|windsurf` installs per-agent configuration in one command. A starter `User` resource is included so the project compiles and runs out of the box. ## Architecture Your project has a one-way dependency on the gofasta library: ``` Your Project (myapp) └── imports → github.com/gofastadev/gofasta/pkg/* ``` The library never imports your code. This means: - **No vendor lock-in** — you can replace any package with your own implementation - **No code generation at runtime** — Wire (and gqlgen, if GraphQL is enabled) run at build time - **No reflection magic** — all wiring is explicit Go code ## Who Is This For Gofasta is designed for Go developers who want to ship production backends quickly without assembling dozens of libraries manually. It is especially useful if you: - Want a full project structure from day one (not just a router) - Need REST, or both REST and GraphQL (via `--graphql`), in the same project - Want code generation that stays out of your way - Value compile-time safety over runtime magic ## Next Steps - [Install the CLI](/docs/getting-started/installation) - [Create your first project](/docs/getting-started/quick-start) - [Understand the project structure](/docs/getting-started/project-structure) ## Related --- ## /docs/getting-started/installation — Installation > Install the Gofasta CLI tool. # Installation The Gofasta CLI is a standalone Go binary. There are two ways to install it. ## Option A: `go install` (recommended for Go developers) **Requires Go 1.25.0 or later.** ```bash go install github.com/gofastadev/cli/cmd/gofasta@latest ``` This compiles the CLI from source using your local Go toolchain and drops the `gofasta` binary into `$GOBIN` — or `$GOPATH/bin` when `GOBIN` is unset, which usually resolves to `~/go/bin`. If `gofasta` runs after this, you're done. If you see `command not found: gofasta`, skip to [Troubleshooting](#troubleshooting) below — it's a one-time PATH fix. Because gofasta is a tool for Go developers, this is the simplest path: you already have everything you need. ## Option B: Pre-built binary (no Go toolchain needed) If you don't have Go installed or just want a single self-contained binary: ```bash curl -fsSL https://raw.githubusercontent.com/gofastadev/cli/main/dist/install.sh | sh ``` This downloads the latest pre-built binary for your platform from [GitHub Releases](https://github.com/gofastadev/cli/releases) and installs it to `/usr/local/bin/gofasta`. Works on macOS and Linux for both `amd64` and `arm64`. The script detects your shell and prints exact `export PATH=…` instructions if the install directory isn't already on your `$PATH`, so most users are done after running it. You can also grab a binary manually from the [releases page](https://github.com/gofastadev/cli/releases) and put it on your `PATH` yourself. ## Verify Installation ```bash gofasta --help ``` You should see a list of available commands: ``` The Gofasta CLI creates new projects, generates code, and runs common development tasks. Usage: gofasta [command] Available Commands: new Create a new Gofasta project dev Run the development server with hot reload generate Generate code (model, service, controller, etc.) migrate Run database migrations seed Run database seeders init Initialize a cloned project serve Start the HTTP server wire Regenerate Wire dependency injection swagger Generate Swagger/OpenAPI docs help Help about any command ``` ## Troubleshooting ### `command not found: gofasta` after `go install` This is the most common first-time issue with any `go install`-distributed CLI, not specific to gofasta. Go installed the binary into `$GOPATH/bin` (typically `~/go/bin`), but that directory isn't on your shell's `$PATH`. **1. Confirm the binary is there:** ```bash ls -l "$(go env GOPATH)/bin/gofasta" ``` If you see the file, the install succeeded — you just need to make its directory reachable by your shell. **2. Add the directory to your `$PATH` once:** ```bash # zsh (default on macOS) echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.zshrc source ~/.zshrc # bash echo 'export PATH="$PATH:$(go env GOPATH)/bin"' >> ~/.bashrc source ~/.bashrc # fish fish_add_path (go env GOPATH)/bin ``` **3. Verify:** ```bash gofasta --version ``` This is a **one-time setup per machine** — every future `go install` (for any Go tool) lands in the same directory, so once `~/go/bin` is on your `$PATH` you never need to do this again. ### `gofasta --version` reports `dev` (pre-v0.1.3 only) CLI versions before v0.1.3 shipped with `Version = "dev"` as a hardcoded default, only overridden for pre-built binaries at release time. `go install` never applies release-time flags, so Option A installs always reported `dev` regardless of the actual tag you installed. This was fixed in **v0.1.3**: the CLI now reads the installed module version via Go's built-in `runtime/debug.ReadBuildInfo()` at startup, so `go install` users now see the correct tag automatically. If you installed before v0.1.3, re-run: ```bash go install github.com/gofastadev/cli/cmd/gofasta@latest ``` ### `go install` picked up the wrong Go version If you see something like `go: github.com/gofastadev/cli@v0.1.2 requires go >= 1.25.0; switching to go1.25.1`, Go's auto-toolchain feature downloaded the required version for you automatically. This is expected — your install will succeed. If it fails, upgrade your local Go to 1.25.0 or later manually via [go.dev/dl](https://go.dev/dl/). ### Which option should I use? | Your situation | Use | |---|---| | You have Go installed and use it day-to-day | **Option A** — no extra downloads, no version drift | | You don't have Go installed | **Option B** — self-contained binary, no toolchain | | Corporate machine where `go install` is blocked | **Option B** — downloads a pre-built binary instead | | You want reproducible pinned versions in CI | Either — both support version pinning (`@v0.1.3` for Option A, `GOFASTA_VERSION=v0.1.3 sh install.sh` is on the roadmap) | Both paths end up as a `gofasta` binary on disk, so pick whichever fits your environment. You can switch between them at any time. ## Prerequisites for Generated Projects The CLI itself only needs Go. But the projects it generates use these tools (installed automatically during `gofasta new`): | Tool | Purpose | Installed via | |------|---------|---------------| | [Wire](https://github.com/google/wire) | Compile-time dependency injection | `go get` + `go mod edit -tool` | | [gqlgen](https://github.com/99designs/gqlgen) | GraphQL code generation | `go get` + `go mod edit -tool` (only when using `--graphql`) | | [Air](https://github.com/air-verse/air) | Hot reload during development | `go get` + `go mod edit -tool` | | [swag](https://github.com/swaggo/swag) | Swagger/OpenAPI doc generation | `go get` + `go mod edit -tool` | | [Docker](https://www.docker.com/) | Run the app + PostgreSQL locally | Manual install | gqlgen is only installed as a tool dependency when you create a project with `gofasta new myapp --graphql`. REST-only projects do not require or include gqlgen. Docker is optional — you can run the app directly on your machine if you have a local PostgreSQL instance. ## Next Steps - [Create your first project](/docs/getting-started/quick-start) ## Related --- ## /docs/getting-started/quick-start — Quick Start > Scaffold a project, start the full dev loop with one command, and ship your first resource — in under five minutes. # Quick Start Five minutes, four commands, a working backend with REST + database + hot reload + observability + interactive controls. ## Step 1: Create a Project ```bash gofasta new myapp ``` Or with a full Go module path: ```bash gofasta new github.com/myorg/myapp ``` This scaffolds a REST-only project. Add `--graphql` if you want gqlgen + a `/graphql` endpoint + playground generated alongside REST: ```bash gofasta new myapp --graphql ``` The output narrates every step — Wire DI generation, `go mod tidy`, git init, the works. By the time it finishes you have a project that compiles, runs, has a starter `User` resource, and can be deployed as-is. ## Step 2: Start the Dev Loop ```bash cd myapp gofasta dev ``` That single command brings up everything: the database (Postgres in Docker), the cache (Redis), the queue dashboard (asynqmon), runs pending migrations, then launches Air for hot-reload on the host. Everything tears down cleanly on `Ctrl+C`. What's actually happening under the hood: | Stage | What runs | |---|---| | **1. Preflight** | Verifies `docker` and `docker compose` are available. | | **2. Service start** | `docker compose up -d` with the `cache` + `queue` profiles auto-activated. db, redis, and asynqmon come up together. | | **3. Health-wait** | Polls each service's compose healthcheck until ready (30s timeout). | | **4. Migrate** | Runs `migrate up` against the now-healthy database. | | **5. Air** | Execs `go tool air` against `.air.toml` — your code rebuilds on every save. | | **6. Teardown** | On `Ctrl+C`, stops services (volumes preserved so the next run reuses your data). | Your app is live at: | Endpoint | URL | |---|---| | REST API | `http://localhost:8080/api/v1/` | | Health check | `http://localhost:8080/health` | | Prometheus metrics | `http://localhost:8080/metrics` | | Swagger UI | `http://localhost:8080/swagger/index.html` *(after `gofasta swagger`)* | If you scaffolded with `--graphql`: | Endpoint | URL | |---|---| | GraphQL | `http://localhost:8080/graphql` | | GraphQL Playground | `http://localhost:8080/graphql-playground` | ### Interactive controls while it's running `gofasta dev` puts your terminal in raw mode and listens for single-key shortcuts. You'll see a banner the moment the loop starts: ``` ⌨ press `r` to restart · `q` to quit · `h` for help ``` | Key | Action | |---|---| | `r` / `R` | **Restart the entire pipeline from scratch** — services stop, `.env` reloads, every stage re-runs. Use this after editing `.env`, `config.yaml`, or rebasing migrations. Air-watched Go files don't need it; Air picks those up automatically. | | `q` / `Q` | Quit cleanly. Same effect as `Ctrl+C`. | | `h` / `H` / `?` | Print the keybinding help inline. | The listener auto-disables when stdin isn't a TTY (CI runs, piped input). Pass `--no-keyboard` to disable it explicitly. ### Useful flags | Flag | What it does | |---|---| | `gofasta dev --dry-run` | Print what *would* run, exit. Great in CI to validate compose + config without spinning anything up. | | `gofasta dev --fresh` | Drop every compose volume before starting. Clean slate database. | | `gofasta dev --seed` | Run seeders after migrations. | | `gofasta dev --no-cache` | Skip the cache (Redis). Use when you don't need it. | | `gofasta dev --no-queue` | Skip the queue dashboard. | | `gofasta dev --no-services` | Skip compose entirely. Air only — use when you bring your own database. | | `gofasta dev --all-in-docker` | Run *everything* in Docker, including Air. Supporting services run detached; the app container's stdout streams to your terminal so hot-reload feels identical to host-Air. | | `gofasta dev --dashboard` | Spin up the [dev dashboard](/docs/cli-reference/dev#the-dashboard) on `:9090` — live routes, request inspector, trace waterfall, EXPLAIN-on-click, panic history, cache hit/miss, HAR export. Only available with the `devtools` build tag, which `gofasta dev` sets automatically. | | `gofasta dev --attach-logs` | Stream `docker compose logs -f` alongside Air output. | | `gofasta dev --json` | NDJSON event stream instead of human log lines — for CI and agents. | Full reference at [/docs/cli-reference/dev](/docs/cli-reference/dev). ## Step 3: Generate a Resource In a separate terminal (leave `gofasta dev` running): ```bash gofasta g scaffold Product name:string price:float ``` This generates **11 files** and patches **4** existing files in one shot: | Generated file | What it is | |---|---| | `app/models/product.model.go` | Database model | | `db/migrations/000006_create_products.up.sql` | Create table migration | | `db/migrations/000006_create_products.down.sql` | Drop table migration | | `app/repositories/interfaces/product_repository.go` | Repository interface | | `app/repositories/product.repository.go` | Repository implementation (GORM) | | `app/services/interfaces/product_service.go` | Service interface | | `app/services/product.service.go` | Service implementation | | `app/dtos/product.dtos.go` | Request/response DTOs with validation | | `app/di/providers/product.go` | Wire DI provider | | `app/rest/controllers/product.controller.go` | REST controller | | `app/rest/routes/product.routes.go` | Route definitions (GET, POST, PUT, DELETE) | Plus automatic patches to wire everything in: - `app/di/container.go` — `ProductService` + `ProductController` fields - `app/di/wire.go` — `ProductSet` added to the Wire build - `app/rest/routes/index.routes.go` — `/api/v1/products` registered - `cmd/serve.go` — controller wired into route config If you scaffolded the project with `--graphql`, pass `--graphql` here too and the resolver + `.gql` schema get generated alongside. ### Press `r` to apply Back in the `gofasta dev` terminal, **press `r`**. The pipeline tears down, re-runs preflight, re-applies migrations (your new `000006_create_products` runs), and Air rebuilds against the new generated code. Your `/api/v1/products` endpoint is now live — no manual `gofasta migrate up`, no separate restart cycle. > Air does pick up `.go` saves automatically. You only need `r` for things outside Air's watch — migrations, config changes, `.env` edits. ## Step 4: Test Your API ```bash # Create a product curl -X POST http://localhost:8080/api/v1/products \ -H "Content-Type: application/json" \ -d '{"name": "Widget", "price": 9.99}' # List curl http://localhost:8080/api/v1/products # Get one curl http://localhost:8080/api/v1/products/{id} # Update curl -X PUT http://localhost:8080/api/v1/products/{id} \ -H "Content-Type: application/json" \ -d '{"name": "Updated Widget", "price": 19.99}' # Delete curl -X DELETE http://localhost:8080/api/v1/products/{id} ``` Want to see every captured request, SQL query, and trace span live? Start `gofasta dev --dashboard` and open `http://localhost:9090`. Click a request row → see its trace waterfall, the SQL it ran, the response body, and a one-click Replay button. ## Supported Field Types | Type | Go type | SQL type (Postgres) | GraphQL type | |---|---|---|---| | `string` | `string` | `VARCHAR(255)` | `String` | | `text` | `string` | `TEXT` | `String` | | `int` | `int` | `INTEGER` | `Int` | | `float` | `float64` | `DECIMAL(10,2)` | `Float` | | `bool` | `bool` | `BOOLEAN` | `Boolean` | | `uuid` | `uuid.UUID` | `UUID` | `ID` | | `time` | `time.Time` | `TIMESTAMP` | `DateTime` | SQL types are automatically adapted for MySQL, SQLite, SQL Server, and ClickHouse based on the `database.driver` in your `config.yaml`. ## What's next - **[gofasta dev — full reference](/docs/cli-reference/dev)** — every flag, every event, the dashboard's debug surfaces (trace waterfall, EXPLAIN-on-click, N+1 detection, HAR export) - **[Code generation](/docs/guides/code-generation)** — every generator: model, repository, service, controller, job, task, email-template, resolver - **[Configuration](/docs/guides/configuration)** — `config.yaml` schema, env-var overrides, multi-database support - **[Authentication](/docs/guides/authentication)** — JWT + Casbin RBAC, scaffolded auth flows - **[Agent integrations](/docs/cli-reference/ai)** — `gofasta ai claude`, `cursor`, `codex`, `aider`, `windsurf` - **[Deployment](/docs/guides/deployment)** — one-command VPS deploy via `gofasta deploy` ## Related --- ## /docs/getting-started/project-structure — Project Structure > Understand the directory layout of a Gofasta project. # Project Structure When you run `gofasta new myapp`, the CLI creates the following structure: ``` myapp/ ├── app/ # Your application code │ ├── main/main.go # Entry point │ ├── models/ # Database models (User starter included) │ ├── dtos/ # Request/response types │ ├── repositories/ # Data access layer │ │ └── interfaces/ # Repository contracts │ ├── services/ # Business logic │ │ └── interfaces/ # Service contracts │ ├── rest/ │ │ ├── controllers/ # HTTP handlers │ │ └── routes/ # Route definitions │ ├── graphql/ # Only present with --graphql │ │ ├── schema/ # GraphQL schema files (.gql) │ │ └── resolvers/ # GraphQL resolvers │ ├── validators/ # Input validation rules │ ├── di/ # Dependency injection (Google Wire) │ │ ├── container.go # Service container struct │ │ ├── wire.go # Wire build configuration │ │ ├── wire_gen.go # Generated Wire output │ │ ├── setters.go # Wire setters │ │ └── providers/ # Wire provider sets │ ├── jobs/ # Cron jobs │ └── tasks/ # Async task handlers (asynq) ├── cmd/ # CLI commands for your app │ ├── root.go # Cobra root command │ ├── serve.go # Starts the HTTP server │ ├── migrate.go # Runs database migrations │ └── seed.go # Runs database seeders ├── db/ │ ├── migrations/ # SQL migration files (up + down) │ └── seeds/ # Database seed functions ├── configs/ # RBAC policies, feature flags ├── deployments/ # Docker, CI/CD workflows, nginx, systemd ├── templates/emails/ # HTML email templates │ └── layouts/ # Email layout templates ├── locales/ # Translation files (i18n YAML) ├── testutil/mocks/ # Test mocks ├── config.yaml # Application configuration ├── compose.yaml # Docker Compose (app + PostgreSQL) ├── Dockerfile # Production container image ├── Makefile # Development shortcuts ├── gqlgen.yml # GraphQL code generation config (only present with --graphql) ├── .air.toml # Air hot reload configuration └── .env.example # Example environment variables ``` ## Key Directories ### `app/` — Your Application Code This is where you spend most of your time. It follows a layered architecture — one directory per technical concern (models, DTOs, repositories, services, controllers, routes), with each resource contributing one file to each directory: ``` Request → Controller → Service → Repository → Database ``` #### Why layered, not feature-module This layout is a deliberate choice, not an oversight. The main alternative — **feature modules** (also called vertical slice architecture, package by feature, or DDD modules), where each business domain lives in its own directory containing its controller, service, repository, and model together — has real merits for large applications. Gofasta ships with a layered layout because: - **It matches the mental model most backend engineers already have.** The "controller → service → repository" pipeline is the dominant pattern in mainstream MVC-style backends, and developers coming from that background find a layered layout immediately familiar. - **It keeps small projects small.** For an app with 3–8 resources, flat layer directories are simpler to navigate than nested feature modules. - **It aligns with Go's stdlib aesthetic** of small, focused packages (`net/http`, `encoding/json`, `database/sql`) rather than large multi-concern packages. - **Cross-cutting concerns have a natural home.** Shared DTOs, middleware, and validators don't have to be forced into a `shared/` or `common/` grab-bag. For projects that grow past ~15 resources or multiple teams, feature-module structure scales better. The [Restructuring to feature modules](#restructuring-to-feature-modules) section below explains how to migrate — the change is localized and the `pkg/*` library imposes zero assumptions about which layout you use. - **`models/`** — GORM database models. Each model embeds `models.BaseModelImpl` from the gofasta library to get standard fields (ID, timestamps, soft delete, versioning). - **`dtos/`** — Data Transfer Objects for request parsing and response formatting. Keeps your API contract separate from your database models. - **`repositories/`** — Data access layer. Each repository has an interface in `interfaces/` and an implementation that uses GORM. - **`services/`** — Business logic layer. Receives DTOs, calls repositories, applies rules. This is where your domain logic goes. - **`rest/controllers/`** — HTTP handlers. Parse requests, call services, return responses. No business logic here. - **`rest/routes/`** — Route definitions. Maps HTTP methods and paths to controller methods. - **`graphql/`** — Schema files (`.gql`) and resolvers. gqlgen generates resolver stubs from your schema. This directory is only present when the project was created with `gofasta new --graphql`. - **`validators/`** — Custom validation rules using `go-playground/validator/v10` struct tags. - **`di/`** — Google Wire dependency injection. `wire.go` defines how to build the container; `wire_gen.go` is the generated output. Tools are registered via `go mod edit -tool` (Go 1.24+ tool dependencies) and invoked via `go tool wire`, `go tool gqlgen`, etc. - **`jobs/`** — Cron jobs for scheduled background work. - **`tasks/`** — Async task handlers using `hibiken/asynq`. ### `cmd/` — Project CLI Commands Your project has its own CLI built with Cobra: - **`serve.go`** — Starts the HTTP server. Initializes the DI container, registers routes, and listens on the configured port. - **`migrate.go`** — Runs database migrations up or down. - **`seed.go`** — Runs database seeders. Useful for populating test data. ### `db/` — Database Files - **`migrations/`** — SQL migration files in pairs: `000001_create_users.up.sql` and `000001_create_users.down.sql`. Run with `gofasta migrate up` and `gofasta migrate down`. - **`seeds/`** — Go functions that insert test/default data into the database. ### `deployments/` — Infrastructure Configuration for the deployment target gofasta natively supports: a Linux VPS running Docker or systemd, reached over SSH via [`gofasta deploy`](/docs/cli-reference/deploy). - **`ci/`** — GitHub Actions workflow templates (test, release, deploy-vps). These ship as inert YAML; copy them into `.github/workflows/` to activate — see the [deployment guide](/docs/guides/deployment#github-actions-workflows) for details. - **`docker/`** — Production compose file used by `gofasta deploy`, plus a development Dockerfile with Air for hot reload. - **`nginx/`** — Reverse proxy configuration with TLS termination. - **`systemd/`** — Service unit and reference deploy script used by the `binary` deploy method. ### `config.yaml` — Application Configuration Central configuration file for your app: ```yaml app: name: myapp port: 8080 env: development database: driver: postgres host: localhost port: 5432 name: myapp_db user: postgres password: postgres jwt: secret: your-secret-key expiration: 24h redis: host: localhost port: 6379 ``` Settings can be overridden with environment variables using the prefix `GOFASTA_` with the pattern `GOFASTA_SECTION_KEY` (e.g., `GOFASTA_DATABASE_HOST=db`). ## What You Write vs. What the Library Provides | You write | The gofasta library provides | |-----------|------------------------------| | `app/models/product.model.go` | `pkg/models` — BaseModelImpl with UUID, timestamps, soft delete | | `app/services/product.service.go` | `pkg/validators` — Input validation, `pkg/utils` — Pagination | | `app/rest/controllers/product.controller.go` | `pkg/httputil` — Bind, Handle, JSON responses | | `app/rest/routes/product.routes.go` | `pkg/middleware` — CORS, logging, rate limiting | | `config.yaml` | `pkg/config` — LoadConfig(), SetupDB() | | `cmd/serve.go` | `pkg/health` — Health check endpoints | ## Restructuring to feature modules The layered default fits most projects, but for larger codebases — many resources, multiple teams, or plans to extract microservices later — **feature modules** (also called vertical slice architecture or package by feature) scale better. The idea: each business domain gets its own directory containing everything that feature needs. ### What the target layout looks like ``` app/ ├── user/ │ ├── model.go # GORM model │ ├── repository.go # Repository impl │ ├── repository_iface.go # Repository interface │ ├── service.go # Business logic │ ├── service_iface.go # Service interface │ ├── controller.go # REST handlers │ ├── routes.go # chi.Router registration │ ├── dtos.go # Request/response types │ ├── validators.go # Feature-specific validators (optional) │ └── service_test.go ├── product/ # Same shape, different domain │ ├── model.go │ ├── repository.go │ ├── service.go │ ├── controller.go │ ├── routes.go │ └── dtos.go ├── invoice/ # Same shape, different domain ├── shared/ # Cross-cutting types: common DTOs, shared validators ├── di/ # Stays as a top-level concern ├── jobs/ # Stays layered — cron jobs don't belong to one domain └── tasks/ # Stays layered — async tasks typically cross domains ``` ### Benefits you get - **Change locality.** Add a `Subscription` resource → create one directory. Under layered, that same addition edits `models/`, `dtos/`, `repositories/`, `services/`, `controllers/`, `routes/`, `validators/`, and tests — eight places for one feature. - **Package-private encapsulation.** Because Go hides lowercase identifiers at the package boundary, a `user/` package can expose `user.Service` and `user.Controller` while keeping `repository`, `mapper`, and internal types unexported. Layered structure forces most types public because layers reference each other across package boundaries. - **Readable `ls app/`.** `user/ product/ invoice/ subscription/ notification/` tells you what the app does. Layered `controllers/ services/ repositories/` tells you only how it's built. - **Easier extraction.** One feature = one candidate microservice later. No multi-directory hunt. ### Drawbacks to weigh - **More ceremony for small apps.** Three resources in three folders with six files each is heavier than six flat folders with three files each. - **Cross-feature references need discipline.** `user` referencing `invoice` can create import cycles if not careful. DDD's rule is that only aggregate roots cross module boundaries, and usually through an interface defined in the consumer side. - **Unfamiliar to engineers coming from layered MVC backends.** Feature modules feel like package-by-feature / vertical-slice codebases; flat layers feel like the layered MVC backends most engineers learn first. Team background matters. ### Migration recipe Starting from a layered Gofasta scaffold, moving one resource at a time keeps the app compilable throughout: 1. **Create the feature directory** — e.g., `mkdir app/user`. 2. **Move and rename files** — `git mv` each layer's user file into the new directory, and rename to drop the redundant prefix: ```bash git mv app/models/user.model.go app/user/model.go git mv app/dtos/user.dtos.go app/user/dtos.go git mv app/repositories/user.repository.go app/user/repository.go git mv app/repositories/interfaces/user_repository.go app/user/repository_iface.go git mv app/services/user.service.go app/user/service.go git mv app/services/interfaces/user_service.go app/user/service_iface.go git mv app/rest/controllers/user.controller.go app/user/controller.go git mv app/rest/routes/user.routes.go app/user/routes.go git mv app/validators/user.validators.go app/user/validators.go ``` 3. **Change each moved file's package declaration** from `package models`, `package services`, `package controllers`, etc. to `package user`. 4. **Update imports in the moved files.** Internal references that used to cross packages (e.g., `controllers.UserController` calling `services.UserService`) collapse to in-package references (`Service` instead of `services.UserService`). Types like `models.User` become just `User`. 5. **Update external callers** — `app/di/providers/user.go.tmpl`, `cmd/serve.go`, and `app/rest/routes/index.routes.go` need their imports changed from `app/rest/controllers`, `app/services`, etc. to `app/user`. 6. **Regenerate Wire** — `gofasta wire`. The dependency graph didn't change, only the import paths, so generated output stays correct after imports are updated. 7. **Compile + test** — `go build ./...` catches any missed import, then `go test ./...`. 8. **Delete the now-empty `app/models/`, `app/dtos/`, etc.** directories for the migrated resource. 9. **Repeat** for each resource. ### What the CLI does (and doesn't) do As of today, `gofasta g scaffold Product name:string price:float` generates into the **layered** layout — that's the only output shape the generator supports. If you've restructured to feature modules, you have two options: - **Run `gofasta g scaffold`, then move the generated files** into your feature directory with the migration recipe above. Takes ~30 seconds per resource. - **Stop using `gofasta g scaffold`** for feature-module projects and write the files by hand. The eight-file template is predictable enough to copy-paste from an existing resource. A `--layout=feature` flag on `gofasta new` and the generators is on the wishlist — see [the white paper's roadmap](/docs/white-paper#24-roadmap) — but not yet implemented. If you'd find it useful, open an issue. ### When to migrate (and when not to) Migrate when: - The app has ~15+ resources and growing. - Multiple teams are contributing and stepping on each other in shared layer directories. - You're planning to extract specific domains into separate services later. - The layered structure is creating visible coupling problems (e.g., `user.service` knows about `invoice.dtos`). Stay layered when: - The app has fewer than ~10 resources. - The team is mostly coming from layered MVC backends and the flat layout matches their mental model. - You haven't felt real pain from the current structure — speculative refactoring is the most expensive kind. Both layouts are legitimate. The Go community is genuinely split on this — Ben Johnson's "Standard Package Layout", the package-by-feature / vertical-slice camp, and the traditional layered camp are all actively defended in production codebases. Pick the one that fits your team and your current scale; you can migrate later if the trade-offs shift. ## Next Steps - [Build a REST API](/docs/guides/rest-api) - [Learn about code generation](/docs/guides/code-generation) - [Configure your database](/docs/guides/database-and-migrations) ## Related --- ## /docs/guides/rest-api — REST API > Building REST APIs with Gofasta -- controllers, routes, DTOs, middleware, and response helpers. # REST API REST is the default API mode in Gofasta. When you run `gofasta new myapp`, the generated project includes a fully working REST API with no additional flags required. GraphQL can be added optionally via the `--graphql` flag. This guide covers how controllers, routes, DTOs, middleware, and response helpers work together to handle HTTP requests. ## Architecture Overview Every REST request flows through four layers: ``` HTTP Request → Route → Controller → Service → Repository → Database ``` - **Routes** map HTTP methods and paths to controller methods - **Controllers** parse requests, validate input, call services, and return responses - **Services** contain business logic and orchestrate repository calls - **Repositories** interact with the database via GORM ## Controllers Controllers live in `app/rest/controllers/`. Each controller receives a service through dependency injection and exposes handler methods for CRUD operations. ```go package controllers import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/apperrors" "github.com/gofastadev/gofasta/pkg/httputil" "myapp/app/dtos" "myapp/app/services/interfaces" ) type ProductController struct { service interfaces.ProductService } func NewProductController(service interfaces.ProductService) *ProductController { return &ProductController{service: service} } func (c *ProductController) Create(w http.ResponseWriter, r *http.Request) error { var req dtos.CreateProductRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return apperrors.BadRequest("invalid request body") } result, err := c.service.Create(r.Context(), req) if err != nil { return err } return httputil.Created(w, result) } func (c *ProductController) List(w http.ResponseWriter, r *http.Request) error { params := httputil.ParsePaginationParams(r) products, total, err := c.service.FindAll(r.Context(), params) if err != nil { return err } return httputil.PaginatedJSON(w, products, total, params) } func (c *ProductController) GetByID(w http.ResponseWriter, r *http.Request) error { id := chi.URLParam(r, "id") product, err := c.service.FindByID(r.Context(), id) if err != nil { return err } return httputil.OK(w, product) } func (c *ProductController) Update(w http.ResponseWriter, r *http.Request) error { id := chi.URLParam(r, "id") var req dtos.UpdateProductRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return apperrors.BadRequest("invalid request body") } result, err := c.service.Update(r.Context(), id, req) if err != nil { return err } return httputil.OK(w, result) } func (c *ProductController) Archive(w http.ResponseWriter, r *http.Request) error { id := chi.URLParam(r, "id") if err := c.service.Delete(r.Context(), id); err != nil { return err } return httputil.NoContent(w) } ``` ## Routes Routes live in `app/rest/routes/`. Each resource has its own route file that registers paths and maps them to controller methods. ```go package routes import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/httputil" "myapp/app/rest/controllers" ) func ProductRoutes(r chi.Router, ctrl *controllers.ProductController) { r.Get("/products", httputil.Handle(ctrl.List)) r.Post("/products", httputil.Handle(ctrl.Create)) r.Get("/products/{id}", httputil.Handle(ctrl.GetByID)) r.Put("/products/{id}", httputil.Handle(ctrl.Update)) r.Delete("/products/{id}", httputil.Handle(ctrl.Archive)) } ``` All resource route files are registered in `app/rest/routes/index.routes.go`, which is called from `cmd/serve.go`: ```go package routes import "github.com/go-chi/chi/v5" func RegisterRoutes(api chi.Router, deps *di.Container) { UserRoutes(api, deps.UserController) ProductRoutes(api, deps.ProductController) } ``` ## DTOs (Data Transfer Objects) DTOs live in `app/dtos/` and define the shape of request and response payloads. They keep your API contract separate from your database models. ```go package dtos import "time" type CreateProductRequest struct { Name string `json:"name" validate:"required"` Price float64 `json:"price" validate:"required,gt=0"` } type UpdateProductRequest struct { Name *string `json:"name"` Price *float64 `json:"price" validate:"omitempty,gt=0"` } type ProductResponse struct { ID string `json:"id"` Name string `json:"name"` Price float64 `json:"price"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } ``` Use pointer fields in update DTOs to distinguish between "not provided" (nil) and "set to zero value". ## Response Helpers The `github.com/gofastadev/gofasta/pkg/httputil` package provides consistent response formatting across your API. ### Standard Responses | Helper | Status Code | Use Case | |--------|-------------|----------| | `httputil.OK(w, data)` | 200 | Successful read or update | | `httputil.Created(w, data)` | 201 | Successful resource creation | | `httputil.NoContent(w)` | 204 | Successful deletion | | `httputil.BadRequest(w, msg)` | 400 | Validation or parsing failure | | `httputil.Unauthorized(w, msg)` | 401 | Missing or invalid auth token | | `httputil.Forbidden(w, msg)` | 403 | Insufficient permissions | | `httputil.NotFound(w, msg)` | 404 | Resource not found | | `httputil.HandleError(w, err)` | varies | Maps error types to status codes automatically | All responses follow a consistent JSON envelope: ```json { "success": true, "data": { ... }, "message": "Resource created successfully" } ``` Error responses follow the same structure: ```json { "success": false, "error": "Product not found", "message": "The requested resource does not exist" } ``` ### Pagination `httputil.ParsePaginationParams` extracts `page`, `per_page`, `sort`, and `order` from query parameters with sensible defaults: ```go // GET /api/v1/products?page=2&per_page=20&sort=created_at&order=desc params := httputil.ParsePaginationParams(r) // params.Page = 2, params.PerPage = 20, params.Sort = "created_at", params.Order = "desc" ``` `httputil.PaginatedJSON` wraps the response with pagination metadata: ```json { "success": true, "data": [ ... ], "meta": { "page": 2, "per_page": 20, "total": 143, "total_pages": 8 } } ``` ### Request Decoding Use `json.NewDecoder` from the standard library to parse request bodies, and `go-playground/validator/v10` struct tags for validation: ```go var req dtos.CreateProductRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return apperrors.BadRequest("invalid request body") } ``` ## Middleware Gofasta includes built-in middleware from `github.com/gofastadev/gofasta/pkg/middleware`. Middleware is applied in `cmd/serve.go` when the server starts. ```go import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/middleware" ) router := chi.NewRouter() router.Use(middleware.Logger()) router.Use(middleware.Recovery()) router.Use(middleware.CORS(middleware.CORSConfig{ AllowOrigins: []string{"http://localhost:3000"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Authorization", "Content-Type"}, })) router.Use(middleware.RateLimiter(middleware.RateLimiterConfig{ Max: 100, Window: time.Minute, })) ``` ### Available Middleware | Middleware | Description | |-----------|-------------| | `middleware.Logger()` | Structured request logging with duration | | `middleware.Recovery()` | Panic recovery with stack trace logging | | `middleware.CORS(config)` | Cross-origin resource sharing | | `middleware.RateLimiter(config)` | Request rate limiting per IP | | `middleware.Auth(jwtConfig)` | JWT token verification | | `middleware.RBAC(enforcer)` | Casbin role-based access control | | `middleware.RequestID()` | Adds a unique request ID to each request | ### Protecting Routes Apply auth middleware to specific route groups: ```go func RegisterRoutes(api chi.Router, deps *di.Container) { // Public routes AuthRoutes(api, deps.AuthController) // Protected routes — chi's Group runs the shared middleware for every // route registered inside the callback without mounting at a new prefix. api.Group(func(protected chi.Router) { protected.Use(middleware.Auth(deps.JWTConfig)) ProductRoutes(protected, deps.ProductController) OrderRoutes(protected, deps.OrderController) }) // Admin-only routes — Route mounts the callback at a sub-path. api.Route("/admin", func(admin chi.Router) { admin.Use(middleware.Auth(deps.JWTConfig)) admin.Use(middleware.RBAC(deps.Enforcer)) AdminRoutes(admin, deps.AdminController) }) } ``` ## Adding a New Endpoint The fastest way to add a new resource is with the scaffold generator: ```bash gofasta g scaffold Order total:float status:string user_id:uuid ``` This generates the model, migration, repository, service, controller, routes, DTOs, and DI provider, then wires everything together automatically. To add a custom endpoint to an existing controller, add the handler method and register the route: ```go // In app/rest/controllers/product.controller.go func (c *ProductController) Search(w http.ResponseWriter, r *http.Request) error { query := r.URL.Query().Get("q") products, err := c.service.Search(r.Context(), query) if err != nil { return err } return httputil.OK(w, products) } // In app/rest/routes/product.routes.go r.Get("/search", httputil.Handle(ctrl.Search)) ``` ## Swapping the Router The scaffold uses [go-chi/chi](https://github.com/go-chi/chi) as its default HTTP router because it builds on `net/http`, has zero external dependencies, and adds ergonomic subrouters and middleware groups. Like every other `pkg/*` default in Gofasta, the router is an **opt-out default**, not a required dependency — you can swap it for `gorilla/mux`, `go-chi` v4, or even Go 1.22+'s standard `http.ServeMux` without touching the rest of the project. ### What "swapping the router" means Every REST-facing file in a generated project touches the router in exactly one of three ways: it constructs the root router, it registers routes on a passed-in router argument, or it reads a path parameter. Replacing chi means editing those three touch points consistently across the files listed below. No code in the gofasta library depends on chi — so once these files compile, you're done. ### Files to edit | File | What to change | |------|----------------| | `app/rest/routes/index.routes.go` | Replace `chi.NewRouter()` with your router constructor. Replace `r.Mount("/api/v1", api)` with your router's subrouter/prefix equivalent. | | `app/rest/routes/*.routes.go` (one per resource) | Change the `r chi.Router` parameter type and the `r.Get` / `r.Post` / `r.Put` / `r.Delete` calls to your router's API. | | `app/rest/controllers/*.controller.go` (one per resource) | Replace every `chi.URLParam(r, "id")` call with your router's path-parameter helper. | | `cmd/serve.go` | If your replacement router does not return `http.Handler`, adjust the top-level `http.NewServeMux()` mount at `mux.Handle("/", apiRouter)`. | | `go.mod` | `go get` the replacement and remove the chi require line (run `go mod tidy` to clean up). | That's it — the gofasta library (`github.com/gofastadev/gofasta/pkg/*`) never imports any router. Middleware in `pkg/middleware` is plain `func(http.Handler) http.Handler`, so it works with any router that accepts `net/http` middleware. ### Example — swapping to gorilla/mux ```go // app/rest/routes/user.routes.go package routes import ( "github.com/gorilla/mux" "github.com/gofastadev/gofasta/pkg/httputil" "myapp/app/rest/controllers" ) func UserRoutes(r *mux.Router, uc *controllers.UserController) { r.HandleFunc("/users", httputil.Handle(uc.ListUsers)).Methods("GET") r.HandleFunc("/users/{id}", httputil.Handle(uc.GetUser)).Methods("GET") // ... } ``` ```go // app/rest/controllers/user.controller.go func (uc *UserController) GetUser(w http.ResponseWriter, r *http.Request) error { id := mux.Vars(r)["id"] // ... } ``` ### Example — swapping to stdlib `http.ServeMux` (Go 1.22+) ```go // app/rest/routes/user.routes.go package routes import ( "net/http" "github.com/gofastadev/gofasta/pkg/httputil" "myapp/app/rest/controllers" ) func UserRoutes(mux *http.ServeMux, uc *controllers.UserController) { mux.HandleFunc("GET /users", httputil.Handle(uc.ListUsers)) mux.HandleFunc("GET /users/{id}", httputil.Handle(uc.GetUser)) // ... } ``` ```go // app/rest/controllers/user.controller.go func (uc *UserController) GetUser(w http.ResponseWriter, r *http.Request) error { id := r.PathValue("id") // ... } ``` Stdlib `ServeMux` has no built-in subrouter or per-group middleware. If you need those, either prefix every route manually or use `http.StripPrefix` with a nested `*http.ServeMux`. ### `gofasta routes` after a swap The `gofasta routes` introspection command parses route files for chi's method-based API (`r.Get`, `r.Post`, etc.) and `r.Mount(...)` calls. If you swap routers, `gofasta routes` will return an empty list — it won't crash your app, but the CLI-side listing stops working. You can either stop using it, or vendor a small fork of `cli/internal/commands/routes.go` that matches your router's syntax. ## Next Steps - [Set up GraphQL alongside REST](/docs/guides/graphql) - [Configure authentication and RBAC](/docs/guides/authentication) - [Generate code with the CLI](/docs/guides/code-generation) - [HTTP Utilities API reference](/docs/api-reference/http-utilities) - [Middleware API reference](/docs/api-reference/middleware) ## Related --- ## /docs/guides/graphql — GraphQL > Building GraphQL APIs with gqlgen in Gofasta -- schemas, resolvers, queries, mutations, and subscriptions. # GraphQL Gofasta includes GraphQL support powered by [gqlgen](https://gqlgen.com/), a type-safe Go GraphQL server library. When enabled, your project ships with a working GraphQL endpoint alongside the REST API, both sharing the same services and repositories. > **Prerequisite:** GraphQL support is opt-in. You must create your project with the `--graphql` flag to include GraphQL files, the gqlgen dependency, and the `/graphql` endpoint: > > ```bash > gofasta new myapp --graphql > ``` > > If you created a REST-only project (without `--graphql`), the `app/graphql/` directory, `gqlgen.yml`, and GraphQL routes will not be present. ## How It Works GraphQL in Gofasta follows this flow: ``` Schema (.gql files) → gqlgen generates → Resolver stubs → You implement → Service layer ``` 1. You define your schema in `.gql` files under `app/graphql/schema/` 2. Run `go generate ./...` (or `gofasta g resolver`) to generate resolver stubs 3. Implement the resolver methods by calling your existing services 4. Both REST and GraphQL share the same service and repository layers ## Project Structure ``` app/graphql/ ├── schema/ │ ├── schema.gql # Root schema (Query, Mutation, Subscription types) │ ├── user.gql # User type and operations │ └── product.gql # Product type and operations ├── resolvers/ │ ├── resolver.go # Resolver struct with service dependencies │ ├── schema.resolvers.go # Generated resolver stubs │ ├── user.resolvers.go # User resolver implementations │ └── product.resolvers.go ├── generated/ │ └── generated.go # Auto-generated runtime code (do not edit) └── model/ └── models_gen.go # Auto-generated Go types from schema ``` The `gqlgen.yml` file at the project root controls code generation paths and type mappings. ## Defining a Schema Schema files use standard GraphQL SDL syntax. Each resource typically gets its own `.gql` file. ```graphql # app/graphql/schema/product.gql type Product { id: ID! name: String! price: Float! createdAt: DateTime! updatedAt: DateTime! } input CreateProductInput { name: String! price: Float! } input UpdateProductInput { name: String price: Float } extend type Query { products(page: Int, perPage: Int): ProductConnection! product(id: ID!): Product! } extend type Mutation { createProduct(input: CreateProductInput!): Product! updateProduct(id: ID!, input: UpdateProductInput!): Product! deleteProduct(id: ID!): Boolean! } ``` The root schema file defines the base types and any custom scalars: ```graphql # app/graphql/schema/schema.gql scalar DateTime type Query type Mutation type Subscription type PageInfo { page: Int! perPage: Int! total: Int! totalPages: Int! } type ProductConnection { nodes: [Product!]! pageInfo: PageInfo! } ``` ## Generating Resolvers After modifying schema files, regenerate the resolver stubs: ```bash go generate ./... ``` Or use the Gofasta CLI: ```bash gofasta g resolver ``` This updates `schema.resolvers.go` with new stub methods for any schema additions. Existing implementations are preserved -- only new stubs are added. ## Implementing Resolvers The resolver struct holds references to your services, injected via Wire: ```go // app/graphql/resolvers/resolver.go package resolvers import "myapp/app/services/interfaces" type Resolver struct { UserService interfaces.UserService ProductService interfaces.ProductService } ``` Resolver methods call the same services used by REST controllers: ```go // app/graphql/resolvers/product.resolvers.go package resolvers import ( "context" "myapp/app/dtos" "myapp/app/graphql/model" ) func (r *queryResolver) Products(ctx context.Context, page *int, perPage *int) (*model.ProductConnection, error) { p := 1 pp := 20 if page != nil { p = *page } if perPage != nil { pp = *perPage } params := &dtos.PaginationParams{Page: p, PerPage: pp} products, total, err := r.ProductService.FindAll(ctx, params) if err != nil { return nil, err } totalPages := (int(total) + pp - 1) / pp return &model.ProductConnection{ Nodes: products, PageInfo: &model.PageInfo{ Page: p, PerPage: pp, Total: int(total), TotalPages: totalPages, }, }, nil } func (r *queryResolver) Product(ctx context.Context, id string) (*model.Product, error) { return r.ProductService.FindByID(ctx, id) } func (r *mutationResolver) CreateProduct(ctx context.Context, input model.CreateProductInput) (*model.Product, error) { req := &dtos.CreateProductRequest{ Name: input.Name, Price: input.Price, } return r.ProductService.Create(ctx, req) } func (r *mutationResolver) UpdateProduct(ctx context.Context, id string, input model.UpdateProductInput) (*model.Product, error) { req := &dtos.UpdateProductRequest{ Name: input.Name, Price: input.Price, } return r.ProductService.Update(ctx, id, req) } func (r *mutationResolver) DeleteProduct(ctx context.Context, id string) (bool, error) { err := r.ProductService.Delete(ctx, id) return err == nil, err } ``` ## Subscriptions gqlgen supports WebSocket-based subscriptions for real-time data: ```graphql # app/graphql/schema/product.gql extend type Subscription { productCreated: Product! } ``` Implement the subscription resolver with a channel: ```go func (r *subscriptionResolver) ProductCreated(ctx context.Context) (<-chan *model.Product, error) { ch := make(chan *model.Product, 1) go func() { defer close(ch) // Listen for events from your event system for { select { case <-ctx.Done(): return case product := <-r.ProductService.OnCreated(): ch <- product } } }() return ch, nil } ``` ## GraphQL Playground In development, the GraphQL Playground is available at: ``` http://localhost:8080/graphql-playground ``` The GraphQL endpoint itself is at: ``` http://localhost:8080/graphql ``` You can use the playground to explore your schema, run queries, and test mutations interactively. ## Authentication in GraphQL GraphQL requests pass through the same middleware stack as REST. The JWT auth middleware extracts the user from the `Authorization` header and sets claims on the request context, which is accessible in resolvers: ```go func (r *mutationResolver) CreateProduct(ctx context.Context, input model.CreateProductInput) (*model.Product, error) { claims := auth.ClaimsFromContext(ctx) userID := claims.UserID role := claims.Role // Use userID and role for authorization logic // ... } ``` ## gqlgen Configuration The `gqlgen.yml` file at your project root controls code generation: ```yaml schema: - app/graphql/schema/*.gql exec: filename: app/graphql/generated/generated.go package: generated model: filename: app/graphql/model/models_gen.go package: model resolver: layout: follow-schema dir: app/graphql/resolvers package: resolvers autobind: - myapp/app/models ``` The `autobind` setting tells gqlgen to map GraphQL types to your existing Go model structs when names match, avoiding duplicate type definitions. ## Adding a New GraphQL Resource 1. Create a schema file in `app/graphql/schema/order.gql` 2. Define the type, inputs, and extend Query/Mutation 3. Run `go generate ./...` to generate resolver stubs 4. Implement the resolver methods by calling your services 5. Add the service dependency to the `Resolver` struct If you generated the resource with `gofasta g scaffold`, the schema file and resolver are already created for you. ## Next Steps - [Build REST endpoints](/docs/guides/rest-api) - [Set up authentication](/docs/guides/authentication) - [Generate code with the CLI](/docs/guides/code-generation) ## Related --- ## /docs/guides/database-and-migrations — Database & Migrations > Database setup, GORM models, migrations, multi-database support, and relationships in Gofasta. # Database & Migrations Gofasta uses [GORM](https://gorm.io/) as its ORM and raw SQL files for migrations. This guide covers database configuration, model definitions, migration management, and multi-database support. ## Database Configuration Database settings live in `config.yaml`: ```yaml database: driver: postgres host: localhost port: 5432 name: myapp_db user: postgres password: postgres ssl_mode: disable max_open_conns: 25 max_idle_conns: 5 conn_max_lifetime: 5m ``` The `github.com/gofastadev/gofasta/pkg/config` package loads this configuration and provides a `SetupDB()` function that initializes the GORM connection: ```go import "github.com/gofastadev/gofasta/pkg/config" cfg := config.LoadConfig("config.yaml") db, err := config.SetupDB(cfg) ``` ## Supported Databases Gofasta supports five database drivers: | Driver | `database.driver` value | Default Port | |--------|------------------------|-------------| | PostgreSQL | `postgres` | 5432 | | MySQL | `mysql` | 3306 | | SQLite | `sqlite` | N/A | | SQL Server | `sqlserver` | 1433 | | ClickHouse | `clickhouse` | 9000 | Switch databases by changing the `database.driver` value in `config.yaml`. `pkg/config.SetupDB` handles driver-specific connection strings and dialect differences automatically. For SQLite, set the `database.name` to a file path: ```yaml database: driver: sqlite name: ./data/myapp.db ``` ## Models Models live in `app/models/` and represent your database tables. Every model embeds `models.BaseModelImpl` from `pkg/models`, which provides standard fields. ### BaseModelImpl The `github.com/gofastadev/gofasta/pkg/models` package provides `BaseModelImpl`: ```go type BaseModelImpl struct { ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"` Version int `gorm:"default:1" json:"version"` } ``` This gives every model: - **UUID primary key** with auto-generation - **Timestamps** for creation and last update - **Soft delete** via `DeletedAt` (records are not removed from the database) - **Optimistic locking** via `Version` for concurrent update safety ### Defining a Model ```go // app/models/product.model.go package models import "github.com/gofastadev/gofasta/pkg/models" type Product struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` Description string `gorm:"type:text" json:"description"` CategoryID *string `gorm:"type:uuid" json:"category_id,omitempty"` } ``` ### Relationships Define relationships using standard GORM conventions: ```go // One-to-Many: Category has many Products type Category struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Products []Product `gorm:"foreignKey:CategoryID" json:"products,omitempty"` } type Product struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` CategoryID *string `gorm:"type:uuid" json:"category_id,omitempty"` Category *Category `gorm:"foreignKey:CategoryID" json:"category,omitempty"` } // Many-to-Many: Product has many Tags type Product struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Tags []Tag `gorm:"many2many:product_tags;" json:"tags,omitempty"` } type Tag struct { models.BaseModelImpl Name string `gorm:"type:varchar(100);not null;uniqueIndex" json:"name"` Products []Product `gorm:"many2many:product_tags;" json:"products,omitempty"` } ``` ## Migrations Gofasta uses SQL migration files in pairs: an `up` file to apply changes and a `down` file to reverse them. Migration files live in `db/migrations/`. ### Migration File Format ``` db/migrations/ ├── 000001_create_users.up.sql ├── 000001_create_users.down.sql ├── 000002_create_categories.up.sql ├── 000002_create_categories.down.sql ├── 000003_create_products.up.sql └── 000003_create_products.down.sql ``` Each pair shares a sequence number and a descriptive name. The sequence number determines execution order. ### Writing Migrations Up migration (creates or modifies schema): ```sql -- db/migrations/000003_create_products.up.sql CREATE TABLE products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL, description TEXT, category_id UUID REFERENCES categories(id), created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), deleted_at TIMESTAMP, version INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX idx_products_category_id ON products(category_id); CREATE INDEX idx_products_deleted_at ON products(deleted_at); ``` Down migration (reverses the up migration): ```sql -- db/migrations/000003_create_products.down.sql DROP TABLE IF EXISTS products; ``` ### Generating Migrations Use the CLI to generate a new migration pair: ```bash gofasta g migration create_orders ``` This creates two empty files with the next sequence number: ``` Created db/migrations/000004_create_orders.up.sql Created db/migrations/000004_create_orders.down.sql ``` When using `gofasta g scaffold` or `gofasta g model`, migration files are generated automatically with the correct SQL for your configured database driver. ### Running Migrations ```bash # Apply all pending migrations gofasta migrate up # Roll back the last migration gofasta migrate down # Roll back all migrations gofasta migrate down --all # Check current migration status gofasta migrate status ``` ## Repositories Repositories provide a data access layer between your services and the database. Each repository has an interface and an implementation. ### Repository Interface ```go // app/repositories/interfaces/product_repository.go package interfaces import ( "context" "myapp/app/models" ) type ProductRepository interface { Create(ctx context.Context, product *models.Product) error FindAll(ctx context.Context, page, perPage int) ([]models.Product, int64, error) FindByID(ctx context.Context, id string) (*models.Product, error) Update(ctx context.Context, product *models.Product) error Delete(ctx context.Context, id string) error } ``` ### Repository Implementation ```go // app/repositories/product.repository.go package repositories import ( "context" "gorm.io/gorm" "myapp/app/models" ) type ProductRepository struct { db *gorm.DB } func NewProductRepository(db *gorm.DB) *ProductRepository { return &ProductRepository{db: db} } func (r *ProductRepository) Create(ctx context.Context, product *models.Product) error { return r.db.WithContext(ctx).Create(product).Error } func (r *ProductRepository) FindAll(ctx context.Context, page, perPage int) ([]models.Product, int64, error) { var products []models.Product var total int64 r.db.WithContext(ctx).Model(&models.Product{}).Count(&total) offset := (page - 1) * perPage err := r.db.WithContext(ctx). Offset(offset). Limit(perPage). Order("created_at DESC"). Find(&products).Error return products, total, err } func (r *ProductRepository) FindByID(ctx context.Context, id string) (*models.Product, error) { var product models.Product err := r.db.WithContext(ctx).Where("id = ?", id).First(&product).Error return &product, err } func (r *ProductRepository) Update(ctx context.Context, product *models.Product) error { return r.db.WithContext(ctx).Save(product).Error } func (r *ProductRepository) Delete(ctx context.Context, id string) error { return r.db.WithContext(ctx).Where("id = ?", id).Delete(&models.Product{}).Error } ``` ### Eager Loading Relationships Use GORM's `Preload` to load related data: ```go func (r *ProductRepository) FindByIDWithCategory(ctx context.Context, id string) (*models.Product, error) { var product models.Product err := r.db.WithContext(ctx). Preload("Category"). Where("id = ?", id). First(&product).Error return &product, err } ``` ## Database Seeds Seeds live in `db/seeds/` and populate your database with initial or test data: ```go // db/seeds/product_seed.go package seeds import "gorm.io/gorm" func SeedProducts(db *gorm.DB) error { products := []models.Product{ {Name: "Widget", Price: 9.99}, {Name: "Gadget", Price: 24.99}, } for _, p := range products { if err := db.Create(&p).Error; err != nil { return err } } return nil } ``` Run seeds with: ```bash gofasta seed ``` ## Next Steps - [Set up authentication](/docs/guides/authentication) - [Build REST endpoints](/docs/guides/rest-api) - [Generate models with the CLI](/docs/guides/code-generation) - [Models API reference](/docs/api-reference/models) - [Config API reference](/docs/api-reference/config) ## Related --- ## /docs/guides/authentication — Authentication > JWT authentication, login and registration flows, middleware, Casbin RBAC, and token refresh in Gofasta. # Authentication The scaffold imports `pkg/auth` as its **default authentication layer** — JWT tokens for stateless auth, Casbin for role-based access control, and a starter `User` model with login and registration endpoints in every new project. Like every `pkg/*` default, it is **opt-out**: a developer who prefers a different JWT library or RBAC engine can delete the `pkg/auth` import and `go get` an alternative without touching the rest of the project. ## Overview The authentication system consists of: - **JWT tokens** for stateless authentication (`github.com/gofastadev/gofasta/pkg/auth`) - **Auth middleware** for protecting routes (`github.com/gofastadev/gofasta/pkg/middleware`) - **Casbin RBAC** for role-based access control - **Pre-built auth controller** with register, login, and token refresh endpoints ## JWT Configuration JWT settings live in `config.yaml`: ```yaml jwt: secret: your-secret-key-change-in-production expiration: 24h refresh_expiration: 168h # 7 days issuer: myapp ``` Override in production with environment variables: ```bash GOFASTA_JWT_SECRET=your-production-secret GOFASTA_JWT_EXPIRATION=1h ``` ## Registration and Login Flow ### Register The built-in auth controller handles user registration: ```bash curl -X POST http://localhost:8080/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123", "first_name": "Jane", "last_name": "Doe" }' ``` Response: ```json { "success": true, "data": { "user": { "id": "550e8400-e29b-41d4-a716-446655440000", "email": "user@example.com", "first_name": "Jane", "last_name": "Doe", "role": "user" }, "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs..." } } ``` ### Login ```bash curl -X POST http://localhost:8080/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "password": "securepassword123" }' ``` The response includes both an access token and a refresh token. ### Token Refresh When the access token expires, use the refresh token to get a new pair: ```bash curl -X POST http://localhost:8080/api/v1/auth/refresh \ -H "Content-Type: application/json" \ -d '{ "refresh_token": "eyJhbGciOiJIUzI1NiIs..." }' ``` ## Auth Service Implementation The auth service handles password hashing, token generation, and user lookup: ```go // app/services/auth.service.go package services import ( "context" "errors" "github.com/gofastadev/gofasta/pkg/auth" "myapp/app/dtos" "myapp/app/models" "myapp/app/repositories/interfaces" ) type AuthService struct { userRepo interfaces.UserRepository jwtConfig *auth.JWTConfig } func NewAuthService(userRepo interfaces.UserRepository, jwtConfig *auth.JWTConfig) *AuthService { return &AuthService{userRepo: userRepo, jwtConfig: jwtConfig} } func (s *AuthService) Register(ctx context.Context, req *dtos.RegisterRequest) (*dtos.AuthResponse, error) { hashedPassword, err := auth.HashPassword(req.Password) if err != nil { return nil, err } user := &models.User{ Email: req.Email, Password: hashedPassword, FirstName: req.FirstName, LastName: req.LastName, Role: "user", } if err := s.userRepo.Create(ctx, user); err != nil { return nil, err } accessToken, err := auth.GenerateToken(user.ID.String(), user.Role, s.jwtConfig) if err != nil { return nil, err } refreshToken, err := auth.GenerateRefreshToken(user.ID.String(), s.jwtConfig) if err != nil { return nil, err } return &dtos.AuthResponse{ User: user.ToResponse(), AccessToken: accessToken, RefreshToken: refreshToken, }, nil } func (s *AuthService) Login(ctx context.Context, req *dtos.LoginRequest) (*dtos.AuthResponse, error) { user, err := s.userRepo.FindByEmail(ctx, req.Email) if err != nil { return nil, errors.New("invalid credentials") } if !auth.CheckPassword(req.Password, user.Password) { return nil, errors.New("invalid credentials") } accessToken, err := auth.GenerateToken(user.ID.String(), user.Role, s.jwtConfig) if err != nil { return nil, err } refreshToken, err := auth.GenerateRefreshToken(user.ID.String(), s.jwtConfig) if err != nil { return nil, err } return &dtos.AuthResponse{ User: user.ToResponse(), AccessToken: accessToken, RefreshToken: refreshToken, }, nil } ``` ## Auth Package Functions The `github.com/gofastadev/gofasta/pkg/auth` package provides these functions: | Function | Description | |----------|-------------| | `auth.HashPassword(password)` | Bcrypt hash a plaintext password | | `auth.CheckPassword(password, hash)` | Compare plaintext against bcrypt hash | | `auth.GenerateToken(userID, role, config)` | Create a signed JWT access token | | `auth.GenerateRefreshToken(userID, config)` | Create a signed JWT refresh token | | `auth.ValidateToken(tokenString, config)` | Parse and validate a JWT token | | `auth.ExtractClaims(tokenString, config)` | Extract claims from a valid token | ## Protecting Routes with Middleware The auth middleware from `github.com/gofastadev/gofasta/pkg/middleware` validates JWT tokens and sets user information on the request context. ```go import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/middleware" ) // Apply to a route group — chi's Group attaches shared middleware to every // route registered inside the callback without mounting at a new prefix. api.Group(func(protected chi.Router) { protected.Use(middleware.Auth(jwtConfig)) // All routes in this group require a valid JWT protected.Get("/profile", httputil.Handle(userController.GetProfile)) protected.Put("/profile", httputil.Handle(userController.UpdateProfile)) }) ``` The middleware uses the standard `func(http.Handler) http.Handler` signature and: 1. Extracts the `Authorization: Bearer ` header 2. Validates the token signature and expiration 3. Sets claims in the request context (accessed via `r.Context()`) 4. Returns 401 if the token is missing or invalid Access user info in controllers: ```go func (c *UserController) GetProfile(w http.ResponseWriter, r *http.Request) error { claims := auth.ClaimsFromContext(r.Context()) userID := claims.UserID role := claims.Role user, err := c.service.FindByID(r.Context(), userID) if err != nil { return err } return httputil.OK(w, user) } ``` ## Role-Based Access Control (RBAC) with Casbin Gofasta uses [Casbin](https://casbin.org/) for fine-grained role-based access control. Casbin policies define who can access which resources. ### Policy Configuration RBAC policies are defined in `configs/rbac_policy.csv`: ```csv p, admin, /api/v1/*, * p, admin, /api/v1/admin/*, * p, user, /api/v1/products, GET p, user, /api/v1/products/{id}, GET p, user, /api/v1/profile, (GET)|(PUT) p, moderator, /api/v1/products, * p, moderator, /api/v1/products/{id}, * ``` Each line defines a policy rule: `p, role, resource, action`. The RBAC model is defined in `configs/rbac_model.conf`: ```ini [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act) ``` ### Applying RBAC Middleware Stack the RBAC middleware after the auth middleware: ```go import ( "github.com/go-chi/chi/v5" "github.com/gofastadev/gofasta/pkg/middleware" ) // Load the Casbin enforcer enforcer := middleware.NewCasbinEnforcer("configs/rbac_model.conf", "configs/rbac_policy.csv") // Admin routes require both authentication and admin role api.Route("/api/v1/admin", func(admin chi.Router) { admin.Use(middleware.Auth(jwtConfig)) admin.Use(middleware.RBAC(enforcer)) admin.Get("/users", httputil.Handle(adminController.ListUsers)) admin.Delete("/users/{id}", httputil.Handle(adminController.DeleteUser)) }) ``` The RBAC middleware uses the standard `func(http.Handler) http.Handler` signature and: 1. Reads the `role` from the request context (set by the auth middleware) 2. Checks the Casbin policy to see if the role can access the requested path with the given HTTP method 3. Returns 403 Forbidden if the policy denies access ### Role Hierarchy Define role inheritance in the policy file using `g` rules: ```csv g, admin, moderator g, moderator, user ``` This means `admin` inherits all permissions from `moderator`, which inherits from `user`. ### Dynamic Policies You can modify policies at runtime through the Casbin enforcer: ```go // Add a policy enforcer.AddPolicy("editor", "/api/v1/articles", "PUT") // Remove a policy enforcer.RemovePolicy("editor", "/api/v1/articles", "PUT") // Check a permission allowed, _ := enforcer.Enforce("user", "/api/v1/products", "GET") ``` ## Auth DTOs ```go // app/dtos/auth.dtos.go package dtos type RegisterRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` FirstName string `json:"first_name" validate:"required"` LastName string `json:"last_name" validate:"required"` } type LoginRequest struct { Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required"` } type RefreshRequest struct { RefreshToken string `json:"refresh_token" validate:"required"` } type AuthResponse struct { User *UserResponse `json:"user"` AccessToken string `json:"access_token"` RefreshToken string `json:"refresh_token"` } ``` ## Next Steps - [Build REST API endpoints](/docs/guides/rest-api) - [Configure your application](/docs/guides/configuration) - [Auth API reference](/docs/api-reference/auth) - [Middleware API reference](/docs/api-reference/middleware) ## Related --- ## /docs/guides/code-generation — Code Generation > Using the Gofasta CLI to generate models, services, controllers, scaffolds, and more. # Code Generation The Gofasta CLI includes a powerful code generation system that creates files following the project's layered architecture (controllers → services → repositories → models). Every generated file follows consistent conventions and integrates with the existing project structure. ## The Generate Command All generators are accessed through `gofasta generate` (or the shorthand `gofasta g`): ```bash gofasta g [field:type ...] ``` The `Name` argument is automatically converted to the correct casing for each context: `PascalCase` for Go types, `snake_case` for file names and database tables, and `camelCase` for JSON fields. ## Available Generators | Generator | Command | What it creates | |-----------|---------|----------------| | scaffold | `gofasta g scaffold` | All layers for a complete resource | | model | `gofasta g model` | Database model + migration | | service | `gofasta g service` | Service interface + implementation | | controller | `gofasta g controller` | REST controller | | repository | `gofasta g repository` | Repository interface + implementation | | dto | `gofasta g dto` | Request/response DTOs | | migration | `gofasta g migration` | Empty migration pair (up + down) | | route | `gofasta g route` | Route registration file | | resolver | `gofasta g resolver` | Regenerate GraphQL resolver stubs | | provider | `gofasta g provider` | Wire DI provider set | | job | `gofasta g job` | Cron job definition | | task | `gofasta g task` | Async task for the queue | | email-template | `gofasta g email-template` | HTML email template | ## Scaffold: The Full Resource Generator The scaffold command is the most commonly used generator. It creates every layer of a resource and wires it into the project: ```bash gofasta g scaffold Product name:string price:float description:text active:bool ``` ### Generated Files | File | Location | Description | |------|----------|-------------| | Model | `app/models/product.model.go` | GORM model with `BaseModelImpl` | | Up migration | `db/migrations/000006_create_products.up.sql` | CREATE TABLE statement | | Down migration | `db/migrations/000006_create_products.down.sql` | DROP TABLE statement | | Repository interface | `app/repositories/interfaces/product_repository.go` | CRUD method signatures | | Repository impl | `app/repositories/product.repository.go` | GORM implementation | | Service interface | `app/services/interfaces/product_service.go` | Business logic signatures | | Service impl | `app/services/product.service.go` | Business logic implementation | | DTOs | `app/dtos/product.dtos.go` | Create/Update request + response types | | Controller | `app/rest/controllers/product.controller.go` | HTTP handlers for CRUD | | Routes | `app/rest/routes/product.routes.go` | Route registrations | | Provider | `app/di/providers/product.go` | Wire provider set | ### Patched Files The scaffold command also modifies existing files to integrate the new resource: | File | What changes | |------|-------------| | `app/di/container.go` | Adds `ProductService` and `ProductController` fields | | `app/di/wire.go` | Adds `ProductSet` to the Wire build | | `app/rest/routes/index.routes.go` | Registers Product routes | | `cmd/serve.go` | Wires `ProductController` into the route config | After scaffolding, run `gofasta wire` to regenerate the Wire DI container, then `gofasta migrate up` to create the database table. ## Field Types Fields are specified as `name:type` pairs. The type determines the Go type, SQL column type, and GraphQL type: | Type | Go type | SQL type (Postgres) | SQL type (MySQL) | GraphQL type | |------|---------|-------------------|-----------------|-------------| | `string` | `string` | `VARCHAR(255)` | `VARCHAR(255)` | `String` | | `text` | `string` | `TEXT` | `TEXT` | `String` | | `int` | `int` | `INTEGER` | `INT` | `Int` | | `float` | `float64` | `DECIMAL(10,2)` | `DECIMAL(10,2)` | `Float` | | `bool` | `bool` | `BOOLEAN` | `TINYINT(1)` | `Boolean` | | `uuid` | `uuid.UUID` | `UUID` | `CHAR(36)` | `ID` | | `time` | `time.Time` | `TIMESTAMP` | `DATETIME` | `DateTime` | SQL types are automatically adapted for the database driver configured in `config.yaml`. ## Individual Generators ### Model Generates the model struct and migration files: ```bash gofasta g model Category name:string description:text ``` Creates: - `app/models/category.model.go` - `db/migrations/000007_create_categories.up.sql` - `db/migrations/000007_create_categories.down.sql` The model automatically embeds `models.BaseModelImpl` for standard fields. ### Service Generates the service interface and implementation: ```bash gofasta g service Notification ``` Creates: - `app/services/interfaces/notification_service.go` - `app/services/notification.service.go` The service implementation receives a repository through its constructor. ### Controller Generates an HTTP controller: ```bash gofasta g controller Payment ``` Creates: - `app/rest/controllers/payment.controller.go` The controller receives a service through its constructor and includes stub CRUD methods. ### Repository Generates the repository interface and GORM implementation: ```bash gofasta g repository Order ``` Creates: - `app/repositories/interfaces/order_repository.go` - `app/repositories/order.repository.go` ### DTO Generates request and response data transfer objects: ```bash gofasta g dto Invoice amount:float status:string due_date:time ``` Creates: - `app/dtos/invoice.dtos.go` ### Migration Generates an empty migration pair for custom SQL: ```bash gofasta g migration add_index_to_products ``` Creates: - `db/migrations/000008_add_index_to_products.up.sql` - `db/migrations/000008_add_index_to_products.down.sql` Both files are empty -- you write the SQL yourself. This is useful for schema changes that are not a simple table creation. ### Route Generates a route registration file: ```bash gofasta g route Subscription ``` Creates: - `app/rest/routes/subscription.routes.go` ### Resolver Regenerates GraphQL resolver stubs from schema files: ```bash gofasta g resolver ``` This runs gqlgen to regenerate resolvers. Existing implementations are preserved. ### Provider Generates a Wire dependency injection provider: ```bash gofasta g provider Analytics ``` Creates: - `app/di/providers/analytics.go` ### Job Generates a cron job definition: ```bash gofasta g job CleanupExpiredTokens ``` Creates: - `app/jobs/cleanup_expired_tokens.job.go` ### Task Generates an async task for the queue: ```bash gofasta g task SendWelcomeEmail ``` Creates: - `app/jobs/send_welcome_email.task.go` ### Email Template Generates an HTML email template: ```bash gofasta g email-template order-confirmation ``` Creates: - `templates/emails/order-confirmation.html` ## Customizing Generated Code All generated code is plain Go -- there are no runtime dependencies on the generator. After generation, you own the code and can modify it freely. Common customizations: - **Add validation rules** to DTOs using `validate` struct tags - **Add indexes** to models using `gorm` struct tags - **Add custom repository methods** beyond the standard CRUD - **Add business logic** to services - **Add custom endpoints** to controllers and routes ## Workflow Example A typical workflow for adding a new feature: ```bash # 1. Generate the full resource gofasta g scaffold Order total:float status:string user_id:uuid # 2. Regenerate Wire DI container gofasta wire # 3. Run migrations gofasta migrate up # 4. Customize the generated code # - Add validation to app/dtos/order.dtos.go # - Add business logic to app/services/order.service.go # - Add auth middleware to app/rest/routes/order.routes.go # 5. Test curl -X POST http://localhost:8080/api/v1/orders \ -H "Content-Type: application/json" \ -H "Authorization: Bearer " \ -d '{"total": 99.99, "status": "pending", "user_id": "..."}' ``` ## Next Steps - [Build REST endpoints](/docs/guides/rest-api) - [Set up GraphQL resolvers](/docs/guides/graphql) - [Manage database migrations](/docs/guides/database-and-migrations) - [CLI scaffold reference](/docs/cli-reference/generate/scaffold) ## Related --- ## /docs/guides/background-jobs — Background Jobs > Cron scheduling, async task queues, job definitions, and recurring jobs in Gofasta. # Background Jobs Gofasta provides two mechanisms for running work outside the request-response cycle: **cron jobs** for scheduled recurring tasks and **async task queues** for deferred one-off work. Both are backed by packages in the gofasta library (`pkg/scheduler` and `pkg/queue`). ## Overview | Mechanism | Use Case | Package | |-----------|----------|---------| | Cron jobs | Recurring scheduled work (cleanup, reports, syncs) | `github.com/gofastadev/gofasta/pkg/scheduler` | | Task queues | One-off deferred work (send email, process upload) | `github.com/gofastadev/gofasta/pkg/queue` | ## Cron Jobs Cron jobs run on a schedule defined using cron expressions. They live in `app/jobs/` and are registered with the scheduler at application startup. ### Defining a Job ```go // app/jobs/cleanup_expired_tokens.job.go package jobs import ( "context" "log/slog" "gorm.io/gorm" ) type CleanupExpiredTokensJob struct { db *gorm.DB } func NewCleanupExpiredTokensJob(db *gorm.DB) *CleanupExpiredTokensJob { return &CleanupExpiredTokensJob{db: db} } func (j *CleanupExpiredTokensJob) Name() string { return "cleanup_expired_tokens" } func (j *CleanupExpiredTokensJob) Schedule() string { return "0 */6 * * *" // Every 6 hours } func (j *CleanupExpiredTokensJob) Run(ctx context.Context) error { result := j.db.WithContext(ctx). Where("expires_at < NOW()"). Delete(&models.RefreshToken{}) if result.Error != nil { return result.Error } slog.Info("cleaned up expired tokens", "count", result.RowsAffected) return nil } ``` Each job implements three methods: - **`Name()`** -- a unique identifier for the job - **`Schedule()`** -- a cron expression defining when the job runs - **`Run(ctx)`** -- the work the job performs ### Generating a Job Use the CLI to generate a job skeleton: ```bash gofasta g job CleanupExpiredTokens ``` This creates `app/jobs/cleanup_expired_tokens.job.go` with the struct, constructor, and method stubs. ### Cron Expression Reference | Expression | Description | |-----------|-------------| | `* * * * *` | Every minute | | `*/5 * * * *` | Every 5 minutes | | `0 * * * *` | Every hour | | `0 */6 * * *` | Every 6 hours | | `0 0 * * *` | Daily at midnight | | `0 0 * * 0` | Weekly on Sunday at midnight | | `0 0 1 * *` | Monthly on the 1st at midnight | The format is: `minute hour day-of-month month day-of-week`. ### Registering Jobs Jobs are registered with the scheduler in `cmd/serve.go`: ```go import "github.com/gofastadev/gofasta/pkg/scheduler" func startServer(deps *di.Container) { // Create scheduler s := scheduler.New() // Register jobs s.Register(jobs.NewCleanupExpiredTokensJob(deps.DB)) s.Register(jobs.NewDailyReportJob(deps.ReportService)) s.Register(jobs.NewSyncInventoryJob(deps.InventoryService)) // Start the scheduler (runs in the background) s.Start() defer s.Stop() // Start the HTTP server // ... } ``` ### Scheduler Options Configure scheduler behavior: ```go s := scheduler.New( scheduler.WithLogger(logger), scheduler.WithRecovery(true), // Recover from panics in jobs scheduler.WithConcurrency(5), // Max concurrent jobs scheduler.WithTimezone("UTC"), // Timezone for cron expressions ) ``` ## Async Task Queues Task queues allow you to defer work to be processed asynchronously. This is useful for operations that should not block the HTTP response, such as sending emails, processing uploads, or calling external APIs. ### Queue Configuration Configure the queue backend in `config.yaml`: ```yaml queue: driver: redis # redis or memory redis: host: localhost port: 6379 db: 1 workers: 5 # Number of concurrent workers retry_attempts: 3 # Max retry attempts for failed tasks retry_delay: 30s # Delay between retries ``` The `memory` driver is useful for development and testing. Use `redis` in production for persistence and multi-instance support. ### Defining a Task ```go // app/jobs/send_welcome_email.task.go package jobs import ( "context" "encoding/json" "log/slog" "github.com/gofastadev/gofasta/pkg/mailer" ) type SendWelcomeEmailTask struct { mailer *mailer.Mailer } func NewSendWelcomeEmailTask(mailer *mailer.Mailer) *SendWelcomeEmailTask { return &SendWelcomeEmailTask{mailer: mailer} } func (t *SendWelcomeEmailTask) Name() string { return "send_welcome_email" } func (t *SendWelcomeEmailTask) Handle(ctx context.Context, payload []byte) error { var data struct { Email string `json:"email"` FirstName string `json:"first_name"` } if err := json.Unmarshal(payload, &data); err != nil { return err } err := t.mailer.Send(ctx, &mailer.Message{ To: []string{data.Email}, Subject: "Welcome to MyApp", Template: "welcome", Data: map[string]interface{}{ "FirstName": data.FirstName, }, }) if err != nil { slog.Error("failed to send welcome email", "email", data.Email, "error", err) return err } slog.Info("sent welcome email", "email", data.Email) return nil } ``` Each task implements: - **`Name()`** -- a unique identifier for the task type - **`Handle(ctx, payload)`** -- processes the task with the given JSON payload ### Generating a Task ```bash gofasta g task SendWelcomeEmail ``` This creates `app/jobs/send_welcome_email.task.go` with the struct and method stubs. ### Dispatching Tasks Enqueue tasks from anywhere in your application -- typically from services or controllers: ```go import "github.com/gofastadev/gofasta/pkg/queue" type AuthService struct { userRepo interfaces.UserRepository queue *queue.Queue } func (s *AuthService) Register(ctx context.Context, req *dtos.RegisterRequest) (*dtos.AuthResponse, error) { // ... create user ... // Dispatch welcome email task payload, _ := json.Marshal(map[string]string{ "email": user.Email, "first_name": user.FirstName, }) s.queue.Dispatch("send_welcome_email", payload) return response, nil } ``` ### Registering Task Handlers Register task handlers when starting the queue worker: ```go import "github.com/gofastadev/gofasta/pkg/queue" func startServer(deps *di.Container) { // Create queue q := queue.New(deps.Config.Queue) // Register task handlers q.RegisterHandler(jobs.NewSendWelcomeEmailTask(deps.Mailer)) q.RegisterHandler(jobs.NewProcessUploadTask(deps.Storage)) // Start processing tasks (runs in the background) q.Start() defer q.Stop() // Start the HTTP server // ... } ``` ### Task Options Control task behavior when dispatching: ```go // Delay execution by 5 minutes q.Dispatch("send_reminder", payload, queue.WithDelay(5*time.Minute)) // Set a custom retry count q.Dispatch("process_payment", payload, queue.WithRetries(5)) // Set a deadline q.Dispatch("generate_report", payload, queue.WithDeadline(time.Now().Add(1*time.Hour))) ``` ## Error Handling and Retries Both cron jobs and task queues support automatic retry on failure: - **Cron jobs** -- if a job returns an error, it is logged but does not affect the next scheduled run - **Task queues** -- failed tasks are retried up to `retry_attempts` times with `retry_delay` between attempts. After exhausting retries, the task is moved to a dead-letter queue for manual inspection ## Monitoring Jobs Use structured logging to monitor job execution: ```go func (j *DailyReportJob) Run(ctx context.Context) error { slog.Info("starting daily report generation") start := time.Now() // ... do work ... slog.Info("daily report completed", "duration", time.Since(start), "records_processed", count, ) return nil } ``` ## Next Steps - [Set up email sending](/docs/guides/email-and-notifications) - [Configure your application](/docs/guides/configuration) - [Scheduler API reference](/docs/api-reference/scheduler) - [Queue API reference](/docs/api-reference/queue) ## Related --- ## /docs/guides/email-and-notifications — Email & Notifications > Sending emails with SMTP, SendGrid, and Brevo, using HTML templates, and notification channels in Gofasta. # Email & Notifications Gofasta includes a mailer package that supports multiple email providers and HTML templates, plus a notifications package for multi-channel delivery. This guide covers configuration, template creation, and sending emails from your application. ## Email Configuration Email settings live in `config.yaml`: ```yaml mail: driver: smtp # smtp, sendgrid, or brevo from_name: MyApp from_address: noreply@myapp.com smtp: host: smtp.mailtrap.io port: 587 username: your-username password: your-password encryption: tls # tls, ssl, or none sendgrid: api_key: SG.xxxxxxxxxxxx brevo: api_key: xkeysib-xxxxxxxxxxxx ``` Override in production with environment variables: ```bash GOFASTA_MAIL_DRIVER=sendgrid GOFASTA_MAIL_SENDGRID_API_KEY=SG.production-key ``` ## Supported Providers | Provider | `mail.driver` | Best For | |----------|-------------|----------| | SMTP | `smtp` | Development (Mailtrap), self-hosted mail servers | | SendGrid | `sendgrid` | Production transactional email at scale | | Brevo | `brevo` | Transactional + marketing email combined | Switch providers by changing the `mail.driver` value. The mailer interface is the same regardless of provider. ## Sending Emails The `github.com/gofastadev/gofasta/pkg/mailer` package provides the `Mailer` struct for sending emails. ### Basic Email ```go import "github.com/gofastadev/gofasta/pkg/mailer" func (s *OrderService) SendConfirmation(ctx context.Context, order *models.Order) error { return s.mailer.Send(ctx, &mailer.Message{ To: []string{order.User.Email}, Subject: "Order Confirmation #" + order.Number, Template: "order-confirmation", Data: map[string]interface{}{ "OrderNumber": order.Number, "Total": order.Total, "Items": order.Items, "CustomerName": order.User.FirstName, }, }) } ``` ### Message Options The `mailer.Message` struct supports these fields: | Field | Type | Description | |-------|------|-------------| | `To` | `[]string` | Recipient email addresses | | `CC` | `[]string` | Carbon copy recipients | | `BCC` | `[]string` | Blind carbon copy recipients | | `Subject` | `string` | Email subject line | | `Template` | `string` | Name of the HTML template (without extension) | | `Data` | `map[string]interface{}` | Template variables | | `Body` | `string` | Plain text body (used if no template) | | `HTMLBody` | `string` | Raw HTML body (used if no template) | | `Attachments` | `[]mailer.Attachment` | File attachments | | `ReplyTo` | `string` | Reply-to address | ### Sending with Attachments ```go err := s.mailer.Send(ctx, &mailer.Message{ To: []string{"user@example.com"}, Subject: "Your Invoice", Template: "invoice", Data: templateData, Attachments: []mailer.Attachment{ { Filename: "invoice.pdf", Content: pdfBytes, MIMEType: "application/pdf", }, }, }) ``` ## HTML Email Templates Email templates live in `templates/emails/` and use Go's `html/template` syntax. ### Template Structure ```html

Order Confirmation

Hi {{.CustomerName}},

Thank you for your order #{{.OrderNumber}}.

{{range .Items}} {{end}}
Item Qty Price
{{.Name}} {{.Quantity}} ${{.Price}}

Total: ${{.Total}}

``` ### Generating Templates Use the CLI to generate a template skeleton: ```bash gofasta g email-template order-confirmation ``` This creates `templates/emails/order-confirmation.html` with a basic responsive layout. ### Template Variables Templates receive data through the `Data` field of `mailer.Message`. Access variables with `{{.VariableName}}`: ```go s.mailer.Send(ctx, &mailer.Message{ To: []string{user.Email}, Subject: "Password Reset", Template: "password-reset", Data: map[string]interface{}{ "UserName": user.FirstName, "ResetLink": resetURL, "ExpiresIn": "1 hour", }, }) ``` ```html

Hi {{.UserName}},

Click the link below to reset your password:

Reset Password

This link expires in {{.ExpiresIn}}.

``` ## Async Email Sending For production workloads, send emails through the task queue to avoid blocking HTTP responses: ```go // Dispatch to queue instead of sending directly payload, _ := json.Marshal(map[string]interface{}{ "to": user.Email, "subject": "Welcome to MyApp", "template": "welcome", "data": map[string]string{ "FirstName": user.FirstName, }, }) s.queue.Dispatch("send_email", payload) ``` See the [Background Jobs guide](/docs/guides/background-jobs) for details on setting up task queues. ## Notifications The `github.com/gofastadev/gofasta/pkg/notify` package provides a multi-channel notification system. A single `Notifier` fans out one `Notification` to every registered channel that the caller targets. ### Available Channels | Channel | Constructor | Backend | |---------|-------------|---------| | `notify.ChannelEmail` | `notify.NewEmailChannel(sender)` | Uses your existing `mailer.EmailSender` (SMTP, SendGrid, or Brevo) | | `notify.ChannelSMS` | `notify.NewSMSChannel(accountSID, authToken, fromNumber)` | Twilio | | `notify.ChannelSlack` | `notify.NewSlackChannel(webhookURL)` | Slack incoming webhooks | | `notify.ChannelDatabase` | `notify.NewDatabaseChannel(db)` | GORM — persists to a `notifications` table for in-app centers | ### Building the Notifier ```go import ( "log/slog" "github.com/gofastadev/gofasta/pkg/mailer" "github.com/gofastadev/gofasta/pkg/notify" ) emailCh := notify.NewEmailChannel(smtpSender) // any mailer.EmailSender slackCh := notify.NewSlackChannel("https://hooks.slack.com/services/xxx/yyy/zzz") dbCh := notify.NewDatabaseChannel(db) // *gorm.DB notifier := notify.NewNotifier(slog.Default(), emailCh, slackCh, dbCh) ``` ### Sending a Notification ```go err := notifier.Send(ctx, notify.Recipient{ ID: user.ID.String(), Email: user.Email, Name: user.Name, }, notify.Notification{ Subject: "Your order has shipped", Body: "Order #" + order.Number + " is on its way.", Template: "order-shipped", // email channel renders this template with Data Data: map[string]any{ "OrderNumber": order.Number, "TrackingCode": order.TrackingCode, }, Channels: []notify.Channel{notify.ChannelEmail, notify.ChannelDatabase}, }, ) ``` Leave `Channels` empty to fan out to every registered channel. ### Custom Channels Implement `ChannelSender` (`Channel()` + `Send()`) and register it with the notifier: ```go notifier.RegisterChannel(myCustomChannel) ``` See the [Notifications API reference](/docs/api-reference/notifications) for the full interface definition. ## Development Tips - Use [Mailtrap](https://mailtrap.io/) as your SMTP provider during development to catch all outgoing emails without delivering them - Set `mail.driver: smtp` with Mailtrap credentials in your development `config.yaml` - Preview email templates by rendering them locally before sending ## Next Steps - [Set up background jobs for async emails](/docs/guides/background-jobs) - [Configure your application](/docs/guides/configuration) - [Mailer API reference](/docs/api-reference/mailer) - [Notifications API reference](/docs/api-reference/notifications) ## Related --- ## /docs/guides/testing — Testing > Test setup, testcontainers-backed integration tests, mocking, and the testutil/testdb helper. # Testing Gofasta projects use Go's built-in `testing` package alongside the `github.com/gofastadev/gofasta/pkg/testutil/testdb` helper, which spins up a real PostgreSQL container via [testcontainers-go](https://github.com/testcontainers/testcontainers-go) for integration tests. For unit tests, mock your repository interfaces with hand-written stubs — Go interfaces make this trivial and no mocking library is required. ## Project Test Structure Tests follow Go conventions and live alongside the code they test: ``` app/ ├── services/ │ ├── product.service.go │ └── product.service_test.go ├── rest/controllers/ │ ├── product.controller.go │ └── product.controller_test.go ├── repositories/ │ ├── product.repository.go │ └── product.repository_test.go ``` Run all tests with: ```bash go test ./... ``` Or use the Makefile shortcut: ```bash make test ``` ## The `testutil/testdb` Package `github.com/gofastadev/gofasta/pkg/testutil/testdb` exposes one primary helper: ```go func SetupTestDB(t *testing.T) *gorm.DB ``` It starts a fresh PostgreSQL container, runs the gofasta library's base migrations (the `citext` extension and the shared trigger functions), and returns a ready-to-use `*gorm.DB`. The container is automatically torn down at the end of the test via `t.Cleanup`, so every test gets an isolated database. **Requirements:** Docker must be running on the machine executing the tests. The first call pulls the `postgres:16-alpine` image. ### Basic Integration Test ```go package repositories_test import ( "context" "testing" "github.com/gofastadev/gofasta/pkg/testutil/testdb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "myapp/app/models" "myapp/app/repositories" ) func TestProductRepository_Create(t *testing.T) { if testing.Short() { t.Skip("skipping integration test (requires Docker)") } db := testdb.SetupTestDB(t) require.NoError(t, db.AutoMigrate(&models.Product{})) repo := repositories.NewProductRepository(db) product := &models.Product{Name: "Widget", Price: 9.99} err := repo.Create(context.Background(), product) require.NoError(t, err) assert.NotEmpty(t, product.ID) // BaseModelImpl auto-populates the UUID } ``` ### Splitting Unit and Integration Tests Use the `testing.Short()` flag to skip container-backed tests when you want a fast unit-only run: ```bash # Fast unit-only run — no Docker required go test -short ./... # Full run including container-backed integration tests go test ./... ``` ## Unit Testing Services Services contain business logic and are the most important layer to test. They should be tested **without** hitting the database — mock the repository interface and exercise the service's logic in isolation. ### Hand-Written Repository Mocks Gofasta generates interface-based repositories, so you can mock them with a simple struct holding function fields. No mock-generation tool is required. ```go // app/repositories/mocks/product_repository_mock.go package mocks import ( "context" "myapp/app/models" ) type MockProductRepository struct { CreateFn func(ctx context.Context, product *models.Product) error FindAllFn func(ctx context.Context, page, perPage int) ([]models.Product, int64, error) FindByIDFn func(ctx context.Context, id string) (*models.Product, error) UpdateFn func(ctx context.Context, product *models.Product) error DeleteFn func(ctx context.Context, id string) error } func (m *MockProductRepository) Create(ctx context.Context, p *models.Product) error { return m.CreateFn(ctx, p) } func (m *MockProductRepository) FindAll(ctx context.Context, page, perPage int) ([]models.Product, int64, error) { return m.FindAllFn(ctx, page, perPage) } func (m *MockProductRepository) FindByID(ctx context.Context, id string) (*models.Product, error) { return m.FindByIDFn(ctx, id) } func (m *MockProductRepository) Update(ctx context.Context, p *models.Product) error { return m.UpdateFn(ctx, p) } func (m *MockProductRepository) Delete(ctx context.Context, id string) error { return m.DeleteFn(ctx, id) } ``` ### Writing Service Tests ```go package services_test import ( "context" "testing" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "myapp/app/dtos" "myapp/app/models" "myapp/app/repositories/mocks" "myapp/app/services" ) func TestProductService_Create(t *testing.T) { mockRepo := &mocks.MockProductRepository{ CreateFn: func(ctx context.Context, p *models.Product) error { p.ID = uuid.New() return nil }, } svc := services.NewProductService(mockRepo) result, err := svc.Create(context.Background(), &dtos.CreateProductRequest{ Name: "Widget", Price: 9.99, }) require.NoError(t, err) assert.Equal(t, "Widget", result.Name) assert.InDelta(t, 9.99, result.Price, 0.0001) } func TestProductService_Create_InvalidPrice(t *testing.T) { svc := services.NewProductService(&mocks.MockProductRepository{}) _, err := svc.Create(context.Background(), &dtos.CreateProductRequest{ Name: "Widget", Price: -5.00, }) assert.Error(t, err) } ``` Because the mock repository is a plain struct, each test only has to set the function fields it actually cares about. Other methods will panic on a nil call — which is exactly the signal you want if a test exercises code it shouldn't. ## Testing Controllers Controllers are standard `net/http` handlers, so test them with the standard library's `net/http/httptest`. There is no special testing router; use the real `chi.Router` and `httputil.Handle` wrapper. ```go package controllers_test import ( "bytes" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/go-chi/chi/v5" "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gofastadev/gofasta/pkg/httputil" "myapp/app/models" "myapp/app/repositories/mocks" "myapp/app/rest/controllers" "myapp/app/services" ) func TestProductController_Create(t *testing.T) { mockRepo := &mocks.MockProductRepository{ CreateFn: func(ctx context.Context, p *models.Product) error { p.ID = uuid.New() return nil }, } svc := services.NewProductService(mockRepo) ctrl := controllers.NewProductController(svc) router := chi.NewRouter() router.Post("/products", httputil.Handle(ctrl.Create)) body, _ := json.Marshal(map[string]any{"name": "Widget", "price": 9.99}) req := httptest.NewRequest(http.MethodPost, "/products", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() router.ServeHTTP(rec, req) require.Equal(t, http.StatusCreated, rec.Code) assert.Contains(t, rec.Body.String(), `"name":"Widget"`) } ``` ## Testing Repositories Repository tests use a **real** PostgreSQL database via `testdb.SetupTestDB` — GORM's query behavior differs subtly between drivers, so testing against Postgres directly catches dialect issues early. Guard these tests with `testing.Short()` so they can be skipped on a fast local run: ```go package repositories_test import ( "context" "testing" "github.com/gofastadev/gofasta/pkg/testutil/testdb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "myapp/app/models" "myapp/app/repositories" ) func TestProductRepository_FindByID(t *testing.T) { if testing.Short() { t.Skip("skipping integration test (requires Docker)") } db := testdb.SetupTestDB(t) require.NoError(t, db.AutoMigrate(&models.Product{})) repo := repositories.NewProductRepository(db) // Seed seed := &models.Product{Name: "Widget", Price: 9.99} require.NoError(t, db.Create(seed).Error) // Exercise found, err := repo.FindByID(context.Background(), seed.ID.String()) require.NoError(t, err) assert.Equal(t, "Widget", found.Name) } ``` ## Testing Protected Endpoints Generate a JWT for the test user using the real `pkg/auth` package and attach it to the request as a `Bearer` token. ```go import "github.com/gofastadev/gofasta/pkg/auth" func TestProductController_Create_Authenticated(t *testing.T) { jwtSvc := auth.NewJWTService(&config.AuthConfig{ JWTSecret: "test-secret-at-least-32-bytes!!", AccessTokenExpiry: time.Hour, }) token, err := jwtSvc.GenerateToken("user-123", "admin") require.NoError(t, err) // ...build router + request as usual... req.Header.Set("Authorization", "Bearer "+token) } ``` For an unauthenticated request, simply omit the header and assert you get `401 Unauthorized`. ## Running Tests ```bash # Run all tests (includes container-backed integration tests — requires Docker) go test ./... # Fast unit-only run (skips anything guarded by testing.Short()) go test -short ./... # Verbose output go test -v ./... # A specific package go test ./app/services/... # A specific test function go test -run TestProductService_Create ./app/services/... # Coverage go test -cover ./... go test -coverprofile=coverage.out ./... go tool cover -html=coverage.out ``` ## Next Steps - [Deploy your application](/docs/guides/deployment) - [Configure your application](/docs/guides/configuration) - [Test Utilities API reference](/docs/api-reference/test-utilities) ## Related --- ## /docs/guides/debugging/overview — Overview > The local dev dashboard — trace waterfalls, per-request logs, N+1 detection, EXPLAIN plans, pprof, replay, and the zero-cost build-tag model behind them. # Debugging in gofasta projects gofasta generates projects with a first-class local debugging surface built on top of idiomatic Go primitives — `net/http/pprof`, OpenTelemetry, GORM callbacks, and `slog`. One command (`gofasta dev --dashboard`) stands up a browser-based inspector that lets you watch every request flow through your code: traces, spans, stack snapshots, SQL, cache ops, logs, panics, replay. Everything below is gated behind the `devtools` build tag. `gofasta dev` sets it automatically via `GOFLAGS`; `go build` without it compiles a stub (pass-through middleware, no-op plugins, no endpoints exposed) — your production binaries carry zero debug surface. ## Start the dashboard ```bash gofasta dev --dashboard ``` Two things happen: 1. **Your app builds with `-tags devtools`**. The `app/devtools` package swaps from the `//go:build !devtools` stub to the real implementation: middleware that captures request metadata, a GORM plugin that records every query, an OpenTelemetry `SpanProcessor` that buffers completed traces, a `slog.Handler` that tees log records into a ring, a cache decorator, a panic-capture wrapper, and `net/http/pprof`. 2. **The CLI starts a tiny HTTP server on `:9090`**. It scrapes the `/debug/*` endpoints your app now exposes and serves a live HTML dashboard backed by Server-Sent Events. The dashboard dies cleanly on `Ctrl+C` along with the rest of the `gofasta dev` pipeline. Use `--dashboard-port` to change the port and `--no-services` / `--no-migrate` / etc. to opt out of specific stages (see `gofasta dev --help`). ## What's in this guide - **[Guided Tour](/docs/guides/debugging/guided-tour)** — walk through a realistic debugging session: slow endpoint → trace waterfall → stack → logs → SQL pattern → fix → replay. - **[Dashboard Panels](/docs/guides/debugging/dashboard-panels)** — reference for every section shown on the dashboard (App cards, Metrics, Profiles, Goroutines, Routes, Services, N+1 findings, Recent Requests, Recent SQL, Trace waterfall, Exceptions, Cache ops). - **[Tracing & Waterfalls](/docs/guides/debugging/tracing)** — what's auto-traced, how to add spans to existing code, wiring SQL as waterfall bars, tracing async tasks and cron jobs. - **[How It Works](/docs/guides/debugging/architecture)** — the Wire + middleware + OTel wiring that gives you visibility without dirtying your business code, plus the endpoint reference and the production-safety build-tag model. - **[Customization](/docs/guides/debugging/customization)** — ring sizes (and how rotation works), disabling individual hooks, where the devtools code lives in your project, library coupling points. ## Related - [`gofasta dev`](/docs/cli-reference/dev) — full flag + event reference for the dev pipeline - [Testing guide](/docs/guides/testing) — how the same trace + ring infrastructure makes integration tests observable - [`pkg/observability`](/docs/api-reference/observability) — the Prometheus + OpenTelemetry primitives the devtools package hooks into --- ## /docs/guides/debugging/guided-tour — Guided Tour > Walk through a realistic debug session using the dashboard — slow endpoint → trace drill-down → stack → logs → SQL pattern → fix → replay. # Guided tour — debugging a slow endpoint The best way to understand what the dashboard gives you is to walk through a realistic debug session. Suppose a developer notices `POST /api/v1/orders` is consistently taking ~600ms when it should be under 50ms. ## Step 1 — spot the slow request The **Recent requests** panel shows the last 200 requests with method, path, status, duration, and a trace ID. The slow `POST /api/v1/orders` is at the top of the list with a 612ms duration and a yellow pill. Every row also has a **Replay** button and a **trace** CTA. Click the trace CTA to open the trace detail card. ## Step 2 — see where time went The trace detail card opens below the list with the root span (`HTTP POST /api/v1/orders`), duration (612ms), and span count (23). The waterfall: ``` HTTP POST /api/v1/orders ████████████████████████████ 612 ms └─ OrderController.Create ██████████████████████████ 598 ms └─ OrderService.Create █████████████████████████ 584 ms └─ OrderRepository.Create ███ 34 ms └─ INSERT INTO orders ██ 28 ms └─ UserRepository.FindByID █ 6 ms └─ UserRepository.FindByID █ 6 ms └─ UserRepository.FindByID █ 6 ms └─ UserRepository.FindByID █ 6 ms ... (40 more times) ``` The insert is fine; the service is doing something else for ~560ms. And there's a whole column of identical `UserRepository.FindByID` calls. ## Step 3 — confirm the pattern Scroll up to the **N+1 findings** panel. The dashboard already flagged it: | Count | Template | Trace | |-------|---------------------------------------|----------| | 44× | `SELECT * FROM users WHERE id = ?` | `a7f3c8…`| The detector normalizes SQL (literals collapsed to `?`), groups by `(trace_id, template)`, and flags any pair with three or more hits. 44 hits of the same query in one request is a textbook N+1. ## Step 4 — see what was logged Back in the trace detail card, switch to the **Logs** tab. Every `slog.Info/Warn/Error` call made during this request is listed, with level, message, and structured attributes — pulled from the log ring by trace ID: ``` 15:34:12.104 INFO order created order_id=ord_8f3... user_id=u42 15:34:12.108 DEBUG user lookup cache miss user_id=u42 15:34:12.110 DEBUG user lookup cache miss user_id=u43 ... (42 more) ``` So not only are there 44 duplicate queries — there's no caching layer between the service and the repository. ## Step 5 — look at the offending query Click any span in the waterfall to see its call-stack snapshot — captured at span start via `runtime.Callers`, 20 frames deep: ``` app/services/order.service.go:87 (*OrderService).Create app/controllers/order.controller.go:91 (*OrderController).Create app/rest/routes/order.go:28 InitOrderRoutes.func1 ``` Line 87 of `order.service.go` is where the offending loop lives. ## Step 6 — fix, verify, replay Edit the service to batch the lookups (`UserRepository.FindManyByID(ids)`), save, Air rebuilds. Back on the dashboard, click the **Replay** button on the original slow request row. A small editor opens prefilled with the captured request body — confirm, send. The new trace appears at the top of the list with a 48ms duration. The N+1 findings panel is empty. Ship it. --- That's the end-to-end loop: **visible slow request → trace drill-down → stack → logs → SQL pattern → fix → replay to verify**. No log greps, no `dlv`, no copy-pasting request bodies between curl and IDE. ## Next - [Dashboard Panels](/docs/guides/debugging/dashboard-panels) — full reference for every panel you saw above. - [Tracing & Waterfalls](/docs/guides/debugging/tracing) — how to get a waterfall that rich in your own code. ## Related --- ## /docs/guides/debugging/dashboard-panels — Dashboard Panels > Reference for every panel on the dev dashboard — what it shows, where the data comes from, and any interactive affordances. # Every debug surface Each panel below is live in the dashboard whenever the app runs with the `devtools` tag. Panels fail soft: if a single endpoint is unreachable, the others keep rendering. ## App cards Six cards at the top of the dashboard: **Health** (polled against `/health` every 5s), **Port**, **Swagger URL** (if `docs/swagger.json` exists), **GraphQL URL** (if `gqlgen.yml` exists), and **Queue URL** (if an asynqmon compose service is running). ## Metrics Parses the Prometheus text output from `/metrics` (emitted by `pkg/observability`) and surfaces: total requests served, in-flight count, and average latency. Refreshes every 5s via SSE. ## Profiles One-click links to every profile `net/http/pprof` exposes: CPU (30s capture), heap, goroutine, mutex, block, allocs, threadcreate, execution trace (5s), and the pprof index. Each opens in a new tab — point `go tool pprof` at the URL, or use the built-in HTML UI. ## Goroutines Parses `/debug/pprof/goroutine?debug=2`, groups goroutines by top-of-stack function, and sorts descending by count. A spike in `net/http.(*conn).serve` is normal under load; a spike in your own code usually means a leak. ## Routes Scraped from `docs/swagger.json`. Each row shows method, path, operation summary, **request type**, and **response type** — extracted from the OpenAPI 2.0 `parameters[in=body].schema` or OpenAPI 3.0 `requestBody.content.schema`, with the primary 2xx response picked automatically. ## Services Runtime state of every compose service attached to this dev session — queried via `docker compose ps --format json` every 5s. Shows name + health/state as colored pills. ## N+1 findings Grouping by `(trace_id, normalized_template)` across the SQL ring. Templates are normalized by collapsing string / numeric literals to `?` and compressing whitespace. Any group with three or more hits surfaces as a finding, sorted by count descending. ## Recent requests The last 200 requests captured by the devtools middleware (ring buffer — oldest request is overwritten when the cap is hit, see [Ring sizes](/docs/guides/debugging/customization#ring-sizes) to change the cap). Each row: - **Time** — wall clock - **Method** — colored badge (GET = cyan, POST = green, PATCH = amber, DELETE = red) - **Path** — URL path - **Status** — pill (2xx green, 3xx cyan, 4xx amber, 5xx red) - **Duration** — wall-clock milliseconds - **Trace** — CTA button that opens the trace detail card below the Traces list - **Replay** — opens the inline editor An **Export HAR** button in the header downloads the current ring as HAR 1.2 JSON for Chrome DevTools, Insomnia, or any HAR-aware viewer. ## Recent SQL Every statement captured by the GORM callback, with wall-clock duration, rows affected, error (if any), and the trace ID that triggered it. Each SELECT row also has an **EXPLAIN** button. ## Traces & trace waterfall The trace ring holds the most recent **50 traces** — older ones are rotated out when new traces arrive (see [Ring sizes](/docs/guides/debugging/customization#ring-sizes) if 50 is too tight). Clicking a row (or the **trace** CTA in a request row) pins that trace and opens a detail card below the list. The card fetches `/debug/traces/{id}` on demand (the summary list endpoint returns traces without spans to keep SSE payloads cheap) and renders: - A waterfall of nested spans — controller → service → repository → SQL — with bar widths proportional to duration and offsets relative to the root span's start - The span's **kind** (`server`, `internal`, `client`) next to its name - A click-to-expand **stack** beside each span name showing up to 20 call frames captured at span start via `runtime.Callers` - **OTel events** attached to the span (error markers, custom annotations) Two tabs inside the card: - **Waterfall** (default) — the span tree - **Logs** — on-demand fetch of every `slog` record emitted during this request (pulled from `/debug/logs?trace_id=…`) The detail card lives in its own DOM surface, so 5s SSE refreshes don't collapse whatever waterfall you're reading. A pinned-row banner above the Traces list keeps the inspected trace visible even if its row rotates out of view. See [Tracing & Waterfalls](/docs/guides/debugging/tracing) for how to get a multi-bar waterfall in your own code — and how to turn SQL into waterfall bars. ## Exceptions `devtools.Recovery` wraps `pkg/middleware`'s recovery middleware. Every panic is recorded with the recovered value, a 20-frame stack, method + path of the offending request, and the trace ID. The table shows all recoveries this session with their stacks rendered as expandable `
` blocks.

## Cache ops

`devtools.WrapCache` decorates `cache.CacheService`. Every `Get`/`Set`/`Delete`/`Flush`/`Ping` records an entry with op, key, hit/miss flag (for `Get`), duration, error, and trace ID. Per-trace aggregation answers *"did this page actually use the cache?"* at a glance.

## Related



---

## /docs/guides/debugging/tracing — Tracing & Waterfalls

> What's auto-traced, how to add spans to existing code, wiring SQL as waterfall bars, and instrumenting async tasks and cron jobs.

# Building a richer waterfall

When you click a request's **trace** button and the detail card shows only one span, it's because nothing *in your code* opened child spans for that request. The dashboard faithfully renders whatever the OpenTelemetry tracer provider saw — if the middleware was the only thing that `Start`ed a span, you get one bar. Here's the full map of what's instrumented by default and where to add spans yourself.

## What's traced automatically

Given `observability.tracing_enabled: true` in `config.yaml`, these boundaries create spans without you writing a single line:

| Layer | Span source | Appears in waterfall? |
|---|---|---|
| HTTP request (root span) | `pkg/observability.TracingMiddleware` in the middleware chain | ✅ always |
| Service methods (generated) | `otel.Tracer(...).Start` calls emitted by `gofasta g scaffold` / `g service` | ✅ for scaffolded resources |
| Repository methods (generated) | `otel.Tracer(...).Start` calls emitted by `gofasta g scaffold` / `g repository` | ✅ for scaffolded resources |
| Built-in `UserService` / `UserRepository` | Instrumented the same way as generated code | ✅ |
| SQL queries | Captured by `devtools.GormPlugin()` into the SQL ring | ❌ (see below) |
| Async tasks (`asynq`), cron jobs | No automatic instrumentation | ❌ (see below) |

**So `/api/v1/users` (built-in) and any route produced by `gofasta g scaffold ` already give you a multi-bar waterfall.** A brand-new route you wrote by hand in an un-instrumented service won't, until you add the calls yourself.

## Adding spans to existing code

The pattern is three lines, repeated per method:

```go
import "go.opentelemetry.io/otel"

const orderServiceTracerName = "yourproject/app/services/order"

func (s *OrderService) PlaceOrder(ctx context.Context, input dtos.TPlaceOrderDto) (*dtos.TOrderResponseDto, error) {
    ctx, span := otel.Tracer(orderServiceTracerName).Start(ctx, "OrderService.PlaceOrder")
    defer span.End()

    // ... existing body, unchanged.
    // Pass `ctx` (the shadowed one) to downstream calls so their
    // spans nest under this one.
    if err := s.OrderRepo.Insert(ctx, input); err != nil {
        span.RecordError(err) // attach error to the span for the dashboard
        return nil, err
    }
    return &dtos.TOrderResponseDto{...}, nil
}
```

Three rules to remember:

1. **Shadow `ctx`.** The `ctx, span := ...` assignment returns a new context carrying the span. If you keep using the outer `ctx` in downstream calls, the children will attach to the parent span of *this* function, not to this function's own span — the waterfall won't nest correctly.
2. **`defer span.End()` immediately.** Anything short of this — forgetting to call `End`, calling it only in the happy path, wrapping in a conditional — leaves unterminated spans that never appear in the trace.
3. **Call `span.RecordError(err)` on every error return.** The dashboard marks errored spans red in the waterfall; without this call, a failed call visually looks identical to a successful one.

The tracer name (`orderServiceTracerName`) should be stable per component — use the module path + package path convention (`yourproject/app/services/order`) so spans group by subsystem in the "Instrumentation scope" attribute.

## Getting SQL queries as waterfall spans

By default, SQL is captured but *not* turned into OTel spans. This is a deliberate separation:

- **`devtools.GormPlugin()`** pushes every query into the in-memory SQL ring with its trace ID attached. That's what feeds the **Recent SQL** panel and the Logs tab of the trace card.
- **OTel spans for SQL** require a separate bridge plugin — `devtools.GormPlugin()` does not create them.

To get SQL as nested bars under your repository spans, add the GORM OpenTelemetry plugin alongside the devtools plugin. In `app/di/providers/core.go`, next to the existing `db.Use(devtools.GormPlugin())` call:

```go
import "gorm.io/plugin/opentelemetry/tracing"

// ... inside the DB provider, after opening the connection:
if err := db.Use(tracing.NewPlugin()); err != nil {
    return nil, err
}
if err := db.Use(devtools.GormPlugin()); err != nil {
    return nil, err
}
```

Add the dependency:

```bash
go get gorm.io/plugin/opentelemetry
```

After a restart, every query fires its own span; the waterfall shows `controller → service → repository → SQL` with real duration bars. The two plugins are complementary: the GORM OTel plugin adds the span; the devtools plugin adds the row in the SQL panel with its vars for `EXPLAIN`. If you only want one, keep devtools — you'll lose the waterfall bars but still have the SQL panel.

## Tracing async tasks and cron jobs

Tasks dispatched through `pkg/queue` (asynq) and jobs started by `pkg/scheduler` (robfig/cron) run outside any HTTP request, so there's no root span to inherit from. Open one yourself at the entry point:

```go
func (h *EmailTaskHandler) Handle(ctx context.Context, task *asynq.Task) error {
    ctx, span := otel.Tracer("yourproject/app/tasks").Start(ctx, "tasks.SendWelcomeEmail")
    defer span.End()

    // ... handler body
}
```

These traces appear in the **Traces** list alongside HTTP traces. The Recent Requests list, being HTTP-only, doesn't show them — so the Traces table is how you find them. This is the single reason the Traces list isn't redundant with Recent Requests.

## Verifying you've done it right

After adding spans, hit the route or fire the task, open the **Traces** list, click the new trace's **trace** button, and look at the waterfall:

- **One bar** → only the middleware span fired. Your `Start` calls either aren't running (is the code path reached?) or are using a context that didn't flow through OTel (`context.Background()` instead of the inherited `ctx`).
- **Multiple bars, but all starting at offset 0** → you're creating sibling spans instead of children. Double-check you're shadowing `ctx` (step 1 above) and passing the shadowed one downstream.
- **Multiple bars, properly nested** → done. The `Logs` tab shows only the records emitted under these spans, filtered by trace ID automatically.

## Related



---

## /docs/guides/debugging/architecture — How It Works

> The Wire + middleware + OTel wiring that gives you deep visibility without dirtying business code, plus the endpoint reference and the production-safety build-tag model.

# How it works without dirtying your code

The dashboard gives you deep visibility *without* asking you to add observability code to your controllers, services, or repositories. Every hook lives in `app/devtools` (the scaffold package) and is wired through Wire DI — your business code stays unchanged.

Here's the actual wiring map.

## 1. HTTP middleware (`cmd/serve.go`)

```go
middlewares := []middleware.Middleware{
    middleware.RequestID(),
    middleware.RequestLogging(logger),
    // Devtools Recovery wraps pkg/middleware.Recovery. In devtools
    // builds it records the panic; in production it delegates.
    middleware.Middleware(devtools.Recovery(middleware.Recovery(logger))),
    middleware.CORS(cfg.Server.AllowedOrigins),
    middleware.SecurityHeaders(cfg.Security),
    // Request capture — pass-through no-op in production.
    middleware.Middleware(devtools.Middleware),
}
```

The `devtools.Middleware` captures: method, path, status, duration, request body (capped at 64 KiB), response body (same cap), and extracts the trace ID from the context.

## 2. Debug endpoints (`cmd/serve.go`)

```go
mux.Handle("/debug/", devtools.Handler())
```

Serves `/debug/requests`, `/debug/sql`, `/debug/traces`, `/debug/logs`, `/debug/errors`, `/debug/cache`, `/debug/explain`, `/debug/health`, and `/debug/pprof/*`.

## 3. OpenTelemetry span processor (`cmd/serve.go`)

```go
if cfg.Observability.TracingEnabled {
    shutdown := observability.InitTracer(cfg.Observability.ServiceName)
    defer shutdown()
    devtools.RegisterTraceProcessor()
}
```

Hooks a `sdktrace.SpanProcessor` into the global tracer provider. Every span captures a 20-frame call stack at `OnStart`; completed traces flush into a 50-entry ring keyed by trace ID.

## 4. GORM plugin (`app/di/providers/core.go`)

```go
func ProvideDB(cfg *config.DatabaseConfig) *gorm.DB {
    db := config.SetupDB(cfg)
    _ = db.Use(devtools.GormPlugin())
    return db
}
```

Registers before/after callbacks on every op. Captures SQL, vars, rows affected, error, duration, and trace ID into a 200-entry ring.

## 5. slog handler wrapper (`app/di/providers/core.go`)

```go
func ProvideLogger(cfg *config.LogConfig) *slog.Logger {
    base := logger.NewLogger(cfg)
    wrapped := slog.New(devtools.WrapLogger(base.Handler()))
    slog.SetDefault(wrapped)
    return wrapped
}
```

Tees every log record into a 500-entry ring keyed by the trace ID extracted from the record's context.

## 6. Cache decorator (`app/di/providers/core.go`)

```go
func ProvideCacheService(cfg *config.CacheConfig, log *slog.Logger) (cache.CacheService, error) {
    inner, err := cache.NewCacheService(cfg, log)
    if err != nil {
        return nil, err
    }
    return devtools.WrapCache(inner), nil
}
```

Decorates the cache with a per-op recorder.

## 7. EXPLAIN database handle (`cmd/serve.go`)

```go
devtools.RegisterDB(container.DB)
```

Stashes the GORM handle so `/debug/explain` can run ad-hoc `EXPLAIN` queries against captured SQL (SELECT-only whitelist).

## 8. Auto-instrumented service/repository layers

The scaffold's `gofasta g scaffold` and `gofasta g service`/`g repository` generators emit:

```go
func (s *OrderService) Create(ctx context.Context, input ...) (..., error) {
    ctx, span := otel.Tracer("...").Start(ctx, "OrderService.Create")
    defer span.End()
    // ... business logic
}
```

Every generated service and repository method is already instrumented — no manual work. The built-in `UserService` / `UserRepository` that ships with `gofasta new` is instrumented the same way, so the trace waterfall for `/api/v1/users` is populated out of the box.

See [Tracing & Waterfalls](/docs/guides/debugging/tracing) for the full layer-by-layer breakdown — what's traced automatically, what you need to add yourself, and why SQL doesn't appear as nested spans by default.

## Production safety

All eight hooks above compile to no-ops in production.

```bash
go build ./...           # production build — stubs only
go build -tags devtools  # dev build — real implementations
```

`gofasta dev` sets `GOFLAGS=-tags=devtools` automatically. The CI build (`deployments/ci/github-actions-test.yml`) and the production Dockerfile never set it.

What "no-op" means for each hook:

| Hook | Stub behavior |
|------|---------------|
| `devtools.Middleware` | Identity — returns `next` unchanged |
| `devtools.Handler()` | 404 everywhere except `/debug/health` which returns `{"devtools":"stub"}` |
| `devtools.RegisterTraceProcessor()` | Empty function body |
| `devtools.GormPlugin()` | Plugin whose `Initialize` returns `nil` |
| `devtools.WrapLogger(h)` | Returns `h` unchanged |
| `devtools.WrapCache(c)` | Returns `c` unchanged |
| `devtools.Recovery(fallback)` | Returns `fallback` directly |
| `devtools.RegisterDB(db)` | Empty function body |

The Go compiler inlines these stubs and dead-code-eliminates the call sites, so a production binary has no unused devtools symbols. You can prove it yourself:

```bash
go build -o prod .              # production build
go tool objdump -s devtools prod | head
# → no devtools functions in the output
```

## Endpoint reference

### Scaffold debug endpoints

Mounted under `/debug/` by `devtools.Handler()`. Active only when the app is built with `-tags devtools`.

| Endpoint | Purpose |
|----------|---------|
| `GET /debug/health` | `{"devtools":"enabled"}` or `{"devtools":"stub"}` |
| `GET /debug/requests` | Recent `RequestEntry` ring (method, path, status, duration, trace ID, request body, response body) |
| `GET /debug/sql` | Recent `QueryEntry` ring (SQL, vars, rows, duration, error, trace ID) |
| `GET /debug/traces` | Summary list of completed traces (spans stripped for cheap polling) |
| `GET /debug/traces/{id}` | One full `TraceEntry` including every span, stack, event, attribute |
| `GET /debug/logs?trace_id=&level=` | `LogEntry` ring filtered by trace ID and/or minimum level |
| `GET /debug/errors` | Recent `ExceptionEntry` ring (recovered value, stack, trace ID, method, path) |
| `GET /debug/cache` | Recent `CacheEntry` ring (op, key, hit/miss, duration, trace ID) |
| `POST /debug/explain` | Run `EXPLAIN` against a captured SELECT. Body: `{"sql": "...", "vars": ["..."]}` |
| `GET /debug/pprof/*` | Standard Go profiling endpoints (index, profile, heap, goroutine, mutex, block, allocs, trace, threadcreate) |

### Dashboard API

Served by the CLI on the dashboard port (default `:9090`). The dashboard proxies most of these to the corresponding `/debug/*` endpoint on your app.

| Endpoint | Purpose |
|----------|---------|
| `GET /` | HTML dashboard with server-side-rendered initial state + SSE subscriber |
| `GET /api/state` | Current `dashboardState` JSON snapshot |
| `GET /api/stream` | Server-Sent Events stream (5s cadence) |
| `GET /api/trace/{id}` | Proxy to `/debug/traces/{id}` |
| `GET /api/logs?trace_id=&level=` | Proxy to `/debug/logs` |
| `POST /api/replay` | Re-fire a captured request. Body: `{"method": "...", "path": "...", "body": "..."}`. Returns `{"status": int, "body": string, "headers": {...}}` |
| `POST /api/explain` | Proxy to `/debug/explain` |
| `GET /api/har` | Download the request ring as HAR 1.2 JSON |

## Related



---

## /docs/guides/debugging/customization — Customization

> Ring sizes and rotation behavior, disabling individual devtools hooks, where the devtools code lives in your project, library coupling.

# Customization

## Ring sizes

Every `/debug/*` panel is backed by a fixed-size in-memory ring buffer. **When a ring fills up, the oldest entry is overwritten by the newest** — a classic FIFO circular buffer. The UI always renders newest-first, so you'll see fresh entries at the top and older ones fall off the tail. If the dashboard reports "50 total" and never grows past that, the ring is at capacity and rotating — not broken.

Defaults balance memory against retention for a typical dev session:

| Dashboard panel | Ring | Default cap |
| --- | --- | --- |
| Recent requests | request ring | **200** |
| Recent SQL | query ring | **200** |
| Cache ops | cache ring | **200** |
| Traces | trace ring (full span trees, heavier) | **50** |
| Exceptions | exception ring | **50** |
| Logs (per-trace tab) | slog ring | **500** |

These caps are **Go constants, not `config.yaml` entries**. The devtools code is scaffolded *into your project* (see `app/devtools/devtools_enabled.go`), so the knob is a one-line edit followed by a restart — no config schema, no runtime indirection, zero cost in production builds (the constants live behind `//go:build devtools`). If you need more history (long-running `gofasta dev` sessions, heavy traffic, trace drill-downs over a larger window), edit the constants at the top of `app/devtools/devtools_enabled.go` and restart:

```go
const ringCapacity = 200         // request + SQL + cache rings
const traceRingCapacity = 50     // trace ring (heavier — holds full span trees)
const maxBodyCapture = 64 * 1024 // request + response body cap
const stackDepth = 20            // frames per span stack snapshot
const logRingCapacity = 500      // slog ring
const exceptionRingCapacity = 50 // panic ring
```

Rough memory budget: each `RequestEntry` is a few KB (two 64 KiB body caps at worst), a `TraceEntry` with 20-frame stacks per span is typically 5–20 KB, a `LogEntry` is a few hundred bytes. At defaults the total devtools footprint is usually under 10 MB even on a busy session — scale the constants up freely if you have the RAM.

Because the rings are Go arrays (`[N]T`), changing the cap requires a recompile — `gofasta dev` picks it up on the next rebuild. There's no runtime reconfiguration path, by design.

## Disabling individual hooks

The scaffold's DI container calls every `devtools.Wrap*` helper unconditionally. To disable one of them, delete or comment the call in `app/di/providers/core.go`:

```go
// Disable slog ring recording (keep the wrapped logger's behavior
// identical to the pkg/logger default):
func ProvideLogger(cfg *config.LogConfig) *slog.Logger {
    return logger.NewLogger(cfg)  // was: slog.New(devtools.WrapLogger(...))
}
```

Wire regenerates automatically (`gofasta wire` or the pre-save Air hook). No other code changes needed.

## Where things live

- `app/devtools/devtools.go` — shared public API (types + function signatures)
- `app/devtools/devtools_enabled.go` — real implementation (`//go:build devtools`)
- `app/devtools/devtools_stub.go` — no-op implementation (`//go:build !devtools`)
- `cmd/serve.go` — wiring: middleware chain, debug handler mount, RegisterTraceProcessor, RegisterDB
- `app/di/providers/core.go` — ProvideLogger, ProvideCacheService, ProvideDB

Edit any of these — they're plain Go files in your project, not dependencies. You own them.

## Library coupling

The scaffold's `app/devtools` package imports:

- `github.com/gofastadev/gofasta/pkg/cache` (for `WrapCache`)
- `go.opentelemetry.io/otel` + `go.opentelemetry.io/otel/sdk/trace` (for the `SpanProcessor`)
- `gorm.io/gorm` (for the GORM plugin)

Swap `pkg/cache` for your own cache and the `WrapCache` implementation needs a matching interface — about 40 lines of decorator code to rewrite.

## Related



---

## /docs/guides/deployment — Deployment

> Deploy Gofasta applications to a VPS with the built-in gofasta deploy command — Docker or compiled binary via SSH, with systemd, nginx, and GitHub Actions wired up.

# Deployment

Gofasta's built-in deployment story targets one concrete environment: **a Linux VPS running Docker or systemd, reached over SSH**. The `gofasta deploy` command handles the full pipeline — build, ship, migrate, health-check, swap symlink, rollback — without requiring any cloud vendor account or third-party platform.

This page covers:

- The fastest path: `gofasta deploy` end-to-end.
- How the generated `Dockerfile`, `systemd` unit, `nginx` config, and CI workflows fit together.
- Activating the CI workflows (they ship inert; you opt into the ones you want).

Cloud-managed deployment (ECS, Cloud Run, App Service, etc.) is not currently supported. See the [whitepaper roadmap](/docs/white-paper#24-roadmap) if that's on the horizon for your project — until then, this page documents what's actually shipped.

## The short version

```bash
# One-time — prepare a fresh Ubuntu/Debian server.
gofasta deploy setup --host deploy@api.example.com

# Deploy the current working copy.
gofasta deploy

# Observe, debug, roll back.
gofasta deploy status
gofasta deploy logs
gofasta deploy rollback
```

Configure the target in `config.yaml`:

```yaml
deploy:
  host: deploy@api.example.com
  method: docker        # "docker" (default) or "binary"
  port: 22
  path: /opt/myapp
  arch: amd64           # "amd64" or "arm64"
  keep_releases: 3      # old releases retained for rollback
```

Full flag and subcommand reference: [gofasta deploy CLI](/docs/cli-reference/deploy).

## Deployment methods

`gofasta deploy` supports two methods. Both target the same VPS layout; only the packaging differs.

### Docker method (default)

1. Builds a Docker image locally using the project's `Dockerfile`.
2. Transfers it to the server via `docker save | ssh docker load` — no registry required.
3. Copies `.env`, `config.yaml`, and the production `compose.yaml` into a timestamped release directory.
4. Runs `docker compose up -d` on the server.
5. Runs pending database migrations inside the container.
6. Polls `/health` until the app responds.
7. Atomically repoints the `current` symlink to the new release.
8. Prunes releases older than `keep_releases`.

Best for: teams that want parity between local (`gofasta dev --all-in-docker`) and production, or that already use Docker Compose locally.

### Binary method

1. Cross-compiles a static binary with `CGO_ENABLED=0 GOOS=linux`.
2. Transfers the binary, migrations, templates, and configs over SCP.
3. Installs the binary to `/usr/local/bin/` and the systemd service unit.
4. Runs pending migrations.
5. Restarts the systemd service.
6. Polls `/health` until the app responds.
7. Atomically repoints `current` and prunes old releases.

Best for: teams that prefer no Docker daemon on the server, or deploy to a low-resource VPS where every MB matters.

## Release directory layout

Both methods use the same Capistrano-style layout on the remote host:

```
/opt/myapp/
├── current -> releases/20260417-153000/   # symlink flipped atomically
├── releases/
│   ├── 20260417-153000/                   # active release
│   └── 20260417-120000/                   # previous (available for rollback)
└── shared/
    ├── .env                               # shared across all releases
    └── config.yaml
```

Each deploy creates a new timestamped directory. The `current` symlink is only flipped after the health check passes, so a failed deploy leaves the previous release serving traffic.

## Deployment files in the scaffold

A `gofasta new` project ships deployment-adjacent files under `deployments/` plus a `Dockerfile` and `compose.yaml` at the project root:

```
.
├── Dockerfile                         # multi-stage build for the `docker` deploy method
├── compose.yaml                       # local dev compose file
└── deployments/
    ├── ci/                            # GitHub Actions workflow templates (opt-in — see below)
    │   ├── github-actions-test.yml
    │   ├── github-actions-release.yml
    │   └── github-actions-deploy-vps.yml
    ├── docker/
    │   ├── compose.production.yaml    # production compose file used by `gofasta deploy`
    │   └── dev.dockerfile             # dev image with Air for hot reload
    ├── nginx/
    │   └── app.conf                   # reverse-proxy snippet for TLS termination
    └── systemd/
        ├── app.service                # service unit for the `binary` deploy method
        └── deploy.sh                  # reference script if you want to deploy without the CLI
```

Everything under `deployments/` is standard Go code the developer owns — feel free to edit, delete, or replace.

## Dockerfile

The root `Dockerfile` is a two-stage build optimized for a minimal Alpine runtime image:

```dockerfile
FROM golang:1.25.0-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app ./app/main

FROM alpine:3.20
RUN apk add --no-cache ca-certificates tzdata
COPY --from=builder /app /app
COPY db/migrations /migrations
COPY config.yaml /config.yaml
COPY templates /templates
ENV PORT=8080
EXPOSE 8080
ENTRYPOINT ["/app"]
CMD ["serve"]
```

`gofasta deploy --method docker` invokes `docker build` against this file, then ships the resulting image to the server over SSH.

## systemd (binary method)

The `deployments/systemd/app.service` unit file manages the binary as a long-running service:

```ini
[Unit]
Description=MyApp API Server
After=network.target postgresql.service

[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp/current
ExecStart=/opt/myapp/current/myapp serve
Restart=always
RestartSec=5
Environment=APP_ENV=production
EnvironmentFile=/opt/myapp/shared/.env

[Install]
WantedBy=multi-user.target
```

`gofasta deploy setup` installs this unit automatically. If you ever need to re-install or edit it:

```bash
sudo cp deployments/systemd/app.service /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
sudo journalctl -u myapp -f
```

## nginx reverse proxy

`deployments/nginx/app.conf` is a reference reverse-proxy config with TLS termination:

```nginx
upstream myapp {
    server 127.0.0.1:8080;
}

server {
    listen 80;
    server_name api.myapp.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.myapp.com;

    ssl_certificate /etc/letsencrypt/live/api.myapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.myapp.com/privkey.pem;

    location / {
        proxy_pass http://myapp;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    location /health {
        proxy_pass http://myapp;
        access_log off;
    }
}
```

`gofasta deploy setup` installs nginx and copies this config into `/etc/nginx/sites-available/`. Point it at your domain, provision a Let's Encrypt certificate with `certbot --nginx -d api.myapp.com`, and reload.

## GitHub Actions workflows

### How activation works

The scaffold ships three workflow templates under `deployments/ci/` — **not** under `.github/workflows/`. That's deliberate: GitHub Actions only picks up workflow files from `.github/workflows/*.yml`. Files anywhere else are inert.

The scaffold stores them as inert templates so a first `git push` doesn't immediately fire a deploy with unset secrets. Each template's top comment documents its required secrets. Read the header, configure the secrets under **Settings → Secrets and variables → Actions**, *then* copy the file in:

```bash
mkdir -p .github/workflows
cp deployments/ci/github-actions-test.yml .github/workflows/test.yml
git add .github/workflows/test.yml
git commit -m "ci: enable test workflow"
git push
```

### Available templates

| Template | Purpose |
|---|---|
| `github-actions-test.yml` | Run `go test -race` on every push + PR against `main`, spin up a Postgres service container, upload coverage to Codecov. No secrets required unless you enable Codecov. |
| `github-actions-release.yml` | On `v*` tag push, build a Docker image and push it to GHCR using the default `GITHUB_TOKEN`. |
| `github-actions-deploy-vps.yml` | On push to `main` (or manual dispatch), run `gofasta deploy` against the configured VPS. Requires `DEPLOY_HOST`, `DEPLOY_SSH_KEY`, and `DEPLOY_PORT` secrets. |

### Example: test workflow

```yaml
# .github/workflows/test.yml
name: Test

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: myapp_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.25"
      - name: Run tests
        run: go test -race -coverprofile=coverage.out ./...
        env:
          GOFASTA_DATABASE_HOST: localhost
          GOFASTA_DATABASE_PORT: 5432
          GOFASTA_DATABASE_USER: postgres
          GOFASTA_DATABASE_PASSWORD: postgres
          GOFASTA_DATABASE_NAME: myapp_test
      - uses: codecov/codecov-action@v4
        with:
          file: coverage.out
```

### Example: VPS deploy workflow

```yaml
# .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with:
          go-version: "1.25"
      - name: Install gofasta CLI
        run: go install github.com/gofastadev/cli/cmd/gofasta@latest
      - name: Configure SSH
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519
          ssh-keyscan -p ${{ secrets.DEPLOY_PORT }} ${{ secrets.DEPLOY_HOST }} >> ~/.ssh/known_hosts
      - name: Deploy
        run: gofasta deploy --host ${{ secrets.DEPLOY_HOST }} --port ${{ secrets.DEPLOY_PORT }}
```

## First-time server setup

`gofasta deploy setup` automates everything below on an Ubuntu/Debian VPS. Use this section as reference for what it's doing under the hood or when running on a distribution it doesn't cover.

```bash
# On the server (one-time)
sudo apt update && sudo apt install -y curl nginx
curl -fsSL https://get.docker.com | sudo sh             # only if using docker method
sudo useradd -r -s /bin/false myapp                     # only if using binary method
sudo mkdir -p /opt/myapp/{releases,shared}
sudo chown -R myapp:myapp /opt/myapp

# Copy your .env and config.yaml into /opt/myapp/shared/
# Run `gofasta deploy` from your local machine.
```

## Troubleshooting

| Symptom | Likely cause |
|---|---|
| `deploy host is required` | `deploy.host` not set in `config.yaml` and `--host` not passed. |
| SSH connection refused | Host/port wrong, or your SSH key isn't in the server's `~/.ssh/authorized_keys`. Test: `ssh -p  user@server echo ok`. |
| Health check failed | Server started but the app didn't come up on `/health` within the timeout. The **previous release stays active** — run `gofasta deploy logs` to inspect. |
| `docker: command not found` on remote | Run `gofasta deploy setup` first, or install Docker manually and re-deploy. |
| Workflow doesn't run after `git push` | You likely forgot to copy the file from `deployments/ci/` into `.github/workflows/`. GitHub Actions only reads the latter. |

## Next Steps

- [gofasta deploy CLI reference](/docs/cli-reference/deploy) — every flag and subcommand.
- [Configure your application](/docs/guides/configuration) — what goes in `config.yaml` vs `.env`.
- [Health check API reference](/docs/api-reference/health) — liveness and readiness endpoints.
- [Observability API reference](/docs/api-reference/observability) — metrics and tracing.

## Related



---

## /docs/guides/configuration — Configuration

> Application configuration with config.yaml, environment variable overrides, database settings, JWT, Redis, and feature flags in Gofasta.

# Configuration

Gofasta uses a `config.yaml` file at the project root as the central configuration source. Every setting can be overridden with environment variables, making it easy to configure different environments without changing files.

## Loading Configuration

The `github.com/gofastadev/gofasta/pkg/config` package loads and parses configuration:

```go
import "github.com/gofastadev/gofasta/pkg/config"

cfg := config.LoadConfig("config.yaml")
```

`LoadConfig` reads the YAML file, then checks for environment variable overrides. The resulting config struct is passed to the DI container and used throughout the application.

## Full config.yaml Reference

Here is the complete configuration file with all available sections and their defaults:

```yaml
# Application settings
app:
  name: myapp
  port: 8080
  env: development        # development, staging, production
  debug: true
  url: http://localhost:8080

# Database configuration
database:
  driver: postgres        # postgres, mysql, sqlite, sqlserver, clickhouse
  host: localhost
  port: 5432
  name: myapp_db
  user: postgres
  password: postgres
  ssl_mode: disable
  max_open_conns: 25
  max_idle_conns: 5
  conn_max_lifetime: 5m

# JWT authentication
jwt:
  secret: change-me-in-production
  expiration: 24h
  refresh_expiration: 168h
  issuer: myapp

# Redis (used for caching, sessions, and queue)
redis:
  host: localhost
  port: 6379
  password: ""
  db: 0

# Cache configuration
cache:
  driver: memory          # memory or redis
  ttl: 5m

# Session configuration
session:
  driver: cookie          # cookie or redis
  secret: session-secret
  max_age: 86400

# Email configuration
mail:
  driver: smtp            # smtp, sendgrid, or brevo
  from_name: MyApp
  from_address: noreply@myapp.com
  smtp:
    host: smtp.mailtrap.io
    port: 587
    username: ""
    password: ""
    encryption: tls
  sendgrid:
    api_key: ""
  brevo:
    api_key: ""

# Queue configuration
queue:
  driver: memory          # memory or redis
  workers: 5
  retry_attempts: 3
  retry_delay: 30s

# Storage configuration
storage:
  driver: local           # local, s3
  local:
    path: ./uploads
  s3:
    bucket: myapp-uploads
    region: us-east-1
    access_key: ""
    secret_key: ""

# Observability
observability:
  metrics:
    enabled: true
    path: /metrics
  tracing:
    enabled: false
    exporter: jaeger
    endpoint: http://localhost:14268/api/traces

# Rate limiting
rate_limit:
  enabled: true
  max: 100
  window: 1m

# CORS
cors:
  allowed_origins:
    - http://localhost:3000
  allowed_methods:
    - GET
    - POST
    - PUT
    - DELETE
    - OPTIONS
  allowed_headers:
    - Authorization
    - Content-Type
  max_age: 86400

# Logging
log:
  level: info             # debug, info, warn, error
  format: json            # json or text

# Feature flags
features:
  graphql_playground: true
  swagger: true
  registration: true
```

## Environment Variable Overrides

Every config value can be overridden with an environment variable. The prefix is always `GOFASTA_`, followed by the section and key in uppercase, joined with underscores:

```
GOFASTA_{SECTION}_{KEY}
```

| Config Path | Environment Variable |
|------------|---------------------|
| `app.port` | `GOFASTA_APP_PORT` |
| `app.env` | `GOFASTA_APP_ENV` |
| `database.host` | `GOFASTA_DATABASE_HOST` |
| `database.password` | `GOFASTA_DATABASE_PASSWORD` |
| `jwt.secret` | `GOFASTA_JWT_SECRET` |
| `jwt.expiration` | `GOFASTA_JWT_EXPIRATION` |
| `redis.host` | `GOFASTA_REDIS_HOST` |
| `redis.port` | `GOFASTA_REDIS_PORT` |
| `mail.driver` | `GOFASTA_MAIL_DRIVER` |
| `mail.sendgrid.api_key` | `GOFASTA_MAIL_SENDGRID_API_KEY` |

Environment variables always take precedence over `config.yaml` values.

## Database Configuration

### PostgreSQL (default)

```yaml
database:
  driver: postgres
  host: localhost
  port: 5432
  name: myapp_db
  user: postgres
  password: postgres
  ssl_mode: disable
```

### MySQL

```yaml
database:
  driver: mysql
  host: localhost
  port: 3306
  name: myapp_db
  user: root
  password: root
```

### SQLite

```yaml
database:
  driver: sqlite
  name: ./data/myapp.db
```

### SQL Server

```yaml
database:
  driver: sqlserver
  host: localhost
  port: 1433
  name: myapp_db
  user: sa
  password: YourStrong!Passw0rd
```

### ClickHouse

```yaml
database:
  driver: clickhouse
  host: localhost
  port: 9000
  name: myapp_db
  user: default
  password: ""
```

### Connection Pool Settings

Tune connection pool settings for production:

```yaml
database:
  max_open_conns: 50      # Maximum number of open connections
  max_idle_conns: 10      # Maximum number of idle connections
  conn_max_lifetime: 10m  # Maximum time a connection can be reused
```

## JWT Configuration

```yaml
jwt:
  secret: your-256-bit-secret
  expiration: 24h          # Access token lifetime
  refresh_expiration: 168h # Refresh token lifetime (7 days)
  issuer: myapp            # Token issuer claim
```

For production, always set the secret via environment variable:

```bash
GOFASTA_JWT_SECRET=$(openssl rand -hex 32)
```

## Redis Configuration

Redis is used by the cache, session, and queue packages when configured with the `redis` driver:

```yaml
redis:
  host: localhost
  port: 6379
  password: ""
  db: 0
```

Multiple components can use the same Redis instance on different databases:

```yaml
redis:
  host: localhost
  port: 6379

cache:
  driver: redis

queue:
  driver: redis

session:
  driver: redis
```

## Feature Flags

Feature flags in `config.yaml` control optional functionality:

```yaml
features:
  graphql_playground: true   # Enable GraphQL Playground UI
  swagger: true              # Enable Swagger documentation endpoint
  registration: true         # Enable user registration endpoint
```

For more complex scenarios — user-targeted rollouts, percentage-based releases, A/B tests — the `github.com/gofastadev/gofasta/pkg/featureflag` package wraps the [OpenFeature Go SDK](https://github.com/open-feature/go-sdk) so you can plug in whichever flag backend you prefer (in-memory, [Flagd](https://flagd.dev), [LaunchDarkly](https://launchdarkly.com), [go-feature-flag](https://gofeatureflag.org), or a custom provider). The simplest production-ready setup is the in-memory provider:

```go
import (
    "github.com/gofastadev/gofasta/pkg/featureflag"
    "github.com/open-feature/go-sdk/openfeature/memprovider"
)

flags := map[string]memprovider.InMemoryFlag{
    "registration": {
        Key:            "registration",
        State:          memprovider.Enabled,
        DefaultVariant: "on",
        Variants:       map[string]any{"on": true, "off": false},
    },
}
ff, err := featureflag.NewInMemoryService(flags, logger)
if err != nil {
    return err
}
defer ff.Close()

if ff.IsEnabled(ctx, "registration", user.ID.String(), map[string]any{"plan": user.Plan}) {
    RegisterAuthRoutes(router, deps.AuthController)
}
```

To swap to a real rule engine, call `openfeature.SetProvider(...)` at startup with any [OpenFeature-compatible provider](https://openfeature.dev/ecosystem). Application code stays unchanged.

See the [Feature Flags API reference](/docs/api-reference/feature-flags) for the flag file format and the full `FeatureFlagService` API.

## Accessing Configuration in Code

The config struct is available throughout your application via dependency injection:

```go
// In a service
type OrderService struct {
    repo   interfaces.OrderRepository
    config *config.AppConfig
}

func NewOrderService(repo interfaces.OrderRepository, config *config.AppConfig) *OrderService {
    return &OrderService{repo: repo, config: config}
}

func (s *OrderService) GetAppURL() string {
    return s.config.App.URL
}
```

## Environment-Specific Configuration

A common pattern is to have a base `config.yaml` for development and override values in other environments:

**Development** -- use `config.yaml` as-is with local defaults.

**Staging** -- override with environment variables in your deployment:

```bash
GOFASTA_APP_ENV=staging
GOFASTA_DATABASE_HOST=staging-db.internal
GOFASTA_JWT_SECRET=staging-secret
```

**Production** -- override all sensitive values:

```bash
GOFASTA_APP_ENV=production
GOFASTA_APP_DEBUG=false
GOFASTA_DATABASE_HOST=prod-db.internal
GOFASTA_DATABASE_PASSWORD=prod-password
GOFASTA_JWT_SECRET=prod-secret
GOFASTA_REDIS_HOST=prod-redis.internal
GOFASTA_MAIL_DRIVER=sendgrid
GOFASTA_MAIL_SENDGRID_API_KEY=SG.prod-key
```

This approach keeps your `config.yaml` safe to commit to version control (it only contains development defaults) while production secrets are managed through environment variables or secret managers.

## Next Steps

- [Set up your database](/docs/guides/database-and-migrations)
- [Configure authentication](/docs/guides/authentication)
- [Deploy your application](/docs/guides/deployment)
- [Config API reference](/docs/api-reference/config)
- [Feature Flags API reference](/docs/api-reference/feature-flags)

## Related



---

## /docs/cli-reference/new — gofasta new

> Create a new Gofasta project with a complete Go web application scaffold.

# gofasta new

Creates a brand-new Gofasta project from scratch. By default, this generates a REST-only project with database migrations, authentication, dependency injection, background jobs, email support, Docker configuration, and CI/CD pipelines. Pass `--graphql` to also include GraphQL support (gqlgen schema, resolvers, `/graphql` endpoint, and playground).

## Usage

```bash
gofasta new  [flags]
```

The `` can be a simple name like `myapp` or a full Go module path like `github.com/myorg/myapp`. When a simple name is used (no `/` in the argument), the module path defaults to the project name. When a full path is provided, it is used as the Go module path and the last segment becomes the project directory name.

## Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--graphql` | `false` | Include GraphQL support (gqlgen schema, resolvers, `/graphql` endpoint, playground) |
| `--gql` | `false` | Alias for `--graphql` |

Without `--graphql`, the generated project is REST-only -- no GraphQL files, no gqlgen dependency, and no `/graphql` routes.

## Examples

Create a project with a simple name:

```bash
gofasta new myapp
```

Create a project with a full Go module path:

```bash
gofasta new github.com/myorg/myapp
```

Create a project with GraphQL support:

```bash
gofasta new myapp --graphql
```

## What It Does

When you run `gofasta new myapp`, the CLI performs the following steps in order:

1. **Create project directory** -- creates the `myapp/` directory
2. **Initialize Go module** -- runs `go mod init` with the appropriate module path
3. **Copy skeleton files** -- writes template files into the project, including a starter User resource
4. **Install Gofasta and Cobra** -- runs `go get github.com/gofastadev/gofasta@latest` (the gofasta library) and `go get github.com/spf13/cobra@latest` to add them to `go.mod`
5. **Install tools** -- runs `go get` to add tool dependencies (wire, air, swag) and `go mod edit -tool` for each. If `--graphql` is passed, gqlgen is also installed.
6. **Tidy modules** -- runs `go mod tidy` to synchronize dependencies
7. **Generate Wire DI code** -- runs `go tool wire ./app/di/` to generate the dependency injection container
8. **Generate GraphQL code** *(only with `--graphql`)* -- runs `go tool gqlgen generate` to create Go types and resolvers from the GraphQL schema
9. **Initialize Git** -- runs `git init` and creates an initial commit

## What It Generates

Running `gofasta new myapp` produces the following project structure. Items marked *(--graphql only)* are only included when the `--graphql` flag is passed:

```
myapp/
  cmd/
    serve.go                        # HTTP server entry point
  app/
    models/                         # GORM database models
      user.model.go
    repositories/                   # Data access layer
      interfaces/
        user_repository.go
      user.repository.go
    services/                       # Business logic layer
      interfaces/
        user_service.go
        auth_service.go
      user.service.go
      auth.service.go
    rest/
      controllers/                  # REST API controllers
        user.controller.go
        auth.controller.go
      routes/                       # Route definitions
        index.routes.go
        user.routes.go
        auth.routes.go
      middlewares/                   # HTTP middlewares
        auth.middleware.go
        casbin.middleware.go
    dtos/                           # Request/response DTOs
      user.dtos.go
      auth.dtos.go
    di/                             # Dependency injection (Google Wire)
      container.go
      wire.go
      providers/
        user.go
        auth.go
    graphql/                        # GraphQL schema and resolvers (--graphql only)
      schema.graphqls
      resolver.go
      generated.go
    jobs/                           # Background jobs
    tasks/                          # Scheduled tasks (cron)
    emails/                         # Email templates
  db/
    migrations/                     # SQL migration files (up/down)
    seeders/                        # Database seed files
  config/
    config.yaml                     # Application configuration
  docker-compose.yml                # Docker Compose for app + database
  Dockerfile                        # Multi-stage Docker build
  Makefile                          # Common development commands
  .github/
    workflows/                      # CI/CD pipeline definitions
  .air.toml                         # Air hot reload configuration
  .env.example                      # Environment variable template
  gqlgen.yml                        # gqlgen GraphQL configuration (--graphql only)
  go.mod
  go.sum
```

## Architecture

The generated project follows a layered architecture:

```
Controller --> Service --> Repository --> Database
```

- **Controllers** handle HTTP requests and delegate to services
- **Services** contain business logic and call repositories
- **Repositories** handle database queries via GORM
- **DI Container** wires everything together using Google Wire

## Related

- [Quick Start](/docs/getting-started/quick-start)
- [Project Structure](/docs/getting-started/project-structure)
- [gofasta init](/docs/cli-reference/init) -- initialize a cloned project
- [gofasta dev](/docs/cli-reference/dev) -- start the development server

---

## /docs/cli-reference/init — gofasta init

> Initialize a cloned or existing Gofasta project by installing dependencies and running code generators.

# gofasta init

Initializes an existing Gofasta project that was cloned from a repository or copied from another machine. This command creates the `.env` file, installs Go dependencies, runs Google Wire dependency injection generation, conditionally runs gqlgen GraphQL code generation (if `gqlgen.yml` exists), runs database migrations, and verifies the project builds successfully.

Use this command instead of `gofasta new` when you already have the project source code and just need to set up the development environment.

## Usage

```bash
gofasta init
```

Run this command from the root directory of your Gofasta project (the directory containing `go.mod`).

This command takes no flags.

## Examples

Initialize a freshly cloned project:

```bash
git clone https://github.com/myorg/myapp.git
cd myapp
gofasta init
```

## What It Does

When you run `gofasta init`, the CLI performs the following steps in order:

1. **Create .env file** -- copies `.env.example` to `.env` so you have a working environment configuration
2. **Install dependencies** -- runs `go mod tidy` to download and synchronize all Go module dependencies
3. **Generate Wire code** -- runs `go tool wire ./app/di/` to generate the dependency injection container from your provider definitions
4. **Generate GraphQL code** *(only if `gqlgen.yml` exists)* -- runs `go tool gqlgen generate` to create Go types and resolvers from your `.graphqls` schema files. This step is automatically skipped for REST-only projects that were created without `--graphql`.
5. **Run migrations** -- runs `migrate -path db/migrations -database  up` to apply all pending database migrations
6. **Verify build** -- runs `go build ./...` to confirm the project compiles successfully

## When to Use

| Scenario | Command |
|----------|---------|
| Starting a brand-new project from scratch | `gofasta new myapp` |
| Cloning an existing project from Git | `gofasta init` |
| Pulling changes that modified `wire.go` or GraphQL schemas | `gofasta init` |
| Setting up the project on a new machine | `gofasta init` |

## Related

- [gofasta new](/docs/cli-reference/new) -- create a new project from scratch
- [gofasta wire](/docs/cli-reference/wire) -- regenerate only Wire DI code
- [gofasta dev](/docs/cli-reference/dev) -- start the development server
- [Installation](/docs/getting-started/installation)

---

## /docs/cli-reference/dev — gofasta dev

> Bring the full local environment up with one command — compose services + migrations + Air hot reload, with interactive restart.

# `gofasta dev`

One-command development loop. Runs the full pipeline: start compose services, wait for healthchecks, apply pending migrations, launch Air for hot-reload, stream structured events for agents, accept interactive keyboard controls, and tear everything down on `Ctrl+C`.

## Usage

```bash
gofasta dev [flags]
```

## What runs by default

`gofasta dev` activates the **`cache` and `queue` compose profiles automatically** so your scaffolded project's redis + asynqmon services come up alongside the database. Pass `--no-cache` / `--no-queue` to opt out.

A second mode, [`--all-in-docker`](#all-in-docker-mode), runs Air *inside* the app container and streams its hot-reload output to the foreground — useful when you want full Docker isolation but still want to see live logs.

While the dev loop is running, the terminal accepts [single-key shortcuts](#interactive-keyboard-controls) — `r` to restart everything from scratch, `q` to quit, `h` for help.

## Pipeline

Each stage can be opted out independently.

1. **Preflight** — verify `docker` and `docker compose` availability.
2. **Fresh volumes** *(optional, `--fresh`)* — drop every compose volume before starting.
3. **Service start** — `docker compose --profile cache --profile queue up -d `.
4. **Health-wait** — poll each service's compose healthcheck until `healthy` (timeout 30s by default).
5. **Migrate** — `migrate up` against the now-healthy database. *Skipped under `--all-in-docker` — the dev container runs `migrate` itself before Air.*
6. **Seed** *(optional, `--seed`)* — run seeders after migrations.
7. **Air** — exec `go tool air` on the host against `.air.toml`. Under `--all-in-docker`, instead tail the app container's stdout via `docker compose logs -f app`.
8. **Teardown** — on `SIGINT`/`SIGTERM` or `q`, stop services (volumes preserved by default). On `r`, re-run the entire pipeline from scratch.

On projects without a `compose.yaml`, steps 1–4 are short-circuited and the pipeline falls straight to Air — preserving the "I'm bringing my own DB" flow.

## Flags

| Flag | Default | Behavior |
| --- | --- | --- |
| `--all-in-docker` | `false` | Run the entire stack (including Air) inside Docker. Supporting services run detached; the app container's stdout streams to the foreground for live hot-reload logs. See [the dedicated section](#all-in-docker-mode). |
| `--no-services` | `false` | Skip all compose orchestration; just run Air (for an externally-managed database). |
| `--no-db` | `false` | Skip DB-like services (postgres, mysql, clickhouse, mariadb, or anything suffixed `-db`). |
| `--no-cache` | `false` | Skip the `cache` compose profile (which is auto-activated by default). |
| `--no-queue` | `false` | Skip the `queue` compose profile (which is auto-activated by default). |
| `--no-migrate` | `false` | Skip `migrate up` after services become healthy. |
| `--no-keyboard` | `false` | Disable the [interactive keyboard layer](#interactive-keyboard-controls). Use this when stdin is piped or for non-interactive sessions. Auto-disabled when stdin is not a TTY. |
| `--no-teardown` | `false` | Leave compose services running on exit (default: stop them). |
| `--keep-volumes` | `true` | Preserve named volumes on teardown. Pass `--keep-volumes=false` to run `docker compose down -v` instead of `docker compose stop`. |
| `--fresh` | `false` | Drop every compose volume before starting — forces a clean database state. |
| `--services=` | *(derived)* | Comma-separated explicit list. Overrides `--no-*` flags. |
| `--profile=` | *(none)* | Additional compose profile to activate. Cache and queue are auto-on unless `--no-cache` / `--no-queue`. |
| `--wait-timeout=` | `30s` | How long to wait for compose services to report healthy. |
| `--env-file=` | `.env` | Alternate env file to load before Air. |
| `--port=` | *(from config)* | Override the `PORT` env var passed to Air / the app binary. |
| `--rebuild` | `false` | Clear Air's `tmp/` build directory before starting so the next build is fresh. Host-Air-only; no-op under `--all-in-docker`. |
| `--seed` | `false` | Run seeders after migrations (equivalent to `gofasta seed` afterward). |
| `--dry-run` | `false` | Print the resolved plan and exit without touching anything. |
| `--attach-logs` | `false` | Stream `docker compose logs -f` for every service alongside Air. Under `--all-in-docker` this multiplexes db/cache/queue alongside the app's foreground logs. |
| `--dashboard` | `false` | Start the local dev dashboard — an HTML debug page with routes, health, and live service state. |
| `--dashboard-port=` | `9090` | Port for the dev dashboard HTTP server. |
| `--json` | `false` *(persistent)* | Emit structured NDJSON events instead of human log lines. |

## Flag resolution priority

1. `--no-services` wins unconditionally — start nothing.
2. `--services=a,b,c` overrides `--no-*` filters — start exactly those.
3. Default: every non-app service in `compose.yaml`, minus any matched by `--no-*` name heuristics. The `app` service is included only under `--all-in-docker`.

**Compose profile resolution.** The profile list passed to `docker compose --profile` is built from:

1. Any value of `--profile=`.
2. `cache` — unless `--no-cache` is set.
3. `queue` — unless `--no-queue` is set.

Duplicates are removed; order is stable. So the default activates `[cache queue]`; `gofasta dev --no-cache` activates `[queue]`; `gofasta dev --no-cache --no-queue` activates `[]`.

## Interactive keyboard controls

While the dev loop is running, gofasta puts stdin in raw mode and watches for these single-key shortcuts:

| Key | Action |
| --- | --- |
| `r` / `R` | **Restart the entire pipeline from scratch.** Services stop, then every stage re-runs (`.env` reload → preflight → start services → wait healthy → migrate → seed → Air). Same effect as Ctrl+C and `gofasta dev` again, without leaving the process. |
| `q` / `Q` | Quit cleanly. Equivalent to Ctrl+C — runs the existing teardown flow. |
| `h` / `H` / `?` | Print the keybinding help inline. |

When `gofasta dev` starts with the keyboard layer active you'll see a one-line banner:

```
⌨  press `r` to restart · `q` to quit · `h` for help
```

The keyboard listener auto-disables when stdin is not a TTY (CI runs, piped input). Pass `--no-keyboard` to disable it explicitly when stdin is needed by something else.

**When you'd press R.** Air handles Go-source reloads automatically — you don't need R for that. Press R when something *outside* the Air-watched filesystem changes:

- You edited `.env` or `config.yaml` and want the new values picked up.
- You added or rebased migration files and want them re-applied from a clean state with `--fresh`.
- A compose service got into a weird state and you want a fresh `up -d` cycle.
- You want to test cold-start behavior (full pipeline replay) without exiting the process.

## --all-in-docker mode

Adds the `app` compose service to the orchestrated set so Air runs **inside the app container** rather than on the host. Supporting services (db, cache, queue) still run detached; the app container's stdout (Air's hot-reload output) is streamed to your terminal via `docker compose logs -f app`, so the experience matches host-side Air.

```bash
gofasta dev --all-in-docker
```

What changes vs. the default mode:

| Stage | Default | `--all-in-docker` |
| --- | --- | --- |
| **Service start** | `compose up -d ` | `compose up -d ` |
| **Migrate** | Host runs `migrate up` against `localhost:5433` | **Skipped on host** — the dev container's `CMD` runs `migrate` against `db:5432` before starting Air. |
| **Air** | `go tool air` on the host | Air runs inside the `app` container; gofasta tails its logs to the foreground. |
| **JSON event** | `{"event":"air","status":"running"}` | `{"event":"air","status":"running-in-docker"}` |

**Mutual exclusions.** `--all-in-docker` is incompatible with `--no-services` and `--no-db` (the in-container app needs the database). Both produce a `DEV_FLAG_CONFLICT` error before any side effect runs. The flag also requires an `app` service in `compose.yaml` — gofasta scaffolds one by default; if your project's compose file is hand-authored without one, the same error fires.

**Multiplexing other service logs.** By default only the app's logs reach the foreground. Pass `--attach-logs` alongside `--all-in-docker` to also stream db / cache / queue logs (compose prefixes each line with the service name).

## Structured output (`--json`)

Pipe `gofasta dev --json` into `jq` or your CI. One NDJSON event per line:

```json
{"event":"preflight","status":"ok","docker":"28.0.1","compose":"v2.26.0"}
{"event":"service","name":"db","status":"starting"}
{"event":"service","name":"db","status":"healthy","duration_ms":2840}
{"event":"migrate","status":"ok","applied":3}
{"event":"air","status":"running","port":8080,"urls":{"rest":"http://localhost:8080","metrics":"/metrics","swagger":"/swagger/index.html","health":"/health"}}
{"event":"shutdown","teardown":"stopped","exit":0}
```

Under `--all-in-docker` the `air` event carries `"status":"running-in-docker"` instead of `"running"` so consumers can branch on the runtime:

```json
{"event":"migrate","status":"skipped","message":"running inside the app container"}
{"event":"air","status":"running-in-docker","port":8080,"urls":{"rest":"http://localhost:8080","health":"/health"}}
```

## Error codes

| Code | When |
| --- | --- |
| `DEV_DOCKER_UNAVAILABLE` | `docker` not on `$PATH` or daemon unreachable |
| `DEV_COMPOSE_NOT_FOUND` | No `compose.yaml` but `--services` or `--all-in-docker` was set |
| `DEV_SERVICE_UNHEALTHY` | A service didn't become healthy within `--wait-timeout` |
| `DEV_MIGRATION_FAILED` | `migrate up` returned non-zero |
| `DEV_AIR_NOT_INSTALLED` | `go tool air` not registered in `go.mod` |
| `DEV_PORT_IN_USE` | The configured PORT is already bound |
| `DEV_FLAG_CONFLICT` | Two flags asked for incompatible behavior — e.g. `--all-in-docker --no-services`, `--all-in-docker --no-db`, or `--all-in-docker` against a `compose.yaml` with no `app` service. |

Each carries a `hint` and `docs` field in the JSON error payload — agents can branch on the code and surface the hint verbatim.

## The dashboard

`gofasta dev --dashboard` starts a tiny HTTP server on `:9090` (configurable via `--dashboard-port`) that serves a live HTML debug page:

- Current app health (polled every 5s against `/health`)
- Running compose services + their state
- Registered REST routes (scraped from `docs/swagger.json`, including request/response Go types)
- Direct links to `/swagger`, `/graphql`, `/metrics` when the project exposes them
- Prometheus metrics (request totals, in-flight gauge, average latency)
- Recent requests ring buffer (method, path, status, duration, trace ID, replay button)
- Recent SQL queries with rows affected and duration
- Per-request trace waterfall with nested spans, stack snapshots, and span events

Updates stream over Server-Sent Events at `/api/stream`; the JSON snapshot is at `/api/state`. The dashboard dies with the rest of the pipeline on `Ctrl+C`.

### Full-request inspection (Levels 1–4)

When the app is built with the `devtools` build tag (`gofasta dev` sets this automatically via `GOFLAGS`), the dashboard stops being a read-only view and becomes an interactive inspector:

**Level 1 — Trace waterfall.** An OpenTelemetry `SpanProcessor` registered by `devtools.RegisterTraceProcessor()` snapshots every span into an in-memory ring keyed by trace ID. Click a trace row to see a waterfall of nested spans — controller → service → repository → SQL — with offsets and durations drawn to scale.

**Level 2 — Stack snapshots per span.** At span start, the processor records up to 20 call frames (`runtime.Callers`) so the dashboard can show exactly where in your source a span was opened. Click the ▸ beside a span to reveal its stack.

**Level 3 — Auto-instrumented service/repository layers.** The scaffold's generators emit `otel.Tracer().Start(ctx, "Service.Method")` at the entry of every generated service and repository method. Any resource scaffolded with `gofasta g scaffold` contributes spans to the waterfall out of the box — no manual instrumentation needed.

**Level 4 — Request replay.** Every captured request keeps its body (capped at 64 KiB) in the ring. The **Replay** button on any row POSTs that request back to the live app. Mutating methods (POST / PUT / PATCH / DELETE) prompt for confirmation first. The response body and status appear inline so you can iterate on a bug without re-clicking through the UI.

All four levels are **no-cost in production**: the `devtools` package ships a stub (`//go:build !devtools`) whose middleware is a pass-through and whose `RegisterTraceProcessor` is a no-op. `go build` without `-tags devtools` dead-code-eliminates every debug surface.

### Deep-inspection features

The dashboard isn't just a live view — it's an interactive inspector covering the full request lifecycle. Everything below is gated behind the `devtools` build tag (set automatically by `gofasta dev`); production builds compile the stub and pay zero cost.

**Go runtime profiles (pprof).** `net/http/pprof` is mounted under `/debug/pprof/` whenever the app runs with the devtools tag. The dashboard surfaces a Profiles panel with one-click links to CPU (30s), heap, goroutine, mutex, block, allocations, and execution trace profiles. Open them in `go tool pprof` or view inline via the browser UI.

**Goroutine inspector.** The dashboard parses `/debug/pprof/goroutine?debug=2` and groups live goroutines by top-of-stack function — click to expand per-bucket stacks. Spotting a runaway goroutine leak is a single refresh away.

**N+1 query detection.** Every captured SQL statement carries its trace ID and a normalized template (literals replaced with `?`). The dashboard groups by (trace, template); any trace with ≥3 duplicate templates surfaces as an N+1 finding with the offending template shown. Click the trace link to see exactly which request caused it.

**EXPLAIN on click.** Each captured SELECT gets an **EXPLAIN** button. Clicking it opens a modal that ships the SQL + captured parameter values to the app's `/debug/explain` endpoint — the app runs EXPLAIN against GORM and returns the plan. A SELECT-only whitelist prevents accidentally re-executing DML.

**Per-request log viewer.** `devtools.WrapLogger` decorates the project's `*slog.Logger` so every record is teed into an in-memory ring keyed by trace ID. Expand any trace row → switch to the Logs tab → see exactly which log lines that request emitted, with levels and attributes.

**Panic + exception history.** `devtools.Recovery` wraps `pkg/middleware.Recovery`. Every recovered panic pushes an entry into the exceptions ring — recovered value, full stack, originating method + path, and the trace ID of the failing request. The Exceptions section surfaces the last 50 entries.

**Cache hit-miss log.** `devtools.WrapCache` decorates `cache.CacheService` so every Get/Set/Delete/Flush/Ping is recorded with op, key, hit/miss flag, and duration. Click a trace ID to see which cache ops happened during that request.

**Queue inspector.** When the scaffold's `asynqmon` compose service (profile=queue) is running, the dashboard detects it and surfaces a direct link in the App cards row.

**Edit-and-replay.** The single-click Replay has been upgraded to an inline editor — click Replay, tweak the captured body in a textarea, hit Send. Response status + body appear inline; mutating methods prompt for confirmation first.

**HAR export.** The **Export HAR** button in the Recent Requests header downloads the current ring as HAR 1.2 JSON, importable into Chrome DevTools, Insomnia, Postman, or any HAR-aware viewer.

### Dashboard endpoints

The dashboard HTTP server also exposes a small JSON API for agents and CI hooks:

| Endpoint | Purpose |
| --- | --- |
| `GET /` | HTML page with server-side-rendered initial state and an SSE subscriber for live updates |
| `GET /api/state` | Current `dashboardState` snapshot as JSON |
| `GET /api/stream` | Server-Sent Events stream (5s cadence) |
| `GET /api/trace/{id}` | Full `TraceEntry` — every span, stack, event, attribute |
| `GET /api/logs?trace_id=&level=` | Slog records filtered by trace / level |
| `POST /api/replay` | Re-fire a captured request. Body: `{ "method": "POST", "path": "/api/v1/orders", "body": "..." }` |
| `POST /api/explain` | Run EXPLAIN against the registered GORM DB. Body: `{ "sql": "SELECT ...", "vars": ["a", "b"] }` |
| `GET /api/har` | Download the current request ring as HAR 1.2 JSON |

### Scaffold-side debug endpoints

These live on the app itself (mounted under `/debug/` by `devtools.Handler()`), not on the dashboard. The dashboard scrapes them on a 5s refresher.

| Endpoint | Purpose |
| --- | --- |
| `GET /debug/requests` | Captured `RequestEntry` ring (method, path, status, duration, trace ID, body, response body) |
| `GET /debug/sql` | Captured `QueryEntry` ring (SQL, vars, rows, duration, trace ID) |
| `GET /debug/traces` | Summary list of completed trace waterfalls |
| `GET /debug/traces/{id}` | One full trace including every span, stack, event |
| `GET /debug/logs?trace_id=&level=` | Filtered slog ring |
| `GET /debug/errors` | Recent panics with stacks |
| `GET /debug/cache` | Recent cache ops |
| `POST /debug/explain` | EXPLAIN a SELECT |
| `GET /debug/pprof/*` | Standard Go profiling endpoints |
| `GET /debug/health` | `{"devtools":"enabled"}` or `{"devtools":"stub"}` |

## Recipes

```bash
# Everything up — db, cache (redis), queue (asynqmon), Air on the host. Ctrl+C to stop.
gofasta dev

# Just the database — drop cache + queue.
gofasta dev --no-cache --no-queue

# Run the entire stack inside Docker. Air runs in the app container; its
# logs stream to your terminal so the experience matches host-Air.
gofasta dev --all-in-docker

# --all-in-docker plus multiplexed db/cache/queue logs in the same terminal.
gofasta dev --all-in-docker --attach-logs

# Bring up services but run Air pointing at a different port.
gofasta dev --port 8090

# Host-run app with your own Postgres — bypass compose entirely.
gofasta dev --no-services

# Debug Air itself — no database, no migrations, just the rebuild loop.
gofasta dev --no-services --no-migrate

# Start with a fresh, seeded database every time.
gofasta dev --fresh --seed

# Add an extra profile beyond the auto-on cache + queue.
gofasta dev --profile observability

# Dev dashboard + log streaming + JSON mode for a CI run.
gofasta dev --dashboard --attach-logs --json

# Print the plan without running it — useful in CI sanity checks.
gofasta dev --dry-run --json

# Disable interactive keyboard (e.g. when a parent process owns stdin).
gofasta dev --no-keyboard
```

## Restart workflow

Once the dev loop is running, you don't need to exit it to re-run from scratch — press **`r`** at any time. The pipeline:

1. SIGINTs Air (or sends teardown to the app container under `--all-in-docker`).
2. Runs the configured teardown — `compose stop` by default, or `compose down -v` if `--keep-volumes=false`.
3. Re-loads `.env` (so edits to it apply).
4. Re-runs every stage from preflight through Air.

A short banner is printed between iterations:

```
⟳ restarting from scratch (iteration 2)
```

If the keyboard layer is disabled (non-TTY or `--no-keyboard`), restart is unavailable — exit and re-invoke `gofasta dev` instead.

## Related



---

## /docs/cli-reference/debug — gofasta debug

> CLI surface for the devtools /debug/* endpoints — requests, SQL, traces, logs, errors, cache, pprof, EXPLAIN, HAR, plus the composed diagnostics (last-slow-request, last-error, watch).

# `gofasta debug`

A first-class CLI interface to the running app's `/debug/*` endpoints. Every command is an agent-friendly alternative to `curl` + `jq` — it discovers the app URL automatically, fails fast with stable error codes when the app is down or the `devtools` build tag isn't set, and honors the global `--json` flag for machine-parseable output.

Every surface described here is gated behind the `devtools` build tag. `gofasta dev` sets the tag via `GOFLAGS` automatically; production builds (no tag) compile the stub and ignore every `/debug/*` request.

## Global flags

Every `gofasta debug ` accepts:

| Flag | Default | Purpose |
|---|---|---|
| `--app-url=` | discovered | Override app URL. Normally read from `config.yaml`'s `server.port` with fallback to `PORT` env / `8080`. |
| `--json` | false | Machine-readable output. Text mode prints tables / waterfalls; JSON mode is the stable API contract. |

## Error codes

Debug commands use a dedicated subsystem so agents branch cleanly:

| Code | When |
|---|---|
| `DEBUG_APP_UNREACHABLE` | App not running at the resolved URL, or `/debug/health` returned 5xx. |
| `DEBUG_DEVTOOLS_OFF` | App is running but built without `-tags devtools`. Rebuild via `gofasta dev`. |
| `DEBUG_TRACE_NOT_FOUND` | Requested trace ID is no longer in the ring (evicted — rings hold the last 50 traces). |
| `DEBUG_BAD_FILTER` | A filter flag value was rejected (status range, cache op, trace status). |
| `DEBUG_BAD_DURATION` | A `--slower-than` / `--threshold` / `--interval` value failed `time.ParseDuration`. |
| `DEBUG_PROFILE_UNSUPPORTED` | `profile ` was called with an unknown profile name. |
| `DEBUG_EXPLAIN_FAILED` | `/debug/explain` rejected the statement (non-SELECT, DB handle not registered, or query errored). |

## Subcommands

### `gofasta debug health`

Probes the app and every `/debug/*` collection endpoint. Run this first after starting `gofasta dev` — it pinpoints whether the app is down, built wrong, or the specific endpoint you need is unreachable.

**Output (text):**

```
App: http://localhost:8080
Reachable: reachable
Devtools: enabled

ENDPOINT              STATUS
/debug/health         200 OK
/debug/requests       200 OK
/debug/sql            200 OK
/debug/traces         200 OK
/debug/logs           200 OK
/debug/errors         200 OK
/debug/cache          200 OK
/debug/pprof/         200 OK
```

**Output (JSON):** `{app_url, reachable, devtools, endpoints: [{path, status, error?}]}`.

### `gofasta debug requests`

List captured requests with client-side filtering.

**Flags:** `--trace=`, `--method=`, `--status=<200|2xx|200-299|200,201>`, `--path=`, `--slower-than=`, `--limit=`.

```bash
gofasta debug requests --slower-than=100ms
gofasta debug requests --status=5xx --json
gofasta debug requests --trace=a7f3c8... --json | jq '.[].path'
```

### `gofasta debug sql`

List captured SQL statements.

**Flags:** `--trace=`, `--slower-than=`, `--contains=`, `--errors-only`, `--limit=`.

```bash
gofasta debug sql --slower-than=50ms
gofasta debug sql --contains="FROM orders" --errors-only
```

### `gofasta debug traces`

Summary list of completed trace waterfalls. Use `debug trace ` for full span trees.

**Flags:** `--slower-than=`, `--status=`, `--limit=`.

```bash
gofasta debug traces --slower-than=200ms
gofasta debug traces --status=error --json
```

### `gofasta debug trace `

Full waterfall for one trace, rendered as an ASCII bar chart with nested spans.

**Flags:** `--with-stacks` prints the 20-frame call stack captured at each span's start.

```
Trace a7f3c8...  POST /api/v1/orders  612 ms  23 spans

    612 ms  [████████████████████████████████████████]  root: POST /api/v1/orders (server)
    598 ms  [████████████████████████████████████████]  └─ OrderController.Create (internal)
    584 ms  [████████████████████████████████████████]     └─ OrderService.Create (internal)
     34 ms  [██                                      ]        ├─ OrderRepository.Create (internal)
     28 ms  [█                                       ]        │  └─ INSERT INTO orders (client)
      6 ms  [                 █                      ]        └─ UserRepository.FindByID (internal)
```

### `gofasta debug logs`

Slog records filtered server-side by trace / level.

**Flags:** `--trace=` (strongly recommended — otherwise returns the full 500-entry ring), `--level=`, `--contains=`.

```bash
gofasta debug logs --trace=a7f3c8...
gofasta debug logs --trace=a7f3c8... --level=WARN
```

### `gofasta debug errors`

Recent recovered panics with stacks.

**Flags:** `--limit=`, `--contains=`.

```bash
gofasta debug errors
gofasta debug errors --contains="nil pointer" --json
```

### `gofasta debug cache`

Cache operations with hit/miss flags. Text mode prints a hit-rate footer; JSON returns the raw entries.

**Flags:** `--trace=`, `--op=`, `--miss-only`, `--limit=`.

```bash
gofasta debug cache --op=get --miss-only
gofasta debug cache --trace=a7f3c8... --json
```

### `gofasta debug goroutines`

Live goroutines grouped by top-of-stack function.

**Flags:** `--filter=`, `--min-count=`.

```bash
gofasta debug goroutines
gofasta debug goroutines --filter=sync --min-count=5
```

### `gofasta debug n-plus-one`

N+1 patterns detected in the recent SQL ring. Reports one row per `(trace_id, normalized_template)` with ≥3 hits.

```bash
gofasta debug n-plus-one
gofasta debug n-plus-one --json | jq '.[] | select(.count > 10)'
```

### `gofasta debug explain `

Run `EXPLAIN` against a captured SELECT via the app's registered `*gorm.DB`.

**Args:** The SQL statement (must start with `SELECT`).
**Flags:** `--vars` one or more bound values.

```bash
gofasta debug explain "SELECT * FROM users WHERE id = ?" --vars=42
```

### `gofasta debug last-slow-request` *(composed)*

Find the newest request ≥ `--threshold` and bundle its trace, logs, SQL, and detected N+1 patterns into one JSON document. One tool call returns everything needed to diagnose the incident.

**Flags:** `--threshold=` (default `200ms`), `--with-trace` (default on), `--with-logs` (default on), `--with-sql` (default on), `--with-stack` (default off — adds 20-frame stacks per span).

```bash
gofasta debug last-slow-request
gofasta debug last-slow-request --threshold=500ms --json
gofasta debug last-slow-request --json | jq '.n_plus_one'
```

**JSON shape:**

```json
{
  "threshold": "200ms",
  "request":  { method, path, status, duration_ms, trace_id, body, response_body },
  "trace":    { trace_id, root_name, duration_ms, span_count, spans: [...] },
  "logs":     [ { time, level, message, attrs, trace_id }, ... ],
  "sql":      [ { sql, vars, rows, duration_ms, trace_id }, ... ],
  "n_plus_one": [ { trace_id, template, count }, ... ]
}
```

### `gofasta debug last-error` *(composed)*

Most recent panic plus its trace plus its logs, bundled.

**Flags:** `--with-trace` (default on), `--with-logs` (default on).

```bash
gofasta debug last-error
gofasta debug last-error --json | jq '.exception.recovered'
```

### `gofasta debug watch`

NDJSON stream of new events. One JSON object per line, tagged with an `event` field (`request`, `sql`, `error`, `cache`, `trace`, `heartbeat`). Polls each enabled ring on a configurable interval and emits only entries strictly newer than the last high-water mark.

**Flags:** `--interval=` (default `1s`), `--requests` (default on), `--errors` (default on), `--sql`, `--cache`, `--trace`.

```bash
gofasta debug watch
gofasta debug watch --sql --cache
gofasta debug watch | jq -c 'select(.event == "request" and .status >= 500)'
```

Heartbeat events fire every 30 seconds during idle periods so downstream pipelines know the stream is still live.

### `gofasta debug profile `

Download a pprof profile. Output goes to stdout unless `--output=` is set.

**Kinds:** `cpu`, `heap`, `goroutine`, `mutex`, `block`, `allocs`, `threadcreate`, `trace`.

**Flags:** `--duration=` (for timed profiles — `cpu` defaults to 30s, `trace` to 5s), `--output=`.

```bash
gofasta debug profile cpu --duration=30s -o cpu.pprof
gofasta debug profile heap -o heap.pprof
go tool pprof -http=:8090 cpu.pprof
```

### `gofasta debug har`

Export the request ring as HAR 1.2 JSON. Import into Chrome DevTools (Network → right-click → Import HAR), Insomnia, Postman, or any HAR viewer.

**Flags:** `--output=`.

```bash
gofasta debug har -o session.har
gofasta debug har --json | jq '.log.entries | length'
```

## Related

- [`gofasta dev`](/docs/cli-reference/dev) — starts the dev server with the `devtools` build tag active.
- [Debugging guide](/docs/guides/debugging/overview) — full walkthrough, including how the devtools package hooks into the scaffold without dirtying the developer's code.

---

## /docs/cli-reference/ai — gofasta ai

> Install per-agent configuration for Claude Code, Cursor, OpenAI Codex, Aider, and Windsurf — permission allowlists, hooks, project rules, and slash commands. Opt-in, idempotent, and extends to any MCP-aware successor.

# gofasta ai

Install project-specific configuration for AI coding agents — permission allowlists, hooks, conventions files, and slash commands. Every gofasta project ships [`AGENTS.md`](https://agents.md/) by default (the universal file every modern agent reads); per-agent configuration is opt-in via this command so developers who don't use AI agents aren't cluttered with dotfiles they don't need.

Every installer is **idempotent** — re-running after a gofasta update refreshes the config without touching files you've edited.

## Usage

```bash
gofasta ai  [--dry-run] [--force]
gofasta ai list
gofasta ai status
```

Inherits the global `--json` flag for machine-parseable output.

## Flags

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--dry-run` | bool | `false` | Preview what would be written without touching disk |
| `--force` | bool | `false` | Overwrite existing files whose contents differ from the template |

## Supported agents

| Key | Agent | What `gofasta ai ` installs |
|-----|-------|---------------------------------|
| `claude` | [Claude Code](https://claude.com/claude-code) | `.claude/settings.json` (project permissions), `.claude/hooks/pre-commit.sh` (runs `gofasta verify`), `.claude/commands/` (project slash commands: `/verify`, `/scaffold`, `/inspect`) |
| `cursor` | [Cursor](https://cursor.sh) | `.cursor/rules/gofasta.mdc` (project rules referencing AGENTS.md) |
| `codex` | [OpenAI Codex](https://developers.openai.com/codex/) | `.codex/config.toml` (command allowlist pointing at AGENTS.md) |
| `aider` | [Aider](https://aider.chat/) | `.aider.conf.yml` (auto-test + auto-lint) + `.aider/CONVENTIONS.md` |
| `windsurf` | [Windsurf](https://codeium.com/windsurf) | `.windsurfrules` (project rules) |

## Subcommands

### `gofasta ai list`

Prints every supported agent:

```bash
$ gofasta ai list
KEY        NAME           DESCRIPTION
claude     Claude Code    Anthropic's official CLI coding agent
cursor     Cursor         AI-first IDE with project-level rules and MCP support
codex      OpenAI Codex   OpenAI's coding agent — reads AGENTS.md by default
aider      Aider          Open-source pair-programming CLI agent
windsurf   Windsurf       Codeium's AI-native IDE
```

### `gofasta ai status`

Shows which agents are currently installed in this project, when they were installed, and which CLI version wrote the templates:

```bash
$ gofasta ai status
AGENT    NAME          INSTALLED AT           CLI VERSION
claude   Claude Code   2026-04-18 15:04 UTC   v0.2.0
cursor   Cursor        2026-04-18 15:06 UTC   v0.2.0
```

## Examples

Install Claude Code config:

```bash
gofasta ai claude
```

Preview what Cursor would install without writing:

```bash
gofasta ai cursor --dry-run
```

Install Aider and overwrite any previously-edited file:

```bash
gofasta ai aider --force
```

List supported agents in JSON:

```bash
gofasta ai list --json
```

## Idempotency + upgrade path

Re-running `gofasta ai ` after a gofasta release is safe:

- **Unchanged files:** skipped silently.
- **Files we wrote last time that we now want to update:** overwritten if `--force`, else halt with an error pointing at the diff.
- **Files you modified after install:** respected — no overwrite without `--force`.

Tracked via `.gofasta/ai.json` — a small manifest that records the installed CLI version per agent. `gofasta ai status` reads it.

## Output shape (JSON)

```json
{
  "agent": "claude",
  "created": [
    ".claude/settings.json",
    ".claude/hooks/pre-commit.sh",
    ".claude/commands/verify.md",
    ".claude/commands/scaffold.md",
    ".claude/commands/inspect.md"
  ],
  "skipped": [],
  "replaced": [],
  "would_replace": []
}
```

## Why this exists

Every modern AI coding agent has its own preferred configuration format — Claude Code reads `.claude/`, Cursor reads `.cursor/rules/`, Aider reads `.aider.conf.yml`, OpenAI Codex reads `AGENTS.md` + `.codex/`, Windsurf reads `.windsurfrules`. Pre-shipping all of them in the scaffold clutters projects for developers who don't use agents; shipping none means every user has to write them from scratch.

The installer is the middle path: scaffold stays clean (just `AGENTS.md`), agent-specific files land only when you explicitly ask. Adding a sixth, seventh, or tenth agent later is a template directory — zero changes to the CLI.

## Related

- [AGENTS.md](https://agents.md/) -- the universal agent guidance format (shipped in every scaffold by default)
- [llms.txt](https://gofasta.dev/llms.txt) -- LLM-optimized docs index for gofasta itself
- [`gofasta verify`](/docs/cli-reference/verify) -- pre-commit check invoked by agent hooks

---

## /docs/cli-reference/verify — gofasta verify

> Run the full preflight gauntlet — gofmt, vet, golangci-lint, tests, build, Wire drift, routes — in one command.

# gofasta verify

Runs every quality gate that CI runs, in order, and fails fast on the first failure. Acts as the single "am I done?" check for both humans and AI coding agents — one command, structured JSON output, non-zero exit on any check failure.

## Usage

```bash
gofasta verify [flags]
```

Run this command from the root directory of your Gofasta project.

## Flags

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--no-lint` | bool | `false` | Skip golangci-lint (useful on a machine without it installed) |
| `--no-race` | bool | `false` | Skip the race detector in `go test` |
| `--keep-going` | bool | `false` | Continue after the first failure and report every result |

Inherits the global `--json` flag to emit one JSON object per check, suitable for agent consumption and CI automation.

## Steps, in order

1. **`gofmt -s -l .`** — formatting
2. **`go vet ./...`** — compiler static checks
3. **`golangci-lint run`** — aggregate linter (skipped if not on `$PATH`)
4. **`go test -race ./...`** — tests with the race detector
5. **`go build ./...`** — every package compiles
6. **Wire drift** — `app/di/wire_gen.go` is in sync with its inputs
7. **Routes** — `app/rest/routes/` parses and has at least one entry

Each step blocks the next; fail-fast is the default. Pass `--keep-going` to run every step regardless of failures.

## Examples

Full preflight, text output:

```bash
$ gofasta verify
  ✓ gofmt            (12ms)
  ✓ go vet           (240ms)
  ✓ golangci-lint    (3421ms)
  ✓ go test          (8920ms)
  ✓ go build         (1810ms)
  ✓ wire drift       (4ms)
  ✓ routes           (6ms)

7 passed · 0 failed · 0 skipped · 14413ms
```

Structured JSON for agent consumption:

```bash
$ gofasta verify --json | jq '.checks[] | select(.status == "fail")'
```

Skip the linter on a machine without golangci-lint installed:

```bash
gofasta verify --no-lint
```

Run every check and report every result (useful for diagnosing multiple issues at once):

```bash
gofasta verify --keep-going
```

## Output shape (JSON)

```json
{
  "checks": [
    { "name": "gofmt", "status": "pass", "duration_ms": 12 },
    { "name": "go vet", "status": "pass", "duration_ms": 240 },
    { "name": "go test", "status": "fail",
      "message": "tests failed",
      "output": "--- FAIL: TestFoo ...",
      "duration_ms": 3200 }
  ],
  "passed": 2,
  "failed": 1,
  "skipped": 0,
  "duration_ms": 3452
}
```

Fields are stable API — AI agents and CI pipelines consume them.

## Why this exists

Agents and humans commonly finish a task having run only one of the quality gates (usually `go test`). `gofasta verify` collapses every gate into one invocation, so the pre-commit check is always complete. Mirrors the `make preflight` target in the CLI and library repos — same contract, same mental model.

## Related

- [`gofasta do health-check`](/docs/cli-reference/do) -- runs `verify` + `status` together
- [`gofasta status`](/docs/cli-reference/status) -- project drift report (complementary to `verify`)
- [Testing guide](/docs/guides/testing) -- how to write tests `verify` will run

---

## /docs/cli-reference/status — gofasta status

> Report the health of the current project — Wire drift, swagger drift, pending migrations, uncommitted generated files, module freshness.

# gofasta status

Runs a set of offline, filesystem-only health checks that answer the question an AI agent asks most often: **"is this project in a clean, up-to-date state?"** Output is a structured report — one row per check — with details that tell the agent (or human) exactly what's out of sync and which command brings it back.

## Usage

```bash
gofasta status [flags]
```

Run this command from the root directory of your Gofasta project. Inherits the global `--json` flag for machine-parseable output.

## Checks

| # | Check | What it reports |
|---|-------|-----------------|
| 1 | Wire drift | Is `app/di/wire_gen.go` older than any `.go` file in `app/di/`? |
| 2 | Swagger drift | Is `docs/swagger.json` older than any controller? |
| 3 | Pending migrations (offline) | Count of `.up.sql` files in `db/migrations/` |
| 4 | Uncommitted generated files | Does git think `wire_gen.go`, `swagger.json`, or generated resolvers differ from HEAD? |
| 5 | go.sum freshness | Does `go mod verify` succeed? |

Checks that don't apply to the current project (no Wire, no Swagger, no git) are reported as `"skip"` rather than failing.

## Statuses

| Status | Meaning |
|--------|---------|
| `ok` | Check passed |
| `drift` | Derived artifact is out of sync — run the remediation command shown in the message |
| `warn` | Something to be aware of (e.g. pending migrations exist) but not necessarily broken |
| `skip` | Check doesn't apply to this project |

Any check with status `drift` causes a non-zero exit so CI and agents can branch on success/failure.

## Examples

Default text output:

```bash
$ gofasta status
✓  wire drift                    in sync
✗  swagger drift                 docs/swagger.json is stale — run `gofasta swagger`
!  pending migrations            3 migration(s) present — run `gofasta migrate up` to apply (offline check)
✓  uncommitted generated files   generated files committed
✓  go.sum freshness              modules verified

4 ok · 1 drift · 1 warnings · 0 skipped
  · swagger drift: controllers newer than swagger.json: user.controller.go
```

Structured JSON:

```bash
$ gofasta status --json | jq '.checks[] | select(.status == "drift") | .message'
"docs/swagger.json is stale — run `gofasta swagger`"
```

## Output shape (JSON)

```json
{
  "checks": [
    { "name": "wire drift", "status": "ok", "message": "in sync" },
    { "name": "swagger drift", "status": "drift",
      "message": "docs/swagger.json is stale — run `gofasta swagger`",
      "detail": ["controllers newer than swagger.json: user.controller.go"] }
  ],
  "ok": 1,
  "drift": 1,
  "warnings": 0,
  "skipped": 0
}
```

Fields are stable API.

## Why this exists

After any round of code generation or manual edits, several derived artifacts (Wire, Swagger) can drift from their inputs without any warning — the code still compiles, but routes go missing or DTOs become stale in the API docs. `gofasta status` surfaces every drift in one command so agents checking their own work get a structured report instead of running five separate checks.

## Related

- [`gofasta verify`](/docs/cli-reference/verify) -- quality gates (complementary to drift detection)
- [`gofasta do health-check`](/docs/cli-reference/do) -- runs `verify` + `status` together
- [`gofasta wire`](/docs/cli-reference/wire) -- remediation for Wire drift
- [`gofasta swagger`](/docs/cli-reference/swagger) -- remediation for Swagger drift
- [`gofasta migrate up`](/docs/cli-reference/migrate) -- remediation for pending migrations

---

## /docs/cli-reference/inspect — gofasta inspect

> Show the full composition of a resource — model fields, DTOs, service methods, controller methods, routes — as structured output.

# gofasta inspect

Inspects a generated resource and emits a structured description of everything that belongs to it. Parses Go source files with the stdlib `go/parser`, not regex, so the output stays accurate even when file formatting varies.

Intended for AI agents and humans who need to understand a resource's shape before modifying it — one command replaces opening six files and squinting at field names and method signatures.

## Usage

```bash
gofasta inspect  [flags]
```

The resource name is the PascalCase model type name. Run from the project root.

## What it checks

For a resource like `User` (snake_case `user`), `inspect` reads:

| File | Content extracted |
|------|-------------------|
| `app/models/user.model.go` | Struct fields + types + GORM tags |
| `app/dtos/user.dtos.go` | Every declared DTO type + its fields |
| `app/services/interfaces/user_service.go` | Service interface method signatures |
| `app/rest/controllers/user.controller.go` | Public controller method signatures |
| `app/rest/routes/user.routes.go` | Registered HTTP routes (method + path) |

Missing files are reported as null/empty fields in the JSON payload and omitted from the text output — the command reports what it finds, not what it expects.

## Examples

Text output:

```bash
$ gofasta inspect User
Resource: User (user)

Model (app/models/user.model.go)
  models.BaseModelImpl   models.BaseModelImpl
  FirstName              string
  OtherNames             string
  Email                  string
  PhoneNumber            string
  Password               string

DTOs
  User (11 field(s))
  TUserResponseDto (2 field(s))
  TUsersResponseDto (2 field(s))
  TCreateUserDto (5 field(s))
  ...

Service methods
  FindUsersWithFilters(ctx context.Context, filters dtos.UserFiltersDto) *dtos.TUsersResponseDto, error
  CreateUser(ctx context.Context, input dtos.TCreateUserDto) *dtos.TUserResponseDto, error
  ...

Routes
  GET     /api/v1/users
  POST    /api/v1/users
  GET     /api/v1/users/{id}
  PUT     /api/v1/users/{id}
  DELETE  /api/v1/users/{id}

Files
  app/models/user.model.go
  app/dtos/user.dtos.go
  app/services/interfaces/user_service.go
  app/rest/controllers/user.controller.go
  app/rest/routes/user.routes.go
```

Structured JSON for agents:

```bash
$ gofasta inspect User --json | jq '.service_methods[].name'
"FindUsersWithFilters"
"CreateUser"
"UpdateUser"
"FindUserByID"
"ArchiveUser"
```

## Output shape (JSON)

```json
{
  "name": "User",
  "snake": "user",
  "model": {
    "file": "app/models/user.model.go",
    "fields": [
      { "name": "FirstName", "type": "string", "tag": "gorm:\"size:100\"" }
    ]
  },
  "dtos": [
    { "file": "app/dtos/user.dtos.go", "name": "TCreateUserDto", "fields": [...] }
  ],
  "routes": [
    { "method": "GET", "path": "/api/v1/users", "file": "user.routes.go" }
  ],
  "service_methods": [
    { "name": "CreateUser", "signature": "CreateUser(ctx context.Context, ...)" }
  ],
  "controller_methods": [ ... ],
  "files": [ "app/models/user.model.go", ... ]
}
```

Fields are stable API.

## Why this exists

An agent asked to "add a SoftArchive endpoint to the Order resource" needs to know:

- Does the Order service already have an Archive method it should reuse?
- What's the shape of `TOrderArchiveDto`?
- Is there already a DELETE route, or does it need to add one?
- Which fields does the model have?

Answering those from scratch means opening 5 files. `gofasta inspect Order` returns it all at once, in a machine-parseable shape the agent can reason over.

## Related

- [`gofasta routes`](/docs/cli-reference/routes) -- list every route across all resources
- [`gofasta generate`](/docs/cli-reference/generate/scaffold) -- generate resources inspect can then inspect
- [Project Structure](/docs/getting-started/project-structure) -- where each artifact lives

---

## /docs/cli-reference/do — gofasta do

> Run a named development workflow — a pre-defined chain of gofasta commands that together accomplish one higher-level task.

# gofasta do

Development workflows are named sequences of gofasta sub-commands that together accomplish one higher-level task. Each one is a transparent chain — no hidden logic, no extra state, no side effects beyond what the individual commands already do. Running a workflow is equivalent to typing its commands in order; the wrapper exists to save keystrokes, document common sequences, and give CI and AI agents atomic named steps to invoke.

## Usage

```bash
gofasta do  [args...] [--dry-run]
gofasta do list
```

Inherits the global `--json` flag for machine-parseable output.

## Flags

| Flag | Type | Default | Description |
|------|------|---------|-------------|
| `--dry-run` | bool | `false` | Print the commands the workflow would run without executing them |

## Registered workflows

| Workflow | Arguments | What it chains |
|----------|-----------|----------------|
| `new-rest-endpoint` | ` [field:type ...]` | `g scaffold` → `migrate up` → `swagger` |
| `rebuild` | — | `wire` → `swagger` |
| `fresh-start` | — | `init` → `migrate up` → `seed` |
| `clean-slate` | — | `db reset` → `seed` |
| `health-check` | — | `verify` → `status` |
| `list` | — | Print every supported workflow |

## Examples

List every workflow:

```bash
$ gofasta do list
WORKFLOW            ARGS                              DESCRIPTION
new-rest-endpoint    [field:type ...]   Scaffold a REST resource, apply its migration, regenerate Swagger
rebuild             —                                 Regenerate every derived artifact (Wire + Swagger)
fresh-start         —                                 First-time project setup after `git clone` — install tool deps, migrate, seed
clean-slate         —                                 Reset the dev database to a known state — drop + re-migrate + re-seed
health-check        —                                 Run `verify` + `status` together — the full project health report
```

Add a full REST endpoint with one command:

```bash
gofasta do new-rest-endpoint Invoice total:float status:string
```

…runs (in order):

1. `gofasta g scaffold Invoice total:float status:string`
2. `gofasta migrate up`
3. `gofasta swagger`

Preview what a workflow would do without executing:

```bash
$ gofasta do new-rest-endpoint Invoice total:float --dry-run

Dry run — workflow "new-rest-endpoint" would execute:

  · scaffold resource (gofasta g scaffold Invoice total:float)
  · apply migrations (gofasta migrate up)
  · regenerate Swagger (gofasta swagger)

No commands were executed. Re-run without --dry-run to apply.
```

Fresh-clone setup in one command:

```bash
git clone https://example.com/myorg/myapp.git
cd myapp
gofasta do fresh-start
```

Full project health check (used as a pre-commit gate):

```bash
gofasta do health-check
```

Structured JSON output for CI:

```bash
$ gofasta do rebuild --json | jq '.steps[] | {description, status, duration_ms}'
{ "description": "regenerate Wire", "status": "ok", "duration_ms": 1820 }
{ "description": "regenerate Swagger", "status": "ok", "duration_ms": 430 }
```

## Output shape (JSON)

```json
{
  "workflow": "new-rest-endpoint",
  "status": "ok",
  "dry_run": false,
  "steps": [
    {
      "description": "scaffold resource",
      "command": ["gofasta", "g", "scaffold", "Invoice", "total:float"],
      "status": "ok",
      "duration_ms": 820
    }
  ],
  "duration_ms": 12340
}
```

Status values: `ok`, `failed`, `planned` (for dry-runs). Fields are stable API.

## Behavior on failure

If any step fails, the chain stops and `gofasta do` emits a partial result with the failing step highlighted. The remaining steps are not attempted — workflows are strictly sequential.

```json
{
  "workflow": "new-rest-endpoint",
  "status": "failed",
  "steps": [
    { "description": "scaffold resource", "status": "ok", "duration_ms": 820 },
    { "description": "apply migrations", "status": "failed",
      "exit_code": 1,
      "error": "migration failed: relation already exists" }
  ]
}
```

## What `gofasta do` does NOT do

Being explicit about the limits:

- **No templating, no code generation, no AST work.** The wrapper never reads your Go files.
- **No undo.** If `migrate up` succeeds and `swagger` fails, the migration stays applied (same as if you'd run them individually).
- **No parallelism.** Steps run strictly sequentially.
- **No re-entry.** Workflows can't invoke other workflows — just sub-commands.
- **No environment magic.** Flags, env vars, working directory all inherit unchanged from the outer `gofasta do` invocation.

## Why this exists

Three concrete wins:

1. **Fewer round-trips for AI agents.** An agent asked to "add an Invoice resource" currently has to run three separate commands, each a separate tool invocation. `gofasta do new-rest-endpoint` = one tool call.
2. **Named atomic steps for CI.** Pipelines doing "freshly apply generated artifacts" can call `gofasta do rebuild` as one stable step.
3. **Discoverability.** `gofasta do list` surfaces every supported workflow, so new users don't have to read docs to find the common sequences.

## Related

- [`gofasta verify`](/docs/cli-reference/verify) -- the full preflight gauntlet
- [`gofasta status`](/docs/cli-reference/status) -- project drift report
- [`gofasta g scaffold`](/docs/cli-reference/generate/scaffold) -- the core of `new-rest-endpoint`
- [`gofasta db`](/docs/cli-reference/db) -- the core of `clean-slate`

---

## /docs/cli-reference/config — gofasta config

> Inspect and validate your project's config.yaml — currently the schema emitter, more subcommands to come.

# gofasta config

Tools that operate on the project's configuration surface.

## Subcommands

| Command | Description |
|---------|-------------|
| [`gofasta config schema`](#gofasta-config-schema) | Emit the JSON Schema (Draft 7) describing `config.yaml` |

## `gofasta config schema`

Emits a [JSON Schema (Draft 7)](https://json-schema.org/draft-07/schema) describing `config.yaml`. The schema is generated by reflecting over the `AppConfig` type in the gofasta library version your project pins — there is no second source of truth to keep in sync.

### Usage

```bash
gofasta config schema
```

### Examples

Print to stdout:

```bash
gofasta config schema
```

Save to a file for editor consumption:

```bash
gofasta config schema > config.schema.json
```

Inspect one section with `jq`:

```bash
gofasta config schema | jq '.properties.database'
```

### Intended consumers

**1. Editor autocomplete + inline validation.** Add one line to the top of your `config.yaml`:

```yaml
# yaml-language-server: $schema=./config.schema.json
```

With the VS Code YAML extension (or JetBrains equivalent) installed, you get:

- Autocomplete on field names (type `databas` and the editor suggests `database`)
- Red underlines on type errors (putting `port: "abc"` shows *"Expected integer"*)
- Required-field warnings in the Problems panel
- Enum hints on fields like `database.driver` (suggests `postgres`, `mysql`, `sqlite`, `sqlserver`, `clickhouse`)

**2. CI validation before deploy.** Catches typos and wrong types before they hit production:

```bash
gofasta config schema > schema.json
ajv validate -s schema.json -d config.yaml
```

**3. AI coding agents editing `config.yaml`.** An agent can fetch the schema to know — from the project's exact pinned gofasta version — which keys are valid, what their types are, and what values are allowed. Replaces guessing from potentially-stale training data.

### How it works

Under the hood, `gofasta config schema` shells out to a tiny helper binary the scaffold ships at `cmd/schema/main.go`:

```go
package main

import (
    "encoding/json"
    "os"
    "github.com/gofastadev/gofasta/pkg/config"
)

func main() {
    enc := json.NewEncoder(os.Stdout)
    enc.SetIndent("", "  ")
    _ = enc.Encode(config.JSONSchema())
}
```

The helper runs inside your project's Go module context, so it imports the exact `github.com/gofastadev/gofasta/pkg/config` version pinned in your `go.mod`. **No version skew between the CLI and the installed library — the schema always matches what your app actually loads.**

You can also invoke the helper directly without the CLI:

```bash
go run ./cmd/schema > config.schema.json
```

### Output shape

A standard Draft-7 JSON Schema document:

```json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "AppConfig",
  "description": "Gofasta application configuration. Loaded from config.yaml and overridden by environment variables prefixed with the project name (e.g. MYAPP_DATABASE_HOST).",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "database": {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "driver": { "type": "string" },
        "host": { "type": "string" },
        "port": { "type": "string" },
        "max_idle": { "type": "integer" },
        "max_life": {
          "type": "string",
          "format": "duration",
          "description": "A Go time.Duration expressed as a string (e.g. \"30s\", \"5m\", \"24h\")."
        }
      }
    }
  }
}
```

### Field-tag conventions honored

When the gofasta library adds struct tags to config fields, the emitter translates them:

| Struct tag | JSON Schema output |
|------------|--------------------|
| `koanf:"name"` | Field name (falls back to lowercase Go field name if missing) |
| `validate:"required"` | Field appears in the parent's `required[]` |
| `validate:"oneof=a b c"` | Field schema gets `enum: ["a","b","c"]` |
| `desc:"..."` | Field schema gets a `description` |

## Related

- [Configuration guide](/docs/guides/configuration) -- what each config section does
- [`pkg/config` reference](/docs/api-reference/config) -- programmatic access
- [`gofasta do rebuild`](/docs/cli-reference/do) -- regenerate every derived artifact

---

## /docs/cli-reference/serve — gofasta serve

> Start the Gofasta production HTTP server.

# gofasta serve

Starts the HTTP server by delegating to `go run ./app/main serve`. Unlike `gofasta dev`, this command does not include hot reload or file watching.

## Usage

```bash
gofasta serve
```

Run this command from the root directory of your Gofasta project.

This command takes no flags.

## Examples

Start the server:

```bash
gofasta serve
```

## How It Works

The `gofasta serve` command simply delegates to:

```bash
go run ./app/main serve
```

All server configuration (port, host, environment, etc.) is controlled through `config.yaml` and environment variables, not through CLI flags.

## Production Deployment

For production, you typically build the binary first and then run it:

```bash
go build -o server ./app/main
./server serve
```

Or use the provided Docker setup:

```bash
docker build -t myapp .
docker run -p 8080:8080 myapp
```

## Available Endpoints

Once the server is running, the following endpoints are available:

| Endpoint | URL |
|----------|-----|
| REST API | `http://localhost:8080/api/v1/` |
| GraphQL | `http://localhost:8080/graphql` |
| Health Check | `http://localhost:8080/health` |
| Swagger UI | `http://localhost:8080/swagger/index.html` |

## Related

- [gofasta dev](/docs/cli-reference/dev) -- start the development server with hot reload
- [gofasta migrate](/docs/cli-reference/migrate) -- run migrations before starting the server
- [gofasta seed](/docs/cli-reference/seed) -- seed the database before starting

---

## /docs/cli-reference/generate/scaffold — scaffold

> Generate a complete CRUD resource with all layers including model, repository, service, controller, routes, DTOs, migration, and DI provider.

# gofasta g scaffold

Generates a full CRUD resource spanning all layers of the application. This is the most powerful generator command -- it creates 11 new files and patches 4 existing files to wire everything together. After running this command and applying migrations, you have a fully working REST API for your resource.

**Aliases:** `s`

## Usage

```bash
gofasta g scaffold  [field:type ...] [flags]
```

`gofasta generate scaffold` also works. The `g` shorthand is equivalent to `generate`.

## Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--graphql` | `false` | Also generate GraphQL schema and wire up the resolver for this resource. This is separate from the `--graphql` flag on `gofasta new`, which enables GraphQL for the entire project. Your project must have been created with `gofasta new --graphql` for this flag to work. |
| `--gql` | `false` | Alias for `--graphql` |

## Field Types

| Type | Go Type | SQL Type (Postgres) | GraphQL Type |
|------|---------|-------------------|--------------|
| `string` | `string` | `VARCHAR(255)` | `String` |
| `text` | `string` | `TEXT` | `String` |
| `int` | `int` | `INTEGER` | `Int` |
| `float` | `float64` | `DECIMAL(10,2)` | `Float` |
| `bool` | `bool` | `BOOLEAN` | `Boolean` |
| `uuid` | `uuid.UUID` | `UUID` | `ID` |
| `time` | `time.Time` | `TIMESTAMP` | `DateTime` |

The `datetime` alias can be used in place of `time`. SQL types are generated per-driver (postgres, mysql, sqlite, sqlserver, clickhouse) based on the driver configured in `config.yaml`.

## Examples

Generate a Product resource with name and price fields:

```bash
gofasta g scaffold Product name:string price:float
```

Generate a BlogPost with multiple fields:

```bash
gofasta g scaffold BlogPost title:string body:text published:bool author_id:uuid published_at:time
```

Generate a resource with GraphQL support:

```bash
gofasta g scaffold Category name:string description:text --graphql
```

## What It Generates

Running `gofasta g scaffold Product name:string price:float` creates 11 files:

| File | Description |
|------|-------------|
| `app/models/product.model.go` | GORM database model |
| `db/migrations/000006_create_products.up.sql` | Create table migration |
| `db/migrations/000006_create_products.down.sql` | Drop table migration |
| `app/repositories/interfaces/product_repository.go` | Repository interface |
| `app/repositories/product.repository.go` | Repository implementation |
| `app/services/interfaces/product_service.go` | Service interface |
| `app/services/product.service.go` | Service implementation |
| `app/dtos/product.dtos.go` | Request/response DTOs |
| `app/di/providers/product.go` | Wire DI provider set |
| `app/rest/controllers/product.controller.go` | REST controller with CRUD handlers |
| `app/rest/routes/product.routes.go` | Route definitions |

It also patches 4 existing files:

| File | Change |
|------|--------|
| `app/di/container.go` | Adds `ProductService` and `ProductController` fields |
| `app/di/wire.go` | Adds `ProductSet` to the Wire build |
| `app/rest/routes/index.routes.go` | Registers Product routes |
| `cmd/serve.go` | Wires `ProductController` into the route config |

After generation, Wire regeneration is run automatically.

With `--graphql`, the scaffold additionally:

- Generates a GraphQL schema file (`.gql`)
- Patches `resolver.go` to add the service dependency
- Runs `gqlgen generate`

## Generated Code Examples

### Model

```go
// app/models/product.model.go
package models

import (
    "time"
    "github.com/google/uuid"
    "gorm.io/gorm"
)

type Product struct {
    ID        uuid.UUID      `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
    Name      string         `gorm:"type:varchar(255);not null" json:"name"`
    Price     float64        `gorm:"type:decimal(10,2);not null" json:"price"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
}
```

### Controller (excerpt)

```go
// app/rest/controllers/product.controller.go
package controllers

import (
    "net/http"

    "myapp/app/dtos"
    svcInterfaces "myapp/app/services/interfaces"
    "myapp/pkg/httputil"
)

type ProductController struct {
    service svcInterfaces.ProductService
}

func NewProductController(service svcInterfaces.ProductService) *ProductController {
    return &ProductController{service: service}
}

// Create handles POST /api/v1/products
func (c *ProductController) Create(w http.ResponseWriter, r *http.Request) error {
    var req dtos.CreateProductRequest
    // parse and validate request body
    // ...
    result, err := c.service.Create(req)
    if err != nil {
        return err
    }
    return httputil.WriteJSON(w, http.StatusCreated, result)
}
```

### Routes (excerpt)

```go
// app/rest/routes/product.routes.go
package routes

import (
    "github.com/go-chi/chi/v5"
    "myapp/app/rest/controllers"
    "myapp/pkg/httputil"
)

func RegisterProductRoutes(r chi.Router, controller *controllers.ProductController) {
    r.Post("/products", httputil.Handle(controller.Create))
    r.Get("/products", httputil.Handle(controller.FindAll))
    r.Get("/products/{id}", httputil.Handle(controller.FindByID))
    r.Put("/products/{id}", httputil.Handle(controller.Update))
    r.Delete("/products/{id}", httputil.Handle(controller.Delete))
}
```

## After Running

After generating a scaffold, complete these steps:

1. Run the migration: `gofasta migrate up`
2. Regenerate Wire (if not done automatically): `gofasta wire`
3. Test the endpoints with curl or your preferred API client

## Related

- [gofasta g model](/docs/cli-reference/generate/model) -- generate only the model
- [gofasta g controller](/docs/cli-reference/generate/controller) -- generate only the controller
- [gofasta g migration](/docs/cli-reference/generate/migration) -- generate only migration files
- [gofasta migrate](/docs/cli-reference/migrate) -- run the generated migrations
- [Quick Start](/docs/getting-started/quick-start)

---

## /docs/cli-reference/generate/model — model

> Generate a GORM database model with typed fields and a corresponding migration pair.

# gofasta g model

Generates a GORM database model file and a SQL migration pair (up + down) with the specified fields and types. The model includes standard fields (`ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`) automatically, plus any custom fields you define.

Use this command when you need only the model and migration without the full scaffold. For a complete resource with all layers, use `gofasta g scaffold` instead.

## Usage

```bash
gofasta g model  [field:type ...]
```

This command has no flags.

## Field Types

| Type | Go Type | GORM Tag |
|------|---------|----------|
| `string` | `string` | `type:varchar(255);not null` |
| `text` | `string` | `type:text` |
| `int` | `int` | `type:integer;not null` |
| `float` | `float64` | `type:decimal(10,2);not null` |
| `bool` | `bool` | `type:boolean;default:false` |
| `uuid` | `uuid.UUID` | `type:uuid` |
| `time` | `time.Time` | `type:timestamp` |

The `datetime` alias can be used in place of `time`.

## Examples

Generate a Product model:

```bash
gofasta g model Product name:string price:float
```

Generate a model with multiple field types:

```bash
gofasta g model Article title:string body:text published:bool view_count:int
```

## What It Generates

Running `gofasta g model Product name:string price:float` creates three files:

| File | Description |
|------|-------------|
| `app/models/product.model.go` | GORM database model |
| `db/migrations/000006_create_products.up.sql` | Create table migration |
| `db/migrations/000006_create_products.down.sql` | Drop table migration |

### Generated Code

```go
// app/models/product.model.go
package models

import (
    "time"

    "github.com/google/uuid"
    "gorm.io/gorm"
)

type Product struct {
    ID        uuid.UUID      `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
    Name      string         `gorm:"type:varchar(255);not null" json:"name"`
    Price     float64        `gorm:"type:decimal(10,2);not null" json:"price"`
    CreatedAt time.Time      `json:"created_at"`
    UpdatedAt time.Time      `json:"updated_at"`
    DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at,omitempty"`
}
```

The model automatically includes:

- A UUID primary key with auto-generation
- `CreatedAt` and `UpdatedAt` timestamps managed by GORM
- `DeletedAt` for soft deletes with an index
- JSON tags using snake_case naming
- GORM tags for column type definitions

## Database Driver Compatibility

Field type mappings adapt automatically based on the database driver configured in `config/config.yaml`:

| Field Type | Postgres | MySQL | SQLite | SQL Server | ClickHouse |
|------------|----------|-------|--------|------------|------------|
| `string` | `VARCHAR(255)` | `VARCHAR(255)` | `TEXT` | `NVARCHAR(255)` | `String` |
| `text` | `TEXT` | `LONGTEXT` | `TEXT` | `NVARCHAR(MAX)` | `String` |
| `int` | `INTEGER` | `INT` | `INTEGER` | `INT` | `Int64` |
| `float` | `DECIMAL(10,2)` | `DECIMAL(10,2)` | `REAL` | `DECIMAL(10,2)` | `Float64` |
| `bool` | `BOOLEAN` | `TINYINT(1)` | `INTEGER` | `BIT` | `UInt8` |
| `uuid` | `UUID` | `CHAR(36)` | `TEXT` | `UNIQUEIDENTIFIER` | `UUID` |
| `time` | `TIMESTAMP` | `DATETIME` | `DATETIME` | `DATETIME2` | `DateTime` |

## Related

- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate a full resource
- [gofasta g migration](/docs/cli-reference/generate/migration) -- generate migration files only
- [gofasta g repository](/docs/cli-reference/generate/repository) -- generate a repository for this model

---

## /docs/cli-reference/generate/repository — repository

> Generate a model, migration, repository interface, and repository implementation for database access.

# gofasta g repository

Generates a model, migration pair, repository interface, and GORM-based repository implementation for a given resource. The repository layer handles all database operations (CRUD queries) and is the only layer that interacts directly with the database.

**Aliases:** `repo`

## Usage

```bash
gofasta g repository  [field:type ...]
```

This command has no flags.

## Examples

Generate a repository with all CRUD methods:

```bash
gofasta g repository Product name:string price:float
```

Generate a repository for an article:

```bash
gofasta g repo Article title:string body:text published:bool
```

## What It Generates

Running `gofasta g repository Product name:string price:float` creates the following files:

| File | Description |
|------|-------------|
| `app/models/product.model.go` | GORM database model |
| `db/migrations/000006_create_products.up.sql` | Create table migration |
| `db/migrations/000006_create_products.down.sql` | Drop table migration |
| `app/repositories/interfaces/product_repository.go` | Repository interface |
| `app/repositories/product.repository.go` | GORM implementation |

### Repository Interface

```go
// app/repositories/interfaces/product_repository.go
package interfaces

import "myapp/app/models"

type ProductRepository interface {
    Create(product *models.Product) (*models.Product, error)
    FindAll(page, limit int) ([]models.Product, int64, error)
    FindByID(id string) (*models.Product, error)
    Update(id string, product *models.Product) (*models.Product, error)
    Delete(id string) error
}
```

### Repository Implementation

```go
// app/repositories/product.repository.go
package repositories

import (
    "myapp/app/models"
    repoInterfaces "myapp/app/repositories/interfaces"
    "gorm.io/gorm"
)

type ProductRepositoryImpl struct {
    db *gorm.DB
}

func NewProductRepository(db *gorm.DB) repoInterfaces.ProductRepository {
    return &ProductRepositoryImpl{db: db}
}

func (r *ProductRepositoryImpl) Create(product *models.Product) (*models.Product, error) {
    if err := r.db.Create(product).Error; err != nil {
        return nil, err
    }
    return product, nil
}

func (r *ProductRepositoryImpl) FindAll(page, limit int) ([]models.Product, int64, error) {
    var products []models.Product
    var total int64

    r.db.Model(&models.Product{}).Count(&total)
    err := r.db.Offset((page - 1) * limit).Limit(limit).Find(&products).Error
    return products, total, err
}

func (r *ProductRepositoryImpl) FindByID(id string) (*models.Product, error) {
    var product models.Product
    if err := r.db.Where("id = ?", id).First(&product).Error; err != nil {
        return nil, err
    }
    return &product, nil
}

func (r *ProductRepositoryImpl) Update(id string, product *models.Product) (*models.Product, error) {
    if err := r.db.Where("id = ?", id).Updates(product).Error; err != nil {
        return nil, err
    }
    return r.FindByID(id)
}

func (r *ProductRepositoryImpl) Delete(id string) error {
    return r.db.Where("id = ?", id).Delete(&models.Product{}).Error
}
```

## Architecture

The repository sits between the service layer and the database:

```
Controller --> Service --> Repository --> Database (GORM)
```

- Repositories receive a `*gorm.DB` instance via dependency injection
- All methods return domain models, not DTOs
- Soft deletes are used by default (via GORM's `DeletedAt` field)
- Pagination is handled with `Offset` and `Limit`

## Related

- [gofasta g model](/docs/cli-reference/generate/model) -- generate the model this repository operates on
- [gofasta g service](/docs/cli-reference/generate/service) -- generate the service that calls this repository
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers at once

---

## /docs/cli-reference/generate/service — service

> Generate a service interface and implementation containing business logic, along with all prerequisite layers.

# gofasta g service

Generates a service interface and its implementation for a given resource, along with all prerequisite layers (model, migration, repository interface, repository implementation, DTOs, and Wire provider). The service layer contains business logic and orchestrates calls to one or more repositories. Services never interact with the database directly -- they delegate data access to repositories.

**Aliases:** `svc`

## Usage

```bash
gofasta g service  [field:type ...] [flags]
```

## Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--graphql` | `false` | Also generate GraphQL schema and wire up the resolver |
| `--gql` | `false` | Alias for `--graphql` |

## Examples

Generate a service with all CRUD methods:

```bash
gofasta g service Product name:string price:float
```

Generate a service with GraphQL support:

```bash
gofasta g service Product name:string price:float --graphql
```

## What It Generates

Running `gofasta g service Product name:string price:float` creates the following files:

| File | Description |
|------|-------------|
| `app/models/product.model.go` | GORM database model |
| `db/migrations/000006_create_products.up.sql` | Create table migration |
| `db/migrations/000006_create_products.down.sql` | Drop table migration |
| `app/repositories/interfaces/product_repository.go` | Repository interface |
| `app/repositories/product.repository.go` | Repository implementation |
| `app/services/interfaces/product_service.go` | Service interface |
| `app/services/product.service.go` | Service implementation |
| `app/dtos/product.dtos.go` | Request/response DTOs |
| `app/di/providers/product.go` | Wire DI provider set |

It also patches 2 existing files:

| File | Change |
|------|--------|
| `app/di/container.go` | Adds `ProductService` field |
| `app/di/wire.go` | Adds `ProductSet` to the Wire build |

With `--graphql`, the service generator additionally:

- Generates a GraphQL schema file (`.gql`)
- Patches `resolver.go` to add the service dependency
- Runs `gqlgen generate`

### Service Interface

```go
// app/services/interfaces/product_service.go
package interfaces

import "myapp/app/dtos"

type ProductService interface {
    Create(req dtos.CreateProductRequest) (*dtos.ProductResponse, error)
    FindAll(page, limit int) (*dtos.PaginatedProductResponse, error)
    FindByID(id string) (*dtos.ProductResponse, error)
    Update(id string, req dtos.UpdateProductRequest) (*dtos.ProductResponse, error)
    Delete(id string) error
}
```

### Service Implementation

```go
// app/services/product.service.go
package services

import (
    "myapp/app/dtos"
    "myapp/app/models"
    repoInterfaces "myapp/app/repositories/interfaces"
    svcInterfaces "myapp/app/services/interfaces"
)

type ProductServiceImpl struct {
    repo repoInterfaces.ProductRepository
}

func NewProductService(repo repoInterfaces.ProductRepository) svcInterfaces.ProductService {
    return &ProductServiceImpl{repo: repo}
}

func (s *ProductServiceImpl) Create(req dtos.CreateProductRequest) (*dtos.ProductResponse, error) {
    product := &models.Product{
        Name:  req.Name,
        Price: req.Price,
    }

    created, err := s.repo.Create(product)
    if err != nil {
        return nil, err
    }

    return dtos.ToProductResponse(created), nil
}

func (s *ProductServiceImpl) FindAll(page, limit int) (*dtos.PaginatedProductResponse, error) {
    products, total, err := s.repo.FindAll(page, limit)
    if err != nil {
        return nil, err
    }

    return dtos.ToPaginatedProductResponse(products, total, page, limit), nil
}
```

## Architecture

The service sits between the controller and repository layers:

```
Controller --> Service --> Repository --> Database
```

- Services receive repositories via dependency injection (constructor injection)
- Services accept DTOs as input and return DTOs as output
- Services convert between DTOs and models internally
- Business rules, validations, and data transformations belong in the service layer

## Related

- [gofasta g repository](/docs/cli-reference/generate/repository) -- generate the repository this service calls
- [gofasta g controller](/docs/cli-reference/generate/controller) -- generate the controller that calls this service
- [gofasta g dto](/docs/cli-reference/generate/dto) -- generate the DTOs this service uses
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers at once

---

## /docs/cli-reference/generate/controller — controller

> Generate a REST controller with CRUD handlers and all prerequisite layers.

# gofasta g controller

Generates a REST controller for a given resource with full CRUD endpoint handlers, along with all prerequisite layers (model, migration, repository, service, DTOs, Wire provider, and routes). The controller delegates all business logic to the service layer and uses [go-chi/chi](https://github.com/go-chi/chi) for HTTP routing with `httputil.Handle()` for error-returning handlers. The router is an opt-out default — see [Swapping the router](/docs/guides/rest-api#swapping-the-router) for alternatives.

**Aliases:** `ctrl`

## Usage

```bash
gofasta g controller  [field:type ...] [flags]
```

## Flags

| Flag | Default | Description |
|------|---------|-------------|
| `--graphql` | `false` | Also generate GraphQL schema and wire up the resolver |
| `--gql` | `false` | Alias for `--graphql` |

## Examples

Generate a controller with all CRUD handlers:

```bash
gofasta g controller Product name:string price:float
```

Generate with GraphQL support:

```bash
gofasta g ctrl Product name:string price:float --graphql
```

## What It Generates

Running `gofasta g controller Product name:string price:float` generates everything from the service generator plus:

| File | Description |
|------|-------------|
| `app/rest/controllers/product.controller.go` | REST controller with CRUD handlers |
| `app/rest/routes/product.routes.go` | Route definitions |

It also patches these existing files:

| File | Change |
|------|--------|
| `app/di/container.go` | Adds `ProductService` and `ProductController` fields |
| `app/di/wire.go` | Adds `ProductSet` to the Wire build |
| `app/rest/routes/index.routes.go` | Registers Product routes |
| `cmd/serve.go` | Wires `ProductController` into the route config |

With `--graphql`, the controller generator additionally:

- Generates a GraphQL schema file (`.gql`)
- Patches `resolver.go` to add the service dependency
- Runs `gqlgen generate`

### Generated Code

```go
// app/rest/controllers/product.controller.go
package controllers

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/go-chi/chi/v5"
    "github.com/go-playground/validator/v10"
    "myapp/app/dtos"
    svcInterfaces "myapp/app/services/interfaces"
)

type ProductController struct {
    service svcInterfaces.ProductService
}

func NewProductController(service svcInterfaces.ProductService) *ProductController {
    return &ProductController{service: service}
}

// Create handles POST /api/v1/products
func (c *ProductController) Create(w http.ResponseWriter, r *http.Request) error {
    var req dtos.CreateProductRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        return err
    }

    result, err := c.service.Create(req)
    if err != nil {
        return err
    }

    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    return json.NewEncoder(w).Encode(result)
}

// FindAll handles GET /api/v1/products
func (c *ProductController) FindAll(w http.ResponseWriter, r *http.Request) error {
    page, _ := strconv.Atoi(r.URL.Query().Get("page"))
    limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
    if page < 1 {
        page = 1
    }
    if limit < 1 {
        limit = 10
    }

    result, err := c.service.FindAll(page, limit)
    if err != nil {
        return err
    }

    w.Header().Set("Content-Type", "application/json")
    return json.NewEncoder(w).Encode(result)
}

// FindByID handles GET /api/v1/products/{id}
func (c *ProductController) FindByID(w http.ResponseWriter, r *http.Request) error {
    id := chi.URLParam(r, "id")
    result, err := c.service.FindByID(id)
    if err != nil {
        return err
    }

    w.Header().Set("Content-Type", "application/json")
    return json.NewEncoder(w).Encode(result)
}
```

## REST Endpoints

The generated controller handles these endpoints (when paired with generated routes):

| Method | Path | Handler | Description |
|--------|------|---------|-------------|
| `POST` | `/api/v1/products` | `Create` | Create a new product |
| `GET` | `/api/v1/products` | `FindAll` | List all products (paginated) |
| `GET` | `/api/v1/products/{id}` | `FindByID` | Get a single product |
| `PUT` | `/api/v1/products/{id}` | `Update` | Update a product |
| `DELETE` | `/api/v1/products/{id}` | `Delete` | Delete a product |

## Architecture

The controller is the outermost layer in the application:

```
HTTP Request --> Controller --> Service --> Repository --> Database
```

- Controllers use standard `net/http` types (`http.ResponseWriter`, `*http.Request`)
- Handler methods return `error` and are wrapped with `httputil.Handle()`
- Route parameters are extracted via `chi.URLParam(r, "name")`
- Request validation uses `go-playground/validator`
- All business logic is delegated to the service layer

## Related

- [gofasta g service](/docs/cli-reference/generate/service) -- generate the service this controller calls
- [gofasta g dto](/docs/cli-reference/generate/dto) -- generate the DTOs used in request/response
- [gofasta g route](/docs/cli-reference/generate/route) -- generate route definitions for this controller
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers at once

---

## /docs/cli-reference/generate/dto — dto

> Generate request and response Data Transfer Objects (DTOs) for a resource.

# gofasta g dto

Generates Data Transfer Objects (DTOs) for a resource, including create request, update request, single response, and paginated response structs. DTOs define the shape of data exchanged between the client and the API, keeping internal model details separate from the API contract.

## Usage

```bash
gofasta g dto  [field:type ...]
```

This command has no flags.

## Examples

Generate all DTOs for a Product:

```bash
gofasta g dto Product name:string price:float
```

Generate DTOs with multiple field types:

```bash
gofasta g dto Article title:string body:text published:bool view_count:int
```

## What It Generates

Running `gofasta g dto Product name:string price:float` creates one file:

```
app/dtos/product.dtos.go
```

### Generated Code

```go
// app/dtos/product.dtos.go
package dtos

import (
    "time"
    "myapp/app/models"
)

// CreateProductRequest represents the request body for creating a product.
type CreateProductRequest struct {
    Name  string  `json:"name" validate:"required"`
    Price float64 `json:"price" validate:"required"`
}

// UpdateProductRequest represents the request body for updating a product.
type UpdateProductRequest struct {
    Name  *string  `json:"name,omitempty"`
    Price *float64 `json:"price,omitempty"`
}

// ProductResponse represents the API response for a single product.
type ProductResponse struct {
    ID        string    `json:"id"`
    Name      string    `json:"name"`
    Price     float64   `json:"price"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// PaginatedProductResponse represents a paginated list of products.
type PaginatedProductResponse struct {
    Data       []ProductResponse `json:"data"`
    Total      int64             `json:"total"`
    Page       int               `json:"page"`
    Limit      int               `json:"limit"`
    TotalPages int               `json:"total_pages"`
}

// ToProductResponse converts a Product model to a ProductResponse DTO.
func ToProductResponse(product *models.Product) *ProductResponse {
    return &ProductResponse{
        ID:        product.ID.String(),
        Name:      product.Name,
        Price:     product.Price,
        CreatedAt: product.CreatedAt,
        UpdatedAt: product.UpdatedAt,
    }
}

// ToPaginatedProductResponse converts a slice of Product models to a paginated response.
func ToPaginatedProductResponse(products []models.Product, total int64, page, limit int) *PaginatedProductResponse {
    var items []ProductResponse
    for _, p := range products {
        items = append(items, *ToProductResponse(&p))
    }

    totalPages := int(total) / limit
    if int(total)%limit != 0 {
        totalPages++
    }

    return &PaginatedProductResponse{
        Data:       items,
        Total:      total,
        Page:       page,
        Limit:      limit,
        TotalPages: totalPages,
    }
}
```

## Design Patterns

- **Create DTOs** use `validate:"required"` tags (go-playground/validator) for mandatory fields
- **Update DTOs** use pointer types (`*string`, `*float64`) so that omitted fields are not confused with zero values
- **Response DTOs** expose only the fields intended for the API consumer, hiding internal fields like `DeletedAt`
- **Converter functions** (`ToProductResponse`, `ToPaginatedProductResponse`) handle model-to-DTO transformation

## Related

- [gofasta g model](/docs/cli-reference/generate/model) -- generate the model that DTOs map to
- [gofasta g controller](/docs/cli-reference/generate/controller) -- generate the controller that uses these DTOs
- [gofasta g service](/docs/cli-reference/generate/service) -- generate the service that converts between DTOs and models
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers at once

---

## /docs/cli-reference/generate/migration — migration

> Generate up and down SQL migration files for a database table.

# gofasta g migration

Generates a pair of SQL migration files (up and down) for creating a database table. The migration number is automatically incremented based on existing migration files in the `db/migrations/` directory. Migration templates are driver-aware -- the generated SQL adapts to the database driver configured in `config.yaml`.

## Usage

```bash
gofasta g migration  [field:type ...]
```

This command has no flags.

## Examples

Generate a migration to create a products table:

```bash
gofasta g migration Product name:string price:float
```

Generate a migration with multiple field types:

```bash
gofasta g migration Article title:string body:text published:bool view_count:int author_id:uuid
```

## What It Generates

Running `gofasta g migration Product name:string price:float` creates two files:

| File | Description |
|------|-------------|
| `db/migrations/000006_create_products.up.sql` | SQL to create the table |
| `db/migrations/000006_create_products.down.sql` | SQL to drop the table |

The migration number (`000006`) is automatically determined by scanning existing migration files and incrementing.

### Up Migration

```sql
-- db/migrations/000006_create_products.up.sql
CREATE TABLE products (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name VARCHAR(255) NOT NULL,
    price DECIMAL(10,2) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
    deleted_at TIMESTAMP
);

CREATE INDEX idx_products_deleted_at ON products(deleted_at);
```

### Down Migration

```sql
-- db/migrations/000006_create_products.down.sql
DROP TABLE IF EXISTS products;
```

## Database Driver Adaptation

The generated SQL syntax adapts based on the database driver configured in `config/config.yaml`. For example, UUID generation differs across databases:

| Driver | UUID Default |
|--------|-------------|
| Postgres | `DEFAULT gen_random_uuid()` |
| MySQL | `DEFAULT (UUID())` |
| SQLite | handled at application level |
| SQL Server | `DEFAULT NEWID()` |
| ClickHouse | `DEFAULT generateUUIDv4()` |

## Running Migrations

After generating migration files, apply them with:

```bash
gofasta migrate up
```

To roll back:

```bash
gofasta migrate down
```

## Related

- [gofasta migrate](/docs/cli-reference/migrate) -- run migration files
- [gofasta g model](/docs/cli-reference/generate/model) -- generate the corresponding model
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers including migrations

---

## /docs/cli-reference/generate/route — route

> Generate REST route definitions for a resource.

# gofasta g route

Generates a route definition file that maps HTTP endpoints to controller handlers using [go-chi/chi](https://github.com/go-chi/chi) as the default router. The generated routes follow RESTful conventions with standard CRUD paths. The router is an opt-out default — see [Swapping the router](/docs/guides/rest-api#swapping-the-router) for alternatives.

## Usage

```bash
gofasta g route 
```

This command has no flags.

## Examples

Generate routes for a Product resource:

```bash
gofasta g route Product
```

## What It Generates

Running `gofasta g route Product` creates one file:

```
app/rest/routes/product.routes.go
```

### Generated Code

```go
// app/rest/routes/product.routes.go
package routes

import (
    "github.com/go-chi/chi/v5"
    "myapp/app/rest/controllers"
    "myapp/pkg/httputil"
)

func RegisterProductRoutes(r chi.Router, controller *controllers.ProductController) {
    r.Post("/products", httputil.Handle(controller.Create))
    r.Get("/products", httputil.Handle(controller.FindAll))
    r.Get("/products/{id}", httputil.Handle(controller.FindByID))
    r.Put("/products/{id}", httputil.Handle(controller.Update))
    r.Delete("/products/{id}", httputil.Handle(controller.Delete))
}
```

## Route Registration

The generated route file is registered in `app/rest/routes/index.routes.go`. When using `gofasta g scaffold`, this registration is patched automatically. When generating routes standalone, you need to add the registration manually:

```go
// app/rest/routes/index.routes.go
func RegisterRoutes(r chi.Router, container *di.Container) {
    RegisterUserRoutes(r, container.UserController)
    RegisterAuthRoutes(r, container.AuthController)
    RegisterProductRoutes(r, container.ProductController) // Add this line
}
```

## Generated Endpoints

| Method | Path | Handler |
|--------|------|---------|
| `POST` | `/api/v1/products` | `Create` |
| `GET` | `/api/v1/products` | `FindAll` |
| `GET` | `/api/v1/products/{id}` | `FindByID` |
| `PUT` | `/api/v1/products/{id}` | `Update` |
| `DELETE` | `/api/v1/products/{id}` | `Delete` |

## Related

- [gofasta g controller](/docs/cli-reference/generate/controller) -- generate the controller these routes point to
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers with automatic route registration

---

## /docs/cli-reference/generate/resolver — resolver

> Patch the GraphQL resolver to add a service dependency for a resource.

# gofasta g resolver

Patches the GraphQL resolver to add a service dependency for the given resource. This is used when you want to wire an existing service into the GraphQL layer.

> **Note:** This command requires a GraphQL-enabled project (created with `gofasta new --graphql`). If your project was created without the `--graphql` flag, the `app/graphql/` directory and `gqlgen.yml` will not exist, and this command will have nothing to patch.

## Usage

```bash
gofasta g resolver 
```

This command has no flags.

## Examples

Add Product service dependency to the resolver:

```bash
gofasta g resolver Product
```

## What It Does

Running `gofasta g resolver Product` patches the existing GraphQL resolver file to add the Product service as a dependency:

| File | Change |
|------|--------|
| `app/graphql/resolver.go` | Adds `productService` field and constructor parameter |

### Resolver Patch

The resolver struct is updated to include the new service dependency:

```go
// app/graphql/resolver.go
package graphql

import (
    svcInterfaces "myapp/app/services/interfaces"
)

type Resolver struct {
    userService    svcInterfaces.UserService
    productService svcInterfaces.ProductService // Added by gofasta g resolver
}
```

## When to Use

Use `gofasta g resolver` when you have already generated the service and GraphQL schema separately and need to wire them together. When using `gofasta g scaffold --graphql` or `gofasta g service --graphql`, the resolver patching is handled automatically.

## Related

- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers including GraphQL
- [gofasta g service](/docs/cli-reference/generate/service) -- generate the service the resolver calls
- [gofasta g dto](/docs/cli-reference/generate/dto) -- generate DTOs used by the resolver

---

## /docs/cli-reference/generate/provider — provider

> Generate a Google Wire dependency injection provider set for a resource.

# gofasta g provider

Generates a [Google Wire](https://github.com/google/wire) provider set file that defines how a resource's dependencies are wired together. The provider set binds interfaces to implementations for the repository, service, and controller layers. It also patches `container.go` and `wire.go` to register the new provider.

## Usage

```bash
gofasta g provider 
```

This command has no flags.

## Examples

Generate a provider for a Product resource:

```bash
gofasta g provider Product
```

## What It Generates

Running `gofasta g provider Product` creates one file and patches two:

| File | Description |
|------|-------------|
| `app/di/providers/product.go` | Wire DI provider set (created) |
| `app/di/container.go` | Adds resource fields (patched) |
| `app/di/wire.go` | Adds `ProductSet` to Wire build (patched) |

### Generated Code

```go
// app/di/providers/product.go
package providers

import (
    "github.com/google/wire"
    "myapp/app/repositories"
    repoInterfaces "myapp/app/repositories/interfaces"
    "myapp/app/services"
    svcInterfaces "myapp/app/services/interfaces"
    "myapp/app/rest/controllers"
)

var ProductSet = wire.NewSet(
    // Repository
    repositories.NewProductRepository,
    wire.Bind(new(repoInterfaces.ProductRepository), new(*repositories.ProductRepositoryImpl)),

    // Service
    services.NewProductService,
    wire.Bind(new(svcInterfaces.ProductService), new(*services.ProductServiceImpl)),

    // Controller
    controllers.NewProductController,
)
```

## How Wire Providers Work

Each provider set uses three Wire constructs:

| Construct | Purpose |
|-----------|---------|
| Constructor function (e.g., `NewProductRepository`) | Tells Wire how to create an instance |
| `wire.Bind(interface, implementation)` | Tells Wire which concrete type satisfies an interface |
| `wire.NewSet(...)` | Groups related providers into a reusable set |

The provider set is referenced in `app/di/wire.go`:

```go
//go:build wireinject

package di

import (
    "github.com/google/wire"
    "myapp/app/di/providers"
)

func InitializeContainer() (*Container, error) {
    wire.Build(
        providers.UserSet,
        providers.AuthSet,
        providers.ProductSet, // Added by scaffold or manually
        wire.Struct(new(Container), "*"),
    )
    return nil, nil
}
```

## Related

- [gofasta wire](/docs/cli-reference/wire) -- regenerate Wire DI code after adding providers
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate all layers with automatic provider registration
- [gofasta g repository](/docs/cli-reference/generate/repository) -- generate the repository bound in this provider
- [gofasta g service](/docs/cli-reference/generate/service) -- generate the service bound in this provider

---

## /docs/cli-reference/generate/job — job

> Generate a scheduled background job that implements the scheduler.Job interface.

# gofasta g job

Generates a background job file for handling recurring work such as sending emails, processing uploads, generating reports, or any task that should run on a schedule. Jobs implement the `scheduler.Job` interface with `Name()` and `Run()` methods, and use GORM for database access and `slog` for structured logging.

## Usage

```bash
gofasta g job  [schedule]
```

The optional second positional argument specifies a cron schedule expression. This command has no flags.

## Examples

Generate a job for sending welcome emails:

```bash
gofasta g job SendWelcomeEmail
```

Generate a job with a specific cron schedule:

```bash
gofasta g job GenerateMonthlyReport "0 0 1 * *"
```

Generate a job that runs every 5 minutes:

```bash
gofasta g job SyncExternalData "*/5 * * * *"
```

## What It Generates

Running `gofasta g job SendWelcomeEmail` creates one file and patches two:

| File | Description |
|------|-------------|
| `app/jobs/send_welcome_email.go` | Job implementation (created) |
| `cmd/serve.go` | Registers the job in the job registry (patched) |
| `config/config.yaml` | Adds the job schedule entry (patched) |

### Generated Code

```go
// app/jobs/send_welcome_email.go
package jobs

import (
    "context"
    "log/slog"

    "gorm.io/gorm"
)

// SendWelcomeEmailJob handles sending welcome emails on a schedule.
type SendWelcomeEmailJob struct {
    db *gorm.DB
}

// NewSendWelcomeEmailJob creates a new instance of SendWelcomeEmailJob.
func NewSendWelcomeEmailJob(db *gorm.DB) *SendWelcomeEmailJob {
    return &SendWelcomeEmailJob{db: db}
}

// Name returns the unique name of this job.
func (j *SendWelcomeEmailJob) Name() string {
    return "SendWelcomeEmail"
}

// Run executes the job logic.
func (j *SendWelcomeEmailJob) Run(ctx context.Context) error {
    slog.Info("Running SendWelcomeEmail job")

    // TODO: Implement job logic here

    return nil
}
```

## Job Interface

All generated jobs implement the `scheduler.Job` interface:

```go
type Job interface {
    Name() string
    Run(ctx context.Context) error
}
```

- `Name()` returns a unique identifier used for logging and the job registry
- `Run()` contains the job logic and receives a context for cancellation support

## Cron Expression Format

The schedule uses standard cron syntax with five fields:

```
* * * * *
| | | | |
| | | | +-- Day of week (0-6, Sunday=0)
| | | +---- Month (1-12)
| | +------ Day of month (1-31)
| +-------- Hour (0-23)
+---------- Minute (0-59)
```

| Expression | Description |
|------------|-------------|
| `0 * * * *` | Every hour at minute 0 |
| `0 0 * * *` | Daily at midnight |
| `0 9 * * 1` | Every Monday at 9:00 AM |
| `*/5 * * * *` | Every 5 minutes |
| `0 0 1 * *` | First day of every month at midnight |

## Related

- [gofasta g task](/docs/cli-reference/generate/task) -- generate an async task handler instead
- [gofasta g email-template](/docs/cli-reference/generate/email-template) -- generate an email template for email jobs
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate a full resource

---

## /docs/cli-reference/generate/task — task

> Generate an async task handler using hibiken/asynq.

# gofasta g task

Generates an async task handler file for processing work asynchronously using [hibiken/asynq](https://github.com/hibiken/asynq). Tasks are useful for offloading work from the HTTP request/response cycle, such as sending emails, processing uploads, or triggering external API calls.

## Usage

```bash
gofasta g task 
```

This command has no flags.

## Examples

Generate a task for cleaning expired sessions:

```bash
gofasta g task CleanExpiredSessions
```

Generate a task for sending notifications:

```bash
gofasta g task SendNotification
```

## What It Generates

Running `gofasta g task CleanExpiredSessions` creates one file:

```
app/tasks/clean_expired_sessions.go
```

### Generated Code

```go
// app/tasks/clean_expired_sessions.go
package tasks

import (
    "context"
    "encoding/json"

    "github.com/hibiken/asynq"
)

const TypeCleanExpiredSessions = "clean_expired_sessions"

// CleanExpiredSessionsPayload contains the data needed to execute this task.
type CleanExpiredSessionsPayload struct {
    // TODO: Add payload fields here
}

// HandleCleanExpiredSessions processes the CleanExpiredSessions task.
func HandleCleanExpiredSessions(ctx context.Context, t *asynq.Task) error {
    var payload CleanExpiredSessionsPayload
    if err := json.Unmarshal(t.Payload(), &payload); err != nil {
        return err
    }

    // TODO: Implement task logic here

    return nil
}

// EnqueueCleanExpiredSessions creates and enqueues a CleanExpiredSessions task.
func EnqueueCleanExpiredSessions(client *asynq.Client, payload CleanExpiredSessionsPayload) (*asynq.TaskInfo, error) {
    data, err := json.Marshal(payload)
    if err != nil {
        return nil, err
    }

    task := asynq.NewTask(TypeCleanExpiredSessions, data)
    return client.Enqueue(task)
}
```

## Task vs Job

| Feature | Task | Job |
|---------|------|-----|
| Library | hibiken/asynq | scheduler.Job interface |
| Trigger | Enqueued from code (manual) | Cron schedule (automatic) |
| Payload | Carries a custom payload | None (runs with context only) |
| Use case | Event-driven async processing | Periodic maintenance |
| Pattern | Handle function + Enqueue helper | Name() + Run() methods |

## Enqueuing Tasks

To enqueue a task from a service or controller:

```go
func (s *UserServiceImpl) Register(req dtos.RegisterRequest) (*dtos.UserResponse, error) {
    user, err := s.repo.Create(mapToUser(req))
    if err != nil {
        return nil, err
    }

    // Enqueue the async task
    _, err = tasks.EnqueueCleanExpiredSessions(s.asynqClient, tasks.CleanExpiredSessionsPayload{
        // populate fields
    })
    if err != nil {
        slog.Error("failed to enqueue task", "error", err)
    }

    return dtos.ToUserResponse(user), nil
}
```

## Related

- [gofasta g job](/docs/cli-reference/generate/job) -- generate a scheduled background job instead
- [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate a full resource
- [gofasta dev](/docs/cli-reference/dev) -- start the dev server which also runs the task worker

---

## /docs/cli-reference/generate/email-template — email-template

> Generate an HTML email template for transactional emails.

# gofasta g email-template

Generates an HTML email template file for sending transactional emails such as welcome messages, password resets, order confirmations, and notifications. Templates use Go's `html/template` package with a base layout for dynamic content rendering.

**Aliases:** `email`

## Usage

```bash
gofasta g email-template 
```

This command has no flags.

## Examples

Generate a welcome email template:

```bash
gofasta g email-template Welcome
```

Generate a password reset template:

```bash
gofasta g email-template PasswordReset
```

Generate an order confirmation template:

```bash
gofasta g email OrderConfirmation
```

## What It Generates

Running `gofasta g email-template Welcome` creates one file:

```
templates/emails/welcome.html
```

### Generated Code

```html

{{"{{"}}define "subject"{{"}}"}}Welcome to {{"{{"}} .AppName {{"}}"}}!{{"{{"}}end{{"}}"}}

{{"{{"}}define "content"{{"}}"}}

Welcome, {{"{{"}} .Name {{"}}"}}!

Thank you for joining {{"{{"}} .AppName {{"}}"}}. We are excited to have you on board.

To get started, click the button below to verify your email address:

Verify Email
{{"{{"}}end{{"}}"}} ``` The template uses Go's `html/template` with a base layout. The `subject` block defines the email subject line, and the `content` block defines the email body that is rendered within the base layout. ## Template Data Each template receives a data struct when rendered. Define the struct in your service or job: ```go type WelcomeEmailData struct { AppName string Name string Email string VerificationURL string } ``` ## Sending Emails Use the email service to render and send templates: ```go func (s *UserServiceImpl) SendWelcomeEmail(user *models.User, verificationToken string) error { data := WelcomeEmailData{ AppName: "My App", Name: user.Name, Email: user.Email, VerificationURL: fmt.Sprintf("https://myapp.com/verify?token=%s", verificationToken), } return s.emailService.Send(EmailMessage{ To: user.Email, Template: "welcome", Data: data, }) } ``` ## Email Configuration SMTP settings are configured in `config/config.yaml`: ```yaml email: smtp_host: smtp.example.com smtp_port: 587 smtp_user: noreply@example.com smtp_password: your-password from_name: My App from_address: noreply@example.com ``` ## Best Practices - Use inline styles for email compatibility across email clients - Use table-based layouts for reliable rendering - Always include a plain-text fallback for accessibility - Test with multiple email clients (Gmail, Outlook, Apple Mail) - Keep templates under 100KB for deliverability ## Related - [gofasta g job](/docs/cli-reference/generate/job) -- generate a background job to send emails asynchronously - [gofasta g task](/docs/cli-reference/generate/task) -- generate an async task for sending emails - [gofasta new](/docs/cli-reference/new) -- create a project with email support configured --- ## /docs/cli-reference/migrate — gofasta migrate > Run database migrations up or down for your Gofasta project. # gofasta migrate Runs database migrations using the [golang-migrate](https://github.com/golang-migrate/migrate) tool with SQL files in the `db/migrations/` directory. Supports applying all pending migrations or rolling back. ## Usage ```bash gofasta migrate ``` Where `` is either `up` or `down`. This command takes no flags. ## Subcommands | Subcommand | Description | |------------|-------------| | `up` | Run all pending migrations | | `down` | Roll back migrations | ## Examples Run all pending migrations: ```bash gofasta migrate up ``` Roll back migrations: ```bash gofasta migrate down ``` ## How It Works The `gofasta migrate` command delegates to the `golang-migrate` tool: ```bash migrate -path db/migrations -database ``` The database URL is built from your `config/config.yaml` settings, with `GOFASTA_` prefixed environment variable overrides applied. ## Supported Databases | Driver | Description | |--------|-------------| | `postgres` | PostgreSQL | | `mysql` | MySQL / MariaDB | | `sqlite` | SQLite | | `sqlserver` | Microsoft SQL Server | | `clickhouse` | ClickHouse | ## Database Configuration The migration command reads database connection settings from `config/config.yaml`: ```yaml database: driver: postgres host: localhost port: 5432 name: myapp_dev user: postgres password: postgres ``` Settings can be overridden using `GOFASTA_` prefixed environment variables. ## Migration File Format Up migrations contain SQL statements to apply changes: ```sql -- db/migrations/000006_create_products.up.sql CREATE TABLE products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255) NOT NULL, price DECIMAL(10,2) NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT NOW(), updated_at TIMESTAMP NOT NULL DEFAULT NOW(), deleted_at TIMESTAMP ); CREATE INDEX idx_products_deleted_at ON products(deleted_at); ``` Down migrations contain SQL statements to reverse changes: ```sql -- db/migrations/000006_create_products.down.sql DROP TABLE IF EXISTS products; ``` ## Related - [gofasta seed](/docs/cli-reference/seed) -- run database seeders - [gofasta init](/docs/cli-reference/init) -- initialize a project including running migrations - [gofasta dev](/docs/cli-reference/dev) -- dev server runs migrations automatically if DB is available --- ## /docs/cli-reference/seed — gofasta seed > Run database seeders to populate your database with initial or test data. # gofasta seed Runs the database seeder files to populate your database with initial data. This is useful for setting up default roles, permissions, admin users, and sample data for development and testing. ## Usage ```bash gofasta seed [flags] ``` Run this command from the root directory of your Gofasta project. ## Flags | Flag | Default | Description | |------|---------|-------------| | `--fresh` | `false` | Drop all tables, re-run all migrations, then seed | ## Examples Run all seeders: ```bash gofasta seed ``` Drop all tables, re-run migrations, and seed from scratch: ```bash gofasta seed --fresh ``` ## How It Works The `gofasta seed` command delegates to: ```bash go run ./app/main seed [--fresh] ``` When the `--fresh` flag is provided, it drops all tables, re-runs all migrations, and then executes the seeders. ## Default Seeders A newly generated Gofasta project includes the following seeders: | Seeder | Description | |--------|-------------| | `roles_seeder.go` | Creates default roles (admin, user, moderator) | | `permissions_seeder.go` | Creates default RBAC permissions | | `users_seeder.go` | Creates an admin user with default credentials | ## Seeder File Structure Seeders are Go files in `db/seeders/` that implement the `Seeder` interface: ```go // db/seeders/users_seeder.go package seeders import ( "myapp/app/models" "gorm.io/gorm" ) type UsersSeeder struct{} func (s *UsersSeeder) Seed(db *gorm.DB) error { users := []models.User{ { Email: "admin@example.com", Name: "Admin User", Password: "hashed_password_here", RoleID: 1, }, } for _, user := range users { if err := db.FirstOrCreate(&user, models.User{Email: user.Email}).Error; err != nil { return err } } return nil } func (s *UsersSeeder) Order() int { return 3 // Controls execution order } ``` The `Order()` method determines the sequence in which seeders run. Lower numbers run first, which is important for satisfying foreign key constraints. ## Related - [gofasta migrate](/docs/cli-reference/migrate) -- run database migrations - [gofasta new](/docs/cli-reference/new) -- create a new project with default seeders - [Quick Start](/docs/getting-started/quick-start) --- ## /docs/cli-reference/db — gofasta db > Database management commands for resetting and managing your development database. # gofasta db Database management commands. Currently provides the `reset` subcommand for resetting your database to a clean state during development. ## Usage ```bash gofasta db [command] ``` ## Subcommands ### gofasta db reset Drop all tables, re-apply all migrations, and run seed functions. Useful during development when you want a clean database. ```bash gofasta db reset ``` #### Flags | Flag | Type | Default | Description | |------|------|---------|-------------| | `--skip-seed` | bool | `false` | Skip running database seeds after migration | #### Examples Reset database and re-seed: ```bash gofasta db reset ``` Reset database without seeding: ```bash gofasta db reset --skip-seed ``` ## How It Works The `gofasta db reset` command performs these steps in order: 1. **Drop all tables** -- runs `migrate drop -f` to force-drop all database tables 2. **Re-apply migrations** -- runs `migrate up` to apply all migrations from scratch 3. **Run seeds** -- delegates to the project binary to run seed functions (unless `--skip-seed` is set) The database connection details are read from `config.yaml` and can be overridden with `GOFASTA_` prefixed environment variables. ## Related - [gofasta migrate](/docs/cli-reference/migrate) -- apply or rollback individual migrations - [gofasta seed](/docs/cli-reference/seed) -- run database seeders - [Database & Migrations Guide](/docs/guides/database-and-migrations) --- ## /docs/cli-reference/deploy — gofasta deploy > Deploy a Gofasta application to a remote VPS via SSH using Docker or a compiled binary. # gofasta deploy Deploys your application to a remote server via SSH. Supports two methods: **Docker** (default) — builds and transfers a Docker image, runs with Docker Compose; and **binary** — cross-compiles a Go binary, transfers via SCP, manages with systemd. ## Usage ```bash gofasta deploy [flags] gofasta deploy [command] ``` Run this command from the root directory of your Gofasta project. ## Flags | Flag | Type | Default | Description | |------|------|---------|-------------| | `--host` | string | | Deploy target (`user@server`) | | `--method` | string | `docker` | Deploy method: `docker` or `binary` | | `--port` | int | `22` | SSH port | | `--path` | string | `/opt/` | Remote deploy directory | | `--arch` | string | `amd64` | Target architecture: `amd64` or `arm64` | | `--dry-run` | bool | `false` | Show commands without executing | All flags can also be set in the `deploy` section of `config.yaml` or via `GOFASTA_DEPLOY_*` environment variables. ## Subcommands | Command | Description | |---------|-------------| | `gofasta deploy setup` | Prepare a fresh server (install Docker, nginx, create directories) | | `gofasta deploy status` | Check the status of the deployed application | | `gofasta deploy logs` | Tail application logs from the remote server | | `gofasta deploy rollback` | Rollback to the previous release | ## Examples Deploy using config.yaml settings: ```bash gofasta deploy ``` Deploy to a specific host: ```bash gofasta deploy --host deploy@api.example.com ``` Deploy as a compiled binary instead of Docker: ```bash gofasta deploy --method binary ``` Preview all commands without executing: ```bash gofasta deploy --dry-run --host deploy@api.example.com ``` First-time server setup: ```bash gofasta deploy setup --host deploy@api.example.com ``` Check status, tail logs, or rollback: ```bash gofasta deploy status gofasta deploy logs gofasta deploy rollback ``` ## Configuration Add a `deploy` section to your `config.yaml`: ```yaml deploy: host: deploy@api.example.com method: docker # docker or binary port: 22 path: /opt/myapp arch: amd64 # amd64 or arm64 health_path: /health health_timeout: 30 # seconds keep_releases: 3 # for rollback ``` Environment variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_DEPLOY_HOST=deploy@api.example.com export GOFASTA_DEPLOY_METHOD=binary ``` ## How It Works ### Docker Method 1. Builds a Docker image locally using the project's `Dockerfile` 2. Transfers the image to the server via `docker save | ssh docker load` 3. Copies `.env` and `config.yaml` to a shared directory on the server 4. Copies the production compose file to a versioned release directory 5. Runs `docker compose up -d` on the server 6. Runs database migrations inside the container 7. Polls the `/health` endpoint until the app responds 8. Updates the `current` symlink to point to the new release 9. Cleans up old releases beyond the `keep_releases` threshold ### Binary Method 1. Cross-compiles a static binary (`CGO_ENABLED=0 GOOS=linux`) 2. Transfers the binary, migrations, templates, and configs via SCP 3. Installs the binary to `/usr/local/bin/` 4. Installs or updates the systemd service file 5. Runs database migrations 6. Restarts the systemd service 7. Polls the `/health` endpoint until the app responds 8. Updates the `current` symlink and cleans up old releases ### Release Directory Structure Deployments use a Capistrano-style release structure on the server: ``` /opt/myapp/ current -> releases/20260409-153000/ # symlink to active release releases/ 20260409-153000/ # newest 20260409-120000/ # previous (available for rollback) shared/ .env # shared across all releases config.yaml ``` Each deploy creates a new timestamped release directory. The `current` symlink is only updated after the health check passes. If a deploy fails, the previous release remains active. ### Rollback `gofasta deploy rollback` activates the previous release by: 1. Finding the release before `current` 2. Starting the previous release's containers (Docker) or installing its binary (binary method) 3. Updating the `current` symlink 4. Running a health check to verify ## Prerequisites - **SSH key-based access** to the target server (password auth is not supported) - **Docker method**: Docker installed locally and on the server - **Binary method**: systemd on the server Use `gofasta deploy setup` to install prerequisites on a fresh Ubuntu/Debian server. ## Troubleshooting **"deploy host is required"** -- Set `deploy.host` in `config.yaml` or pass `--host`. **SSH connection refused** -- Verify the host, port, and that your SSH key is authorized on the server. Test with `ssh -p user@server echo ok`. **Health check failed** -- The app didn't respond at the health endpoint within the timeout. Check logs with `gofasta deploy logs`. The previous release remains active. **Docker not found on remote** -- Run `gofasta deploy setup` to install Docker on the server. ## Related - [Deployment Guide](/docs/guides/deployment) -- full walkthrough of the Docker and binary methods, systemd, nginx, and GitHub Actions - [gofasta serve](/docs/cli-reference/serve) -- start the production server - [Configuration Guide](/docs/guides/configuration) -- application configuration --- ## /docs/cli-reference/wire — gofasta wire > Regenerate Google Wire dependency injection code for your Gofasta project. # gofasta wire Regenerates the [Google Wire](https://github.com/google/wire) dependency injection container code. Wire is a compile-time dependency injection framework that reads your provider definitions and generates the wiring code automatically. Run this command whenever you add, remove, or modify DI providers in your project. ## Usage ```bash gofasta wire ``` Run this command from the root directory of your Gofasta project. This command takes no flags. ## Examples Regenerate Wire DI code: ```bash gofasta wire ``` ## How It Works The `gofasta wire` command runs: ```bash go tool wire ./app/di/ ``` The Wire code generation process: 1. Reads provider definitions from `app/di/providers/` directory 2. Reads the injector definition from `app/di/wire.go` 3. Generates `app/di/wire_gen.go` with the concrete wiring code ### Provider Definition Each provider file in `app/di/providers/` defines a Wire provider set: ```go // app/di/providers/product.go package providers import ( "github.com/google/wire" "myapp/app/repositories" repoInterfaces "myapp/app/repositories/interfaces" "myapp/app/services" svcInterfaces "myapp/app/services/interfaces" "myapp/app/rest/controllers" ) var ProductSet = wire.NewSet( repositories.NewProductRepository, wire.Bind(new(repoInterfaces.ProductRepository), new(*repositories.ProductRepositoryImpl)), services.NewProductService, wire.Bind(new(svcInterfaces.ProductService), new(*services.ProductServiceImpl)), controllers.NewProductController, ) ``` ### Injector Definition The `app/di/wire.go` file defines the injector function that Wire implements: ```go //go:build wireinject package di import ( "github.com/google/wire" "myapp/app/di/providers" ) func InitializeContainer() (*Container, error) { wire.Build( providers.UserSet, providers.AuthSet, providers.ProductSet, // Add new provider sets here wire.Struct(new(Container), "*"), ) return nil, nil } ``` ## When to Run Run `gofasta wire` after: - Running `gofasta g scaffold` to generate a new resource - Running `gofasta g provider` to create a new provider - Manually adding or modifying provider sets - Changing constructor signatures in repositories, services, or controllers - Resolving Wire compilation errors ## Troubleshooting **Provider not found** -- Make sure the provider set is imported and included in the `wire.Build()` call in `app/di/wire.go`. **Duplicate bindings** -- Wire requires exactly one provider for each type. Check that you do not have two providers binding to the same interface. **Cycle detected** -- Wire does not allow circular dependencies. Refactor your code to break the cycle. ## Related - [gofasta g provider](/docs/cli-reference/generate/provider) -- generate a new Wire provider - [gofasta g scaffold](/docs/cli-reference/generate/scaffold) -- generate a full resource with providers - [gofasta init](/docs/cli-reference/init) -- initialize a project including Wire generation --- ## /docs/cli-reference/swagger — gofasta swagger > Generate OpenAPI/Swagger documentation from code annotations. # gofasta swagger Generates OpenAPI/Swagger documentation from annotation comments in your Go source code. The generated specification is served as an interactive Swagger UI at `/swagger/index.html` when the server is running. This command uses [swag](https://github.com/swaggo/swag) under the hood to parse Go annotations and produce the OpenAPI spec. ## Usage ```bash gofasta swagger ``` Run this command from the root directory of your Gofasta project. This command takes no flags. ## Examples Generate Swagger docs: ```bash gofasta swagger ``` ## How It Works The `gofasta swagger` command runs: ```bash go tool swag init -g app/main/main.go -o docs/ ``` This parses the Go annotation comments in your source code starting from `app/main/main.go` and generates the OpenAPI specification files in the `docs/` directory. ## Annotation Format Gofasta controllers use swag-style annotations to describe API endpoints. Here is an example from a generated controller: ```go // CreateProduct godoc // @Summary Create a new product // @Description Create a new product with the provided data // @Tags Products // @Accept json // @Produce json // @Param body body dtos.CreateProductRequest true "Product data" // @Success 201 {object} dtos.ProductResponse // @Failure 400 {object} dtos.ErrorResponse // @Failure 401 {object} dtos.ErrorResponse // @Failure 500 {object} dtos.ErrorResponse // @Security BearerAuth // @Router /api/v1/products [post] func (c *ProductController) Create(ctx *gin.Context) { // ... } ``` The general API information is defined in `app/main/main.go`: ```go // @title My App API // @version 1.0 // @description REST API for My App // @host localhost:8080 // @BasePath /api/v1 // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization ``` ## What It Generates Running `gofasta swagger` produces three files: | File | Description | |------|-------------| | `docs/docs.go` | Go file that embeds the spec for serving at runtime | | `docs/swagger.json` | OpenAPI spec in JSON format | | `docs/swagger.yaml` | OpenAPI spec in YAML format | ## Accessing Swagger UI After generating the docs and starting the server, the interactive Swagger UI is available at: ``` http://localhost:8080/swagger/index.html ``` This provides an interactive interface where you can browse all endpoints, view request/response schemas, and test API calls directly from the browser. ## When to Run Run `gofasta swagger` after: - Adding or modifying controller annotations - Changing DTO structures that are referenced in annotations - Adding new routes or controllers - Updating the general API information in `app/main/main.go` ## Related - [gofasta serve](/docs/cli-reference/serve) -- start the server to view Swagger UI - [gofasta dev](/docs/cli-reference/dev) -- start the dev server to view Swagger UI --- ## /docs/cli-reference/routes — gofasta routes > Display all registered API routes in a formatted table. # gofasta routes Parses route files in `app/rest/routes/` and displays a formatted table of all registered routes, including HTTP method, path, and source file. ## Usage ```bash gofasta routes ``` Run this command from the root directory of your Gofasta project. This command takes no flags. ## Examples ```bash $ gofasta routes METHOD PATH FILE GET /health routes/index.routes.go GET /api/v1/users routes/user.routes.go POST /api/v1/users routes/user.routes.go GET /api/v1/users/:id routes/user.routes.go PUT /api/v1/users/:id routes/user.routes.go DELETE /api/v1/users/:id routes/user.routes.go ``` ## How It Works The command scans all `.go` files in `app/rest/routes/` and parses route registration calls (e.g., `GET`, `POST`, `PUT`, `DELETE`) to build the table. It does not start the server — it performs static analysis of the route files. ## Related - [REST API Guide](/docs/guides/rest-api) -- build and customize REST endpoints - [gofasta generate route](/docs/cli-reference/generate/route) -- generate a route file - [gofasta serve](/docs/cli-reference/serve) -- start the HTTP server --- ## /docs/cli-reference/console — gofasta console > Start an interactive Go REPL to explore your project code. # gofasta console Launches [yaegi](https://github.com/traefik/yaegi) (a Go interpreter) in the current project directory for interactive exploration of your application code. ## Usage ```bash gofasta console ``` This command takes no flags. ## Prerequisites Yaegi must be installed: ```bash go install github.com/traefik/yaegi/cmd/yaegi@latest ``` ## Examples Start the REPL: ```bash gofasta console ``` Once inside, you can import and test your project's packages interactively: ```go > import "myapp/app/models" > u := models.User{FirstName: "Alice"} > u.FirstName "Alice" ``` ## Related - [gofasta dev](/docs/cli-reference/dev) -- start the development server - [gofasta doctor](/docs/cli-reference/doctor) -- check system prerequisites --- ## /docs/cli-reference/doctor — gofasta doctor > Check system prerequisites, project configuration, and database connectivity. # gofasta doctor Verifies that required and optional tools are installed, checks project configuration, and tests database connectivity. Useful for diagnosing setup issues and for including in bug reports. ## Usage ```bash gofasta doctor ``` This command takes no flags. ## Examples ```bash $ gofasta doctor Required: ✓ go go version go1.25.0 darwin/arm64 ✓ migrate v4.18.1 Optional: ✓ docker Docker version 27.4.0 ✓ air available (Go tool) ✓ wire available (Go tool) ✓ gqlgen available (Go tool) ✓ swag available (Go tool) Project: ✓ config.yaml found ✓ database reachable ``` ## What It Checks ### Required Tools | Tool | What it checks | |------|---------------| | `go` | Go compiler is installed and reports its version | | `migrate` | golang-migrate CLI is installed | ### Optional Tools | Tool | What it checks | |------|---------------| | `docker` | Docker is installed (needed for `docker compose` workflows) | | `air` | Air hot-reload tool is available as a Go tool | | `wire` | Google Wire DI generator is available | | `gqlgen` | GraphQL code generator is available (only needed for GraphQL projects) | | `swag` | Swagger/OpenAPI generator is available | ### Project Health When run inside a project directory (where `config.yaml` exists): | Check | What it verifies | |-------|-----------------| | `config.yaml` | Configuration file exists | | `database` | Database is reachable using the connection details from config | ## Related - [Installation](/docs/getting-started/installation) -- install the CLI and prerequisites - [gofasta init](/docs/cli-reference/init) -- initialize a project after cloning --- ## /docs/cli-reference/upgrade — gofasta upgrade > Upgrade the Gofasta CLI to the latest version. # gofasta upgrade Checks for a newer version of the Gofasta CLI and installs it. The upgrade method is automatically detected based on how gofasta was originally installed. ## Usage ```bash gofasta upgrade ``` This command takes no flags. ## How It Works The command detects your installation method and runs the appropriate upgrade: | Installation method | Upgrade command | |--------------------|----------------| | Go install | `go install github.com/gofastadev/cli/cmd/gofasta@latest` | | Binary (curl install) | Downloads the latest release from GitHub and replaces the current binary | ## Examples ```bash $ gofasta upgrade Checking for updates... Current version: v0.5.0 Latest version: v0.6.0 Upgrading via go install... ✓ Upgraded to v0.6.0 ``` ## Related - [Installation](/docs/getting-started/installation) -- install the CLI - [gofasta version](/docs/cli-reference/version) -- check your current version --- ## /docs/cli-reference/version — gofasta version > Display the CLI version, Go version, and OS/architecture details. # gofasta version Displays the CLI version, Go version, and OS/architecture details. ## Usage ```bash gofasta version ``` This command takes no flags. You can also use the `-v` or `--version` flag on the root command: ```bash gofasta -v ``` ## Examples ```bash $ gofasta version gofasta version 0.6.0 Go: go1.25.0 OS: darwin/arm64 ``` ## Related - [gofasta upgrade](/docs/cli-reference/upgrade) -- upgrade to the latest version - [gofasta doctor](/docs/cli-reference/doctor) -- check system prerequisites --- ## /docs/api-reference/config — Config > Configuration loading, database setup, and environment variable overrides for Gofasta applications. # Config The `config` package provides configuration loading from YAML files with environment variable overrides, and database connection setup for all supported drivers. ## Import ```go import "github.com/gofastadev/gofasta/pkg/config" ``` ## Key Types ### AppConfig The root configuration struct that maps to your `config.yaml` file. Internally, the config system uses [koanf](https://github.com/knadh/koanf) for loading YAML and environment variable overrides. ```go type AppConfig struct { Server ServerConfig `koanf:"server"` Database DatabaseConfig `koanf:"database"` GraphQL GraphQLConfig `koanf:"graphql"` Log LogConfig `koanf:"log"` Email EmailConfig `koanf:"email"` Jobs []JobConfig `koanf:"jobs"` Auth AuthConfig `koanf:"auth"` RateLimit RateLimitConfig `koanf:"rate_limit"` Cache CacheConfig `koanf:"cache"` Security SecurityConfig `koanf:"security"` Storage StorageConfig `koanf:"storage"` Queue QueueConfig `koanf:"queue"` WebSocket WebSocketConfig `koanf:"websocket"` I18n I18nConfig `koanf:"i18n"` FeatureFlag FeatureFlagConfig `koanf:"feature_flag"` Encryption EncryptionConfig `koanf:"encryption"` Session SessionConfig `koanf:"session"` Observability ObservabilityConfig `koanf:"observability"` } ``` ### ServerConfig ```go type ServerConfig struct { Port string `koanf:"port"` ShutdownTimeout time.Duration `koanf:"shutdown_timeout"` AllowedOrigins []string `koanf:"allowed_origins"` } ``` ### DatabaseConfig ```go type DatabaseConfig struct { Driver string `koanf:"driver"` Host string `koanf:"host"` Port string `koanf:"port"` User string `koanf:"user"` Password string `koanf:"password"` Name string `koanf:"name"` SSLMode string `koanf:"sslmode"` MaxIdle int `koanf:"max_idle"` MaxOpen int `koanf:"max_open"` MaxLife time.Duration `koanf:"max_life"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `LoadConfig` | `func LoadConfig() (*AppConfig, error)` | Loads configuration from `config.yaml` (if present) and applies `GOFASTA_`-prefixed environment variable overrides | | `SetupDB` | `func SetupDB(cfg DatabaseConfig) (*gorm.DB, error)` | Initializes a GORM database connection using the provided config | | `SetupDBWithMigrate` | `func SetupDBWithMigrate(cfg DatabaseConfig, models ...interface{}) (*gorm.DB, error)` | Sets up the database and runs auto-migration for the given models | ## Usage ### Loading Configuration Create a `config.yaml` in your project root: ```yaml server: port: "8080" shutdown_timeout: 15s allowed_origins: - "*" database: driver: postgres host: localhost port: "5432" name: mydb user: postgres password: secret sslmode: disable max_idle: 10 max_open: 100 max_life: 1h ``` Load it in your application: ```go cfg, err := config.LoadConfig() if err != nil { log.Fatalf("failed to load config: %v", err) } fmt.Println(cfg.Server.Port) // "8080" ``` ### Environment Variable Overrides Any configuration field can be overridden by setting an environment variable with the `GOFASTA_` prefix. The prefix is stripped, and underscores map to nested keys (e.g., `GOFASTA_DATABASE_HOST` maps to `database.host`). Environment variables take precedence over YAML values. ```bash export GOFASTA_SERVER_PORT=9090 export GOFASTA_DATABASE_HOST=production-db.example.com ``` ```go cfg, _ := config.LoadConfig() // cfg.Server.Port is now "9090", not "8080" // cfg.Database.Host is now "production-db.example.com" ``` ### Setting Up the Database ```go cfg, _ := config.LoadConfig() db, err := config.SetupDB(cfg.Database) if err != nil { log.Fatalf("failed to connect to database: %v", err) } // With auto-migration db, err := config.SetupDBWithMigrate(cfg.Database, &User{}, &Post{}) if err != nil { log.Fatalf("failed to setup database: %v", err) } ``` ### Supported Database Drivers The `driver` field in `DatabaseConfig` accepts the following values: | Driver | Value | Package | |--------|-------|---------| | PostgreSQL | `postgres` | `gorm.io/driver/postgres` | | MySQL | `mysql` | `gorm.io/driver/mysql` | | SQLite | `sqlite` | `gorm.io/driver/sqlite` | | SQL Server | `sqlserver` | `gorm.io/driver/sqlserver` | | ClickHouse | `clickhouse` | `gorm.io/driver/clickhouse` | ### Dependency Injection with Wire Use the config in your Wire provider set: ```go var ConfigSet = wire.NewSet( config.LoadConfig, config.SetupDB, ) ``` ## Related Pages - [Logger](/docs/api-reference/logger) -- Configure logging output - [Models](/docs/api-reference/models) -- Base model that works with SetupDB - [Seeds](/docs/api-reference/seeds) -- Seed data after database setup - [Cache](/docs/api-reference/cache) -- Reads the `cache` config block - [Sessions](/docs/api-reference/sessions) -- Reads the `sessions` config block - [Queue](/docs/api-reference/queue) -- Reads the `queue` config block - [Storage](/docs/api-reference/storage) -- Reads the `storage` config block --- ## /docs/api-reference/logger — Logger > Structured logging with configurable levels, context-aware fields, and multiple output formats. # Logger The `logger` package provides structured logging with support for multiple log levels, contextual fields, JSON and text output formats, and request-scoped logging. ## Import ```go import "github.com/gofastadev/gofasta/pkg/logger" ``` ## Key Types ### Logger ```go type Logger interface { Debug(msg string, fields ...Field) Info(msg string, fields ...Field) Warn(msg string, fields ...Field) Error(msg string, fields ...Field) Fatal(msg string, fields ...Field) WithFields(fields ...Field) Logger WithContext(ctx context.Context) Logger } ``` ### Field ```go type Field struct { Key string Value interface{} } ``` ### LogConfig The logger configuration is part of the root `AppConfig` and uses koanf struct tags. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_LOG_LEVEL`). ```go type LogConfig struct { Level string `koanf:"level"` Format string `koanf:"format"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `New` | `func New(cfg LoggerConfig) Logger` | Creates a new logger instance with the given configuration | | `F` | `func F(key string, value interface{}) Field` | Creates a structured log field | | `Err` | `func Err(err error) Field` | Creates an error field | | `FromContext` | `func FromContext(ctx context.Context) Logger` | Retrieves the logger from a context | | `WithContext` | `func WithContext(ctx context.Context, l Logger) context.Context` | Stores a logger in a context | ## Log Levels | Level | Description | |-------|-------------| | `debug` | Detailed diagnostic information for development | | `info` | General operational events | | `warn` | Potentially harmful situations that deserve attention | | `error` | Error events that allow the application to continue | | `fatal` | Severe errors that cause the application to terminate | ## Usage ### Basic Logging ```go log := logger.New(logger.LoggerConfig{ Level: "info", Format: "json", Output: "stdout", }) log.Info("server started", logger.F("port", 8080)) log.Error("request failed", logger.F("path", "/api/users"), logger.Err(err)) ``` Output (JSON format): ```json {"level":"info","msg":"server started","port":8080,"timestamp":"2026-04-07T10:00:00Z"} {"level":"error","msg":"request failed","path":"/api/users","error":"connection refused","timestamp":"2026-04-07T10:00:01Z"} ``` ### Context-Aware Logging Attach a logger to the request context to carry request-scoped fields through the call chain. ```go func (m *LoggingMiddleware) Handle(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqLogger := m.logger.WithFields( logger.F("request_id", r.Header.Get("X-Request-ID")), logger.F("method", r.Method), logger.F("path", r.URL.Path), ) ctx := logger.WithContext(r.Context(), reqLogger) next.ServeHTTP(w, r.WithContext(ctx)) }) } // Later in a service or repository func (s *UserService) Create(ctx context.Context, input CreateUserInput) (*User, error) { log := logger.FromContext(ctx) log.Info("creating user", logger.F("email", input.Email)) // ... } ``` ### Derived Loggers Create child loggers that inherit parent fields. ```go serviceLog := log.WithFields( logger.F("component", "user-service"), logger.F("version", "1.2.0"), ) serviceLog.Info("processing request") // -> {"component":"user-service","version":"1.2.0","msg":"processing request",...} ``` ### Configuration via config.yaml ```yaml log: level: info format: json # "json" or "text" ``` Environment variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_LOG_LEVEL=debug export GOFASTA_LOG_FORMAT=json ``` ### Wire Integration ```go var LoggerSet = wire.NewSet( logger.New, ) ``` ## Related Pages - [Config](/docs/api-reference/config) -- Logger configuration loading - [Middleware](/docs/api-reference/middleware) -- Logging middleware for HTTP requests - [Observability](/docs/api-reference/observability) -- Metrics and tracing complement logging --- ## /docs/api-reference/errors — Errors > Custom error types, HTTP error responses, error wrapping, and standardized error codes. # Errors The `errors` package provides structured error handling with custom error types, HTTP-aware error responses, error wrapping with context, and a standardized set of error codes for consistent API responses. ## Import ```go import "github.com/gofastadev/gofasta/pkg/errors" ``` ## Key Types ### AppError The primary error type used throughout Gofasta applications. ```go type AppError struct { Code string `json:"code"` Message string `json:"message"` Detail string `json:"detail,omitempty"` HTTPStatus int `json:"-"` Err error `json:"-"` } func (e *AppError) Error() string func (e *AppError) Unwrap() error ``` ### ErrorResponse The JSON structure returned to API clients. ```go type ErrorResponse struct { Error ErrorBody `json:"error"` } type ErrorBody struct { Code string `json:"code"` Message string `json:"message"` Detail string `json:"detail,omitempty"` } ``` ## Error Codes | Constant | Code | HTTP Status | Description | |----------|------|-------------|-------------| | `ErrNotFound` | `NOT_FOUND` | 404 | Resource not found | | `ErrBadRequest` | `BAD_REQUEST` | 400 | Malformed or invalid request | | `ErrUnauthorized` | `UNAUTHORIZED` | 401 | Missing or invalid authentication | | `ErrForbidden` | `FORBIDDEN` | 403 | Insufficient permissions | | `ErrConflict` | `CONFLICT` | 409 | Resource conflict (e.g., duplicate) | | `ErrInternal` | `INTERNAL_ERROR` | 500 | Unexpected server error | | `ErrValidation` | `VALIDATION_ERROR` | 422 | Input validation failure | | `ErrTimeout` | `TIMEOUT` | 504 | Operation timed out | | `ErrServiceUnavailable` | `SERVICE_UNAVAILABLE` | 503 | Upstream dependency unavailable | ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `New` | `func New(code string, message string, status int) *AppError` | Creates a new AppError | | `Wrap` | `func Wrap(err error, code string, message string, status int) *AppError` | Wraps an existing error with context | | `NotFound` | `func NotFound(resource string, id string) *AppError` | Returns a 404 error for a missing resource | | `BadRequest` | `func BadRequest(message string) *AppError` | Returns a 400 error | | `Unauthorized` | `func Unauthorized(message string) *AppError` | Returns a 401 error | | `Forbidden` | `func Forbidden(message string) *AppError` | Returns a 403 error | | `Internal` | `func Internal(err error) *AppError` | Wraps an internal error with a 500 response | | `Validation` | `func Validation(detail string) *AppError` | Returns a 422 validation error | | `Is` | `func Is(err error, code string) bool` | Checks if an error matches a specific error code | | `ToResponse` | `func ToResponse(err error) (int, ErrorResponse)` | Converts an error to an HTTP status and JSON response | ## Usage ### Creating Errors ```go // Simple error creation err := errors.New("PAYMENT_FAILED", "payment processing failed", 402) // Convenience constructors err := errors.NotFound("User", "user-123") // -> {"code":"NOT_FOUND","message":"User not found","detail":"id: user-123"} err := errors.BadRequest("email field is required") err := errors.Unauthorized("token has expired") err := errors.Forbidden("admin role required") ``` ### Wrapping Errors ```go user, err := repo.FindByID(ctx, id) if err != nil { return errors.Wrap(err, "NOT_FOUND", "user not found", 404) } // The original error is preserved and accessible var appErr *errors.AppError if stderrors.As(err, &appErr) { fmt.Println(appErr.Unwrap()) // original error } ``` ### Converting to HTTP Responses ```go func (c *UserController) GetUser(w http.ResponseWriter, r *http.Request) { user, err := c.service.FindUser(ctx, id) if err != nil { status, response := errors.ToResponse(err) httputil.JSON(w, status, response) return } httputil.JSON(w, http.StatusOK, user) } ``` ### Checking Error Codes ```go err := service.CreateUser(ctx, input) if errors.Is(err, "CONFLICT") { // handle duplicate user } if errors.Is(err, "VALIDATION_ERROR") { // handle validation failure } ``` ## Related Pages - [HTTP Utilities](/docs/api-reference/http-utilities) -- JSON response helpers that work with AppError - [Validators](/docs/api-reference/validators) -- Validation errors integrate with this package - [Middleware](/docs/api-reference/middleware) -- Recovery middleware catches panics and returns error responses - [GraphQL](/docs/api-reference/graphql) -- AppError is mapped onto GraphQL extensions by this package's presenter - [i18n](/docs/api-reference/i18n) -- Translates error messages --- ## /docs/api-reference/models — Models > Base model with UUID primary keys, automatic timestamps, soft delete, optimistic locking via record_version, and is_active/is_deletable flags. # Models The `models` package provides the `BaseModelImpl` struct that serves as the foundation for all GORM models in a Gofasta application. It includes UUID primary keys, automatic timestamp management, soft delete support, optimistic locking via `RecordVersion`, and boolean flags `IsActive` and `IsDeletable`. ## Import ```go import "github.com/gofastadev/gofasta/pkg/models" ``` ## Key Types ### BaseModel The interface that all Gofasta models implement. ```go type BaseModel interface { gorm.Model GetID() uuid.UUID GetCreatedAt() time.Time GetUpdatedAt() time.Time GetDeletedAt() time.Time GetIsActive() bool GetIsDeletable() bool GetRecordVersion() int } ``` ### BaseModelImpl The concrete base struct to embed in all your GORM models. ```go type BaseModelImpl struct { ID uuid.UUID `gorm:"type:uuid;primary_key;"` CreatedAt time.Time `gorm:"type:timestamp;not null;"` UpdatedAt time.Time `gorm:"type:timestamp;not null;"` DeletedAt time.Time `gorm:"type:timestamp;"` RecordVersion int `gorm:"type:int;not null;default:1"` IsActive bool `gorm:"type:bool;not null;default:true"` IsDeletable bool `gorm:"type:bool;not null;default:true"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `GetID` | `func (b BaseModelImpl) GetID() uuid.UUID` | Returns the model's UUID | | `GetCreatedAt` | `func (b BaseModelImpl) GetCreatedAt() time.Time` | Returns the creation timestamp | | `GetUpdatedAt` | `func (b BaseModelImpl) GetUpdatedAt() time.Time` | Returns the last update timestamp | | `GetDeletedAt` | `func (b BaseModelImpl) GetDeletedAt() time.Time` | Returns the soft-delete timestamp | | `GetIsActive` | `func (b BaseModelImpl) GetIsActive() bool` | Returns whether the record is active | | `GetIsDeletable` | `func (b BaseModelImpl) GetIsDeletable() bool` | Returns whether the record can be deleted | | `GetRecordVersion` | `func (b BaseModelImpl) GetRecordVersion() int` | Returns the current version for optimistic locking | | `BeforeCreate` | `func (b *BaseModelImpl) BeforeCreate(tx *gorm.DB) error` | GORM hook that generates a UUID and sets defaults before insert | ## Usage ### Defining a Model Embed `BaseModelImpl` in your domain models to inherit UUID, timestamps, soft delete, and versioning. ```go type User struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Email string `gorm:"type:varchar(255);uniqueIndex;not null" json:"email"` Password string `gorm:"type:varchar(255);not null" json:"-"` Role string `gorm:"type:varchar(50);default:'user'" json:"role"` } type Post struct { models.BaseModelImpl Title string `gorm:"type:varchar(255);not null" json:"title"` Content string `gorm:"type:text" json:"content"` AuthorID string `gorm:"type:uuid;not null" json:"author_id"` Author User `gorm:"foreignKey:AuthorID" json:"author,omitempty"` } ``` ### Automatic UUID Generation The `BeforeCreate` hook automatically generates a UUID v4 for new records and sets default values for `IsActive`, `IsDeletable`, and `RecordVersion`. ```go user := User{Name: "Jane", Email: "jane@example.com"} db.Create(&user) fmt.Println(user.ID) // "f47ac10b-58cc-4372-a567-0e02b2c3d479" fmt.Println(user.IsActive) // true fmt.Println(user.IsDeletable) // true fmt.Println(user.RecordVersion) // 1 ``` ### Soft Delete GORM's soft delete is built in. Calling `Delete` sets `deleted_at` instead of removing the row. ```go // Soft delete db.Delete(&user) fmt.Println(user.IsDeleted()) // true // Query excludes soft-deleted records by default db.Find(&users) // only returns non-deleted users // Include soft-deleted records db.Unscoped().Find(&users) // returns all users // Permanently delete db.Unscoped().Delete(&user) ``` ### Optimistic Locking Use the `RecordVersion` field to prevent lost updates in concurrent scenarios. Note that a database trigger automatically increments `record_version` on update, but you can also check it at the application level. ```go func (r *UserRepository) Update(ctx context.Context, user *User) error { result := r.db.Model(user). Where("record_version = ?", user.RecordVersion). Updates(map[string]interface{}{ "name": user.Name, "email": user.Email, "record_version": user.RecordVersion + 1, }) if result.RowsAffected == 0 { return errors.New("CONFLICT", "record was modified by another request", 409) } return result.Error } ``` ### Auto-Migration ```go db, err := config.SetupDBWithMigrate(cfg.Database, &User{}, &Post{}) ``` ## Related Pages - [Config](/docs/api-reference/config) -- Database setup and connection - [Seeds](/docs/api-reference/seeds) -- Seed initial data for models - [Types](/docs/api-reference/types) -- Shared type definitions --- ## /docs/api-reference/http-utilities — HTTP Utilities > Request binding, response helpers, JSON utilities, and pagination support for HTTP handlers. # HTTP Utilities The `httputil` package provides helper functions for common HTTP handler tasks including request body binding, JSON response writing, pagination, and standardized response structures. ## Import ```go import "github.com/gofastadev/gofasta/pkg/httputil" ``` ## Key Types ### PaginationParams ```go type PaginationParams struct { Page int `json:"page"` PageSize int `json:"page_size"` Offset int `json:"-"` } ``` ### PaginatedResponse ```go type PaginatedResponse[T any] struct { Data []T `json:"data"` Total int64 `json:"total"` Page int `json:"page"` PageSize int `json:"page_size"` TotalPages int `json:"total_pages"` } ``` ### SuccessResponse ```go type SuccessResponse struct { Message string `json:"message"` Data interface{} `json:"data,omitempty"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `Bind` | `func Bind(r *http.Request, dest interface{}) error` | Decodes the JSON request body into the destination struct | | `BindQuery` | `func BindQuery(r *http.Request, dest interface{}) error` | Binds URL query parameters to a struct | | `JSON` | `func JSON(w http.ResponseWriter, status int, data interface{})` | Writes a JSON response with the given status code | | `Success` | `func Success(w http.ResponseWriter, message string, data interface{})` | Writes a 200 JSON success response | | `Created` | `func Created(w http.ResponseWriter, message string, data interface{})` | Writes a 201 JSON created response | | `NoContent` | `func NoContent(w http.ResponseWriter)` | Writes a 204 no content response | | `Error` | `func Error(w http.ResponseWriter, err error)` | Converts an error to the appropriate HTTP error response | | `ParsePagination` | `func ParsePagination(r *http.Request) PaginationParams` | Extracts page and page_size from query parameters | | `Paginate` | `func Paginate[T any](items []T, total int64, params PaginationParams) PaginatedResponse[T]` | Constructs a paginated response | | `PathParam` | `func PathParam(r *http.Request, name string) string` | Extracts a named path parameter from the request | ## Usage ### Binding Request Bodies ```go func (c *UserController) Create(w http.ResponseWriter, r *http.Request) { var input CreateUserInput if err := httputil.Bind(r, &input); err != nil { httputil.Error(w, errors.BadRequest(err.Error())) return } user, err := c.service.Create(r.Context(), input) if err != nil { httputil.Error(w, err) return } httputil.Created(w, "user created", user) } ``` ### Binding Query Parameters ```go type ListUsersQuery struct { Role string `query:"role"` Status string `query:"status"` Search string `query:"search"` } func (c *UserController) List(w http.ResponseWriter, r *http.Request) { var query ListUsersQuery if err := httputil.BindQuery(r, &query); err != nil { httputil.Error(w, errors.BadRequest(err.Error())) return } // query.Role, query.Status, query.Search are populated } ``` ### JSON Responses ```go // Success with data httputil.Success(w, "user retrieved", user) // -> 200 {"message":"user retrieved","data":{...}} // Created httputil.Created(w, "user created", user) // -> 201 {"message":"user created","data":{...}} // No content httputil.NoContent(w) // -> 204 // Custom status httputil.JSON(w, http.StatusAccepted, map[string]string{"status": "processing"}) // -> 202 {"status":"processing"} ``` ### Pagination ```go func (c *UserController) List(w http.ResponseWriter, r *http.Request) { params := httputil.ParsePagination(r) // GET /api/users?page=2&page_size=20 // params.Page = 2, params.PageSize = 20, params.Offset = 20 users, total, err := c.service.List(r.Context(), params) if err != nil { httputil.Error(w, err) return } response := httputil.Paginate(users, total, params) httputil.JSON(w, http.StatusOK, response) // -> {"data":[...],"total":100,"page":2,"page_size":20,"total_pages":5} } ``` ### Error Responses The `Error` function automatically detects `*errors.AppError` and uses the appropriate HTTP status code. Unknown errors default to 500 Internal Server Error. ```go httputil.Error(w, errors.NotFound("User", "123")) // -> 404 {"error":{"code":"NOT_FOUND","message":"User not found","detail":"id: 123"}} httputil.Error(w, fmt.Errorf("something broke")) // -> 500 {"error":{"code":"INTERNAL_ERROR","message":"internal server error"}} ``` ## Related Pages - [Errors](/docs/api-reference/errors) -- Error types used by the Error() helper - [Validators](/docs/api-reference/validators) -- Validate input before binding - [Middleware](/docs/api-reference/middleware) -- Request processing pipeline --- ## /docs/api-reference/middleware — Middleware > HTTP middleware for CORS, rate limiting, logging, authentication, panic recovery, and request ID injection. # Middleware The `middleware` package provides a collection of HTTP middleware functions for cross-cutting concerns including CORS, rate limiting, request logging, JWT authentication, panic recovery, and request ID propagation. ## Import ```go import "github.com/gofastadev/gofasta/pkg/middleware" ``` ## Key Types ### CORSConfig ```go type CORSConfig struct { AllowedOrigins []string `koanf:"allowed_origins"` AllowedMethods []string `koanf:"allowed_methods"` AllowedHeaders []string `koanf:"allowed_headers"` ExposedHeaders []string `koanf:"exposed_headers"` AllowCredentials bool `koanf:"allow_credentials"` MaxAge time.Duration `koanf:"max_age"` } ``` ### RateLimitConfig ```go type RateLimitConfig struct { Enabled bool `koanf:"enabled"` Rate string `koanf:"rate"` Store string `koanf:"store"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `CORS` | `func CORS(cfg CORSConfig) func(http.Handler) http.Handler` | Adds CORS headers based on configuration | | `RateLimit` | `func RateLimit(cfg RateLimitConfig) func(http.Handler) http.Handler` | Limits request rate per client IP | | `Logger` | `func Logger(log logger.Logger) func(http.Handler) http.Handler` | Logs request method, path, status, and duration | | `Auth` | `func Auth(cfg auth.AuthConfig) func(http.Handler) http.Handler` | Validates JWT tokens and injects claims into context | | `RBAC` | `func RBAC(enforcer *casbin.Enforcer) func(http.Handler) http.Handler` | Enforces role-based access control via Casbin | | `Recovery` | `func Recovery(log logger.Logger) func(http.Handler) http.Handler` | Recovers from panics and returns a 500 response | | `RequestID` | `func RequestID() func(http.Handler) http.Handler` | Generates or propagates a unique request ID | | `Timeout` | `func Timeout(d time.Duration) func(http.Handler) http.Handler` | Cancels requests that exceed the given duration | | `Chain` | `func Chain(middlewares ...func(http.Handler) http.Handler) func(http.Handler) http.Handler` | Composes multiple middleware into a single handler wrapper | ## Usage ### Applying Middleware ```go mux := http.NewServeMux() // Apply middleware in order (outermost first) handler := middleware.Chain( middleware.RequestID(), middleware.Recovery(log), middleware.Logger(log), middleware.CORS(middleware.CORSConfig{ AllowedOrigins: []string{"https://myapp.com"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"}, AllowedHeaders: []string{"Authorization", "Content-Type"}, MaxAge: 12 * time.Hour, }), )(mux) http.ListenAndServe(":8080", handler) ``` ### JWT Authentication ```go authMiddleware := middleware.Auth(auth.AuthConfig{ JWTSecret: "my-secret-key", AccessTokenExpiry: 15 * time.Minute, }) // Protect specific routes protectedMux := http.NewServeMux() protectedMux.HandleFunc("GET /api/profile", profileHandler) protectedMux.HandleFunc("PUT /api/profile", updateProfileHandler) mux.Handle("/api/", authMiddleware(protectedMux)) ``` Access the authenticated claims in your handler: ```go func profileHandler(w http.ResponseWriter, r *http.Request) { claims := auth.ClaimsFromContext(r.Context()) fmt.Println(claims.UserID) // "user-123" fmt.Println(claims.Roles) // ["admin"] } ``` ### Role-Based Access Control ```go enforcer, _ := auth.NewEnforcer(auth.AuthConfig{ RBACModelPath: "configs/rbac_model.conf", RBACPolicyPath: "configs/rbac_policy.csv", }) adminRoutes := http.NewServeMux() adminRoutes.HandleFunc("GET /api/admin/users", listUsersHandler) adminRoutes.HandleFunc("DELETE /api/admin/users/{id}", deleteUserHandler) mux.Handle("/api/admin/", middleware.Chain( middleware.Auth(authCfg), middleware.RBAC(enforcer), )(adminRoutes)) ``` ### Rate Limiting ```go limiter := middleware.RateLimit(middleware.RateLimitConfig{ RequestsPerSecond: 10, Burst: 20, Window: time.Minute, }) mux.Handle("/api/", limiter(apiHandler)) ``` ### Request Logging ```go loggingMiddleware := middleware.Logger(log) // Logs: {"method":"GET","path":"/api/users","status":200,"duration":"12ms","request_id":"abc-123"} ``` ### Panic Recovery ```go recoveryMiddleware := middleware.Recovery(log) // Catches panics, logs the stack trace, and returns: // 500 {"error":{"code":"INTERNAL_ERROR","message":"internal server error"}} ``` ## Related Pages - [Auth](/docs/api-reference/auth) -- JWT and RBAC configuration used by auth middleware - [Logger](/docs/api-reference/logger) -- Logger used by logging and recovery middleware - [HTTP Utilities](/docs/api-reference/http-utilities) -- Response helpers used within middleware - [Errors](/docs/api-reference/errors) -- Recovery middleware turns panics into AppError responses - [Observability](/docs/api-reference/observability) -- Tracing + metrics middleware - [Sessions](/docs/api-reference/sessions) -- Session-loading middleware - [i18n](/docs/api-reference/i18n) -- Locale-detection middleware --- ## /docs/api-reference/graphql — GraphQL > Shared GraphQL schema fragments for pagination, sorting, errors, and the health probe. # GraphQL The `graphql` package is **not Go code** — it ships a small set of shared `.gql` schema fragments that scaffolded projects pull into their gqlgen build. The fragments cover the cross-cutting concerns every backend ends up needing: pagination input/output, sort orientation, common API errors, and a simple health Query/Mutation. The package is consumed by gqlgen's `schema` glob in your project's `gqlgen.yml`; you do not import it as Go code. ## Import The fragments are loaded by gqlgen, not the Go compiler. In your project's `gqlgen.yml`: ```yaml schema: - app/graphql/schema/**/*.gql ``` The CLI scaffold copies the fragments below into `app/graphql/schema/` at project creation time. You can also `go:embed` them from the library if you need to feed them to gqlgen programmatically: ```go import _ "embed" //go:embed common.gql var CommonGQL string ``` ## Schema: `common.gql` Contains pagination + sorting inputs, the response envelope, and the error DTO that the rest of the codebase converges on. ```graphql scalar DateTime enum SortOrientation { ASC DESC } input TPaginationInputDto { limit: Int page: Int } input TSortingInputDto { sortByField: String! sortOrientation: SortOrientation } type TPaginationObjectDto { totalRecords: Int recordsPerPage: Int totalPages: Int currentPage: Int } type TCommonAPIErrorDto { fieldName: String message: String! } type TCommonResponseDto { status: Int! message: String errors: [TCommonAPIErrorDto!] } ``` These types mirror the Go DTOs in [`pkg/types`](/docs/api-reference/types). Keeping the shapes identical means the same response shape works for REST and GraphQL — pagination, sorting, and error envelopes round-trip without any per-transport translation in your service layer. ## Schema: `server.health.gql` A minimal health probe so gqlgen has a non-empty Query/Mutation root from the very first build of a new project: ```graphql type Query { queryHealth: TCommonResponseDto! } type Mutation { mutationHealth: TCommonResponseDto! } ``` When you scaffold a resource with `gofasta g scaffold --graphql`, the generator extends these roots with `(...)`, `(...)`, `create(...)`, `update(...)`, `delete(...)` operations. ## GraphQLConfig Routing for the GraphQL server is configured via `pkg/config`'s `GraphQLConfig` struct, applied in your `cmd/serve.go`: ```go type GraphQLConfig struct { PlaygroundRoute string `koanf:"playground_route"` GeneralRoute string `koanf:"general_route"` } ``` Defaults from `config.yaml`: ```yaml graphql: playground_route: /graphql-playground general_route: /graphql ``` Environment-variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_GRAPHQL_GENERAL_ROUTE=/api/graphql export GOFASTA_GRAPHQL_PLAYGROUND_ROUTE=/api/graphql-playground ``` ## Usage ### Page-able query A scaffolded list query is auto-generated to use `TPaginationInputDto` + `TSortingInputDto` and return `TPaginationObjectDto` alongside the data: ```graphql type ProductsResponse { data: [Product!]! pagination: TPaginationObjectDto! } extend type Query { products( pagination: TPaginationInputDto sort: TSortingInputDto ): ProductsResponse! } ``` Your resolver receives Go pointers to the DTOs: ```go func (r *queryResolver) Products( ctx context.Context, pagination *types.TPaginationInputDto, sort *types.TSortingInputDto, ) (*ProductsResponse, error) { return r.svc.List(ctx, pagination, sort) } ``` ### Surfacing errors Service-layer `*errors.AppError` values are mapped to GraphQL error extensions by [`pkg/errors`](/docs/api-reference/errors)' GraphQL presenter — you don't have to convert them by hand. Errors meant for the user-visible payload travel via `TCommonResponseDto.errors`: ```go return &TCommonResponseDto{ Status: http.StatusUnprocessableEntity, Message: "validation failed", Errors: []*types.TCommonAPIErrorDto{ {FieldName: ptr("price"), Message: "must be > 0"}, }, }, nil ``` ## Adding to an existing project If your project predates this package and doesn't have the fragments locally, copy them in once: ```bash mkdir -p app/graphql/schema cp $(go env GOPATH)/pkg/mod/github.com/gofastadev/gofasta@*/pkg/graphql/schema/*.gql \ app/graphql/schema/ ``` Then point gqlgen at them in `gqlgen.yml` and re-run `gofasta wire`. ## Related Pages - [Types](/docs/api-reference/types) — Go DTOs that mirror these GraphQL types - [Errors](/docs/api-reference/errors) — Application errors + GraphQL presenter - [HTTP Utilities](/docs/api-reference/http-utilities) — REST request/response helpers using the same DTO shapes --- ## /docs/api-reference/auth — Auth > JWT token generation and validation, claims management, auth middleware, and Casbin RBAC setup. # Auth The `auth` package provides JWT-based authentication with token generation and validation, custom claims, authentication middleware, and role-based access control (RBAC) powered by Casbin. ## Import ```go import "github.com/gofastadev/gofasta/pkg/auth" ``` ## Key Types ### Claims ```go type Claims struct { UserID string `json:"user_id"` Email string `json:"email"` Roles []string `json:"roles"` jwt.RegisteredClaims } ``` ### AuthConfig The auth configuration uses koanf struct tags and is part of the root `AppConfig`. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_AUTH_JWT_SECRET`). ```go type AuthConfig struct { JWTSecret string `koanf:"jwt_secret"` AccessTokenExpiry time.Duration `koanf:"access_token_expiry"` RefreshTokenExpiry time.Duration `koanf:"refresh_token_expiry"` RBACModelPath string `koanf:"rbac_model"` RBACPolicyPath string `koanf:"rbac_policy"` } ``` Casbin RBAC configuration files (`rbac_model.conf` and `rbac_policy.csv`) are scaffolded in the `configs/` directory by default. ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `GenerateToken` | `func GenerateToken(cfg AuthConfig, claims Claims) (string, error)` | Creates a signed JWT token from the given claims | | `ValidateToken` | `func ValidateToken(cfg AuthConfig, tokenStr string) (*Claims, error)` | Parses and validates a JWT token, returning the claims | | `GenerateRefreshToken` | `func GenerateRefreshToken(cfg AuthConfig, userID string) (string, error)` | Creates a long-lived refresh token | | `HashPassword` | `func HashPassword(password string) (string, error)` | Hashes a password using bcrypt | | `CheckPassword` | `func CheckPassword(hashed, password string) bool` | Compares a bcrypt hash with a plaintext password | | `NewEnforcer` | `func NewEnforcer(cfg RBACConfig) (*casbin.Enforcer, error)` | Creates a Casbin enforcer for RBAC policy evaluation | ## Usage ### Generating and Validating Tokens ```go cfg := auth.AuthConfig{ JWTSecret: "my-secret-key", AccessTokenExpiry: 15 * time.Minute, RefreshTokenExpiry: 7 * 24 * time.Hour, RBACModelPath: "configs/rbac_model.conf", RBACPolicyPath: "configs/rbac_policy.csv", } claims := auth.Claims{ UserID: "user-123", Email: "user@example.com", Roles: []string{"admin", "editor"}, } // Generate an access token token, err := auth.GenerateToken(cfg, claims) if err != nil { log.Fatalf("failed to generate token: %v", err) } // Validate the token parsed, err := auth.ValidateToken(cfg, token) if err != nil { log.Fatalf("invalid token: %v", err) } fmt.Println(parsed.UserID) // "user-123" fmt.Println(parsed.Roles) // ["admin", "editor"] ``` ### Password Hashing ```go hashed, err := auth.HashPassword("my-secure-password") if err != nil { log.Fatalf("failed to hash password: %v", err) } ok := auth.CheckPassword(hashed, "my-secure-password") fmt.Println(ok) // true ``` ### Casbin RBAC Setup Define a Casbin model file (`rbac_model.conf`): ```ini [request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act ``` Define a policy file (`rbac_policy.csv`): ```csv p, admin, /api/users, GET p, admin, /api/users, POST p, editor, /api/posts, GET p, editor, /api/posts, PUT g, alice, admin g, bob, editor ``` Initialize the enforcer (paths default to `configs/rbac_model.conf` and `configs/rbac_policy.csv`): ```go enforcer, err := auth.NewEnforcer(auth.AuthConfig{ RBACModelPath: "configs/rbac_model.conf", RBACPolicyPath: "configs/rbac_policy.csv", }) if err != nil { log.Fatalf("failed to create enforcer: %v", err) } allowed, _ := enforcer.Enforce("alice", "/api/users", "POST") fmt.Println(allowed) // true ``` ### Wire Integration ```go var AuthSet = wire.NewSet( auth.NewEnforcer, wire.Struct(new(auth.AuthConfig), "*"), ) ``` ## Related Pages - [Middleware](/docs/api-reference/middleware) -- Auth middleware for protecting routes - [Sessions](/docs/api-reference/sessions) -- Session-based authentication alternative - [Encryption](/docs/api-reference/encryption) -- Cryptographic utilities --- ## /docs/api-reference/cache — Cache > In-memory and Redis caching with TTL support, cache strategies, and key management. # Cache The `cache` package provides a unified caching interface with in-memory and Redis backends. It supports TTL-based expiration, cache-aside patterns, and key management utilities. ## Import ```go import "github.com/gofastadev/gofasta/pkg/cache" ``` ## Key Types ### Cache The primary caching interface implemented by all cache drivers. ```go type Cache interface { Get(ctx context.Context, key string, dest interface{}) error Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error Delete(ctx context.Context, key string) error Exists(ctx context.Context, key string) (bool, error) Flush(ctx context.Context) error Close() error } ``` ### CacheConfig The cache configuration uses koanf struct tags and is part of the root `AppConfig`. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_CACHE_DRIVER`). ```go type CacheConfig struct { Driver string `koanf:"driver"` Redis RedisConfig `koanf:"redis"` } type RedisConfig struct { Host string `koanf:"host"` Port string `koanf:"port"` Password string `koanf:"password"` DB int `koanf:"db"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewMemoryCache` | `func NewMemoryCache(cfg CacheConfig) Cache` | Creates an in-memory cache instance | | `NewRedisCache` | `func NewRedisCache(cfg CacheConfig) (Cache, error)` | Creates a Redis-backed cache instance | | `NewCache` | `func NewCache(cfg CacheConfig) (Cache, error)` | Factory function that creates a cache based on the driver config | | `BuildKey` | `func BuildKey(prefix string, parts ...string) string` | Constructs a namespaced cache key | ## Usage ### In-Memory Cache ```go c := cache.NewMemoryCache(cache.CacheConfig{ Driver: "memory", }) defer c.Close() // Set a value err := c.Set(ctx, "user:123", user, 10*time.Minute) if err != nil { log.Fatalf("failed to cache: %v", err) } // Retrieve the value var cached User err = c.Get(ctx, "user:123", &cached) if err != nil { log.Println("cache miss") } ``` ### Redis Cache ```go c, err := cache.NewRedisCache(cache.CacheConfig{ Driver: "redis", Redis: cache.RedisConfig{ Host: "localhost", Port: "6379", Password: "", DB: 0, }, }) if err != nil { log.Fatalf("failed to connect to Redis: %v", err) } defer c.Close() err = c.Set(ctx, "session:abc", sessionData, 30*time.Minute) ``` ### Cache-Aside Pattern ```go func GetUser(ctx context.Context, c cache.Cache, repo UserRepository, id string) (*User, error) { var user User key := cache.BuildKey("users", id) // Try cache first err := c.Get(ctx, key, &user) if err == nil { return &user, nil } // Cache miss -- fetch from database userPtr, err := repo.FindByID(ctx, id) if err != nil { return nil, err } // Populate cache _ = c.Set(ctx, key, userPtr, 10*time.Minute) return userPtr, nil } ``` ### Cache Invalidation ```go // Delete a single key err := c.Delete(ctx, "user:123") // Check existence before fetching exists, err := c.Exists(ctx, "user:123") if exists { // use cached value } // Flush all keys (use with caution) err = c.Flush(ctx) ``` ### Configuration via config.yaml ```yaml cache: driver: redis # "memory" or "redis" redis: host: localhost port: "6379" password: "" db: 0 ``` Environment variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_CACHE_DRIVER=redis export GOFASTA_CACHE_REDIS_HOST=redis.example.com ``` ## Related Pages - [Config](/docs/api-reference/config) -- Cache configuration loading - [Sessions](/docs/api-reference/sessions) -- Redis-backed session storage - [Resilience](/docs/api-reference/resilience) -- Circuit breakers for cache backends --- ## /docs/api-reference/storage — Storage > File storage abstraction with local and S3 drivers, upload/download, and signed URL generation. # Storage The `storage` package provides a unified file storage interface with support for local filesystem and Amazon S3 (or S3-compatible) backends. It handles file uploads, downloads, deletion, and signed URL generation. ## Import ```go import "github.com/gofastadev/gofasta/pkg/storage" ``` ## Key Types ### Storage ```go type Storage interface { Put(ctx context.Context, path string, content io.Reader, opts ...PutOption) error Get(ctx context.Context, path string) (io.ReadCloser, error) Delete(ctx context.Context, path string) error Exists(ctx context.Context, path string) (bool, error) URL(path string) string SignedURL(ctx context.Context, path string, expiry time.Duration) (string, error) List(ctx context.Context, prefix string) ([]FileInfo, error) } ``` ### FileInfo ```go type FileInfo struct { Path string `json:"path"` Size int64 `json:"size"` ContentType string `json:"content_type"` LastModified time.Time `json:"last_modified"` } ``` ### StorageConfig The storage configuration uses koanf struct tags and is part of the root `AppConfig`. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_STORAGE_DRIVER`). S3 storage uses minio-go under the hood. ```go type StorageConfig struct { Driver string `koanf:"driver"` Local LocalStorageConfig `koanf:"local"` S3 S3Config `koanf:"s3"` } type LocalStorageConfig struct { Path string `koanf:"path"` } type S3Config struct { Endpoint string `koanf:"endpoint"` Bucket string `koanf:"bucket"` AccessKey string `koanf:"access_key"` SecretKey string `koanf:"secret_key"` Region string `koanf:"region"` UseSSL bool `koanf:"use_ssl"` } ``` ### PutOption ```go type PutOption func(*putOptions) func WithContentType(ct string) PutOption func WithACL(acl string) PutOption func WithMetadata(meta map[string]string) PutOption ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewStorage` | `func NewStorage(cfg StorageConfig) (Storage, error)` | Creates a storage instance based on the configured driver | | `NewLocalStorage` | `func NewLocalStorage(cfg StorageConfig) Storage` | Creates a local filesystem storage | | `NewS3Storage` | `func NewS3Storage(cfg StorageConfig) (Storage, error)` | Creates an S3-compatible storage | ## Usage ### Uploading Files ```go store, err := storage.NewStorage(storage.StorageConfig{ Driver: "s3", S3: storage.S3Config{ Bucket: "my-bucket", Region: "us-east-1", AccessKey: "AKIA...", SecretKey: "secret", UseSSL: true, }, }) if err != nil { log.Fatalf("failed to create storage: %v", err) } file, header, _ := r.FormFile("avatar") defer file.Close() path := fmt.Sprintf("avatars/%s/%s", userID, header.Filename) err = store.Put(ctx, path, file, storage.WithContentType(header.Header.Get("Content-Type")), storage.WithACL("public-read"), ) ``` ### Downloading Files ```go reader, err := store.Get(ctx, "avatars/user-123/photo.jpg") if err != nil { return errors.NotFound("File", path) } defer reader.Close() io.Copy(w, reader) ``` ### Generating Signed URLs ```go url, err := store.SignedURL(ctx, "documents/report.pdf", 15*time.Minute) if err != nil { return err } // url is a time-limited pre-signed URL for the file ``` ### Listing Files ```go files, err := store.List(ctx, "avatars/user-123/") if err != nil { return err } for _, f := range files { fmt.Printf("Path: %s, Size: %d, Type: %s\n", f.Path, f.Size, f.ContentType) } ``` ### Deleting Files ```go err := store.Delete(ctx, "avatars/user-123/old-photo.jpg") ``` ### Local Filesystem Storage ```go store := storage.NewLocalStorage(storage.StorageConfig{ Driver: "local", Local: storage.LocalStorageConfig{ Path: "./uploads", }, }) url := store.URL("avatars/photo.jpg") // -> "./uploads/avatars/photo.jpg" ``` ### Configuration via config.yaml ```yaml storage: driver: s3 # "local" or "s3" local: path: "./uploads" s3: endpoint: "s3.amazonaws.com" bucket: my-bucket region: us-east-1 access_key: "AKIA..." secret_key: "secret" use_ssl: true ``` Environment variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_STORAGE_DRIVER=s3 export GOFASTA_STORAGE_S3_BUCKET=my-bucket export GOFASTA_STORAGE_S3_ACCESS_KEY=AKIA... ``` ## Related Pages - [Config](/docs/api-reference/config) -- Storage configuration loading - [HTTP Utilities](/docs/api-reference/http-utilities) -- File upload handling in controllers - [Encryption](/docs/api-reference/encryption) -- Encrypt files before storage --- ## /docs/api-reference/mailer — Mailer > Email sending with SMTP, SendGrid, and Brevo drivers, template rendering, and attachments. # Mailer The `mailer` package provides a unified email sending interface with support for SMTP, SendGrid, and Brevo (formerly Sendinblue) drivers. It includes HTML template rendering, attachments, and batch sending. ## Import ```go import "github.com/gofastadev/gofasta/pkg/mailer" ``` ## Key Types ### Mailer ```go type Mailer interface { Send(ctx context.Context, msg Message) error SendBatch(ctx context.Context, msgs []Message) error Close() error } ``` ### Message ```go type Message struct { From Address `json:"from"` To []Address `json:"to"` CC []Address `json:"cc,omitempty"` BCC []Address `json:"bcc,omitempty"` ReplyTo *Address `json:"reply_to,omitempty"` Subject string `json:"subject"` Body string `json:"body"` HTMLBody string `json:"html_body,omitempty"` Attachments []Attachment `json:"attachments,omitempty"` Headers map[string]string `json:"headers,omitempty"` } ``` ### Address ```go type Address struct { Name string `json:"name"` Email string `json:"email"` } ``` ### Attachment ```go type Attachment struct { Filename string `json:"filename"` ContentType string `json:"content_type"` Content []byte `json:"-"` } ``` ### EmailConfig The email configuration uses koanf struct tags and is part of the root `AppConfig`. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_EMAIL_PROVIDER`). Email templates use Go's `html/template` for rendering. ```go type EmailConfig struct { Provider string `koanf:"provider"` FromName string `koanf:"from_name"` FromAddress string `koanf:"from_address"` SMTP SMTPConfig `koanf:"smtp"` SendGrid SendGridConfig `koanf:"sendgrid"` Brevo BrevoConfig `koanf:"brevo"` } type SMTPConfig struct { Host string `koanf:"host"` Port int `koanf:"port"` Username string `koanf:"username"` Password string `koanf:"password"` UseTLS bool `koanf:"use_tls"` } type SendGridConfig struct { APIKey string `koanf:"api_key"` } type BrevoConfig struct { APIKey string `koanf:"api_key"` } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewMailer` | `func NewMailer(cfg MailerConfig) (Mailer, error)` | Creates a mailer using the configured driver | | `NewSMTPMailer` | `func NewSMTPMailer(cfg MailerConfig) (Mailer, error)` | Creates an SMTP mailer | | `NewSendGridMailer` | `func NewSendGridMailer(cfg MailerConfig) (Mailer, error)` | Creates a SendGrid mailer | | `NewBrevoMailer` | `func NewBrevoMailer(cfg MailerConfig) (Mailer, error)` | Creates a Brevo mailer | | `RenderTemplate` | `func RenderTemplate(templatePath string, data interface{}) (string, error)` | Renders an HTML email template with the given data | ## Usage ### Sending a Simple Email ```go m, err := mailer.NewMailer(mailer.MailerConfig{ Driver: "smtp", Host: "smtp.gmail.com", Port: 587, Username: "user@gmail.com", Password: "app-password", FromName: "My App", FromEmail: "noreply@myapp.com", }) if err != nil { log.Fatalf("failed to create mailer: %v", err) } defer m.Close() err = m.Send(ctx, mailer.Message{ To: []mailer.Address{{Name: "Jane", Email: "jane@example.com"}}, Subject: "Welcome to My App", Body: "Thanks for signing up!", }) ``` ### Sending with HTML Template Create a template file at `templates/welcome.html`: ```html

Welcome, {{.Name}}!

Your account has been created successfully.

Verify your email ``` Render and send: ```go htmlBody, err := mailer.RenderTemplate("templates/welcome.html", map[string]string{ "Name": "Jane", "VerifyURL": "https://myapp.com/verify?token=abc123", }) if err != nil { return err } err = m.Send(ctx, mailer.Message{ To: []mailer.Address{{Name: "Jane", Email: "jane@example.com"}}, Subject: "Welcome to My App", HTMLBody: htmlBody, }) ``` ### Sending with Attachments ```go pdfContent, _ := os.ReadFile("invoice.pdf") err = m.Send(ctx, mailer.Message{ To: []mailer.Address{{Email: "customer@example.com"}}, Subject: "Your Invoice", Body: "Please find your invoice attached.", Attachments: []mailer.Attachment{ { Filename: "invoice.pdf", ContentType: "application/pdf", Content: pdfContent, }, }, }) ``` ### Configuration via config.yaml ```yaml email: provider: sendgrid # "smtp", "sendgrid", or "brevo" from_name: "My App" from_address: "noreply@myapp.com" smtp: host: smtp.gmail.com port: 587 username: "user@gmail.com" password: "app-password" use_tls: true sendgrid: api_key: "SG.xxxxx" brevo: api_key: "xkeysib-xxxxx" ``` Environment variable overrides use the `GOFASTA_` prefix: ```bash export GOFASTA_EMAIL_PROVIDER=sendgrid export GOFASTA_EMAIL_SENDGRID_API_KEY=SG.xxxxx export GOFASTA_EMAIL_FROM_ADDRESS=noreply@myapp.com ``` ## Related Pages - [Config](/docs/api-reference/config) -- Mailer configuration loading - [Queue](/docs/api-reference/queue) -- Queue email sending for async delivery - [Notifications](/docs/api-reference/notifications) -- Higher-level notification dispatch - [Slack](/docs/api-reference/slack) -- Outbound chat sibling - [WhatsApp](/docs/api-reference/whatsapp) -- Outbound WhatsApp sibling - [Push Notifications](/docs/api-reference/push) -- Outbound mobile push sibling - [i18n](/docs/api-reference/i18n) -- Translate email template content per locale --- ## /docs/api-reference/slack — Slack > Outbound Slack messaging via incoming webhooks or bot tokens, with file uploads and threading support. # Slack The `slack` package provides outbound Slack messaging primitives. It is the chat counterpart to [`pkg/mailer`](/docs/api-reference/mailer): a `Sender` interface and one or more concrete implementations selected by configuration. Two delivery modes ship in the standard build: - **`webhook`** — the simplest path. A single Incoming Webhook URL is configured per app; `PostMessage` POSTs the payload to that URL. Channel/icon/username are determined by the webhook owner; the `Channel` field on `Message` is ignored. **Files cannot be uploaded** in webhook mode. - **`api`** — uses bot tokens against `api.slack.com`. `PostMessage` hits `chat.postMessage`, `UploadFile` hits `files.uploadV2` (the modern 2-step external-upload + complete flow). Supports threading, blocks, attachments, and per-call channel overrides. Inbound interactivity (Slack POSTing to your service when a user clicks a button) is intentionally **not** in this package — that webhook belongs in the service that owns the domain action; only signing-secret verification and `action_id` routing live there. This package stays focused on outbound traffic so swapping providers is trivial. ## Import ```go import "github.com/gofastadev/gofasta/pkg/slack" ``` ## Key Types ### Sender ```go type Sender interface { Name() string PostMessage(ctx context.Context, msg Message) (*PostResult, error) UploadFile(ctx context.Context, file FileUpload) (*PostResult, error) } ``` Named `Sender` (not `SlackSender`) because callers import the package as `slack` — `slack.Sender` reads naturally; `slack.SlackSender` stutters. ### Message ```go type Message struct { Channel string // channel ID (e.g. "C04JB471TQU"); ignored by webhook senders Text string BlocksJSON string // raw block-kit JSON (e.g. `[{"type":"section",...}]`) AttachmentsJSON string // raw attachments JSON (legacy; prefer Blocks) ThreadTimestamp string // optional — reply-in-thread IconEmoji string // optional — overrides default icon IconURL string Username string // optional — overrides bot display name UnfurlLinks *bool // tri-state — nil = provider default UnfurlMedia *bool } ``` Only one of (`Text`, `BlocksJSON`, `AttachmentsJSON`) is required; passing several is allowed — Slack uses `Text` as the notification fallback when blocks are present. `BlocksJSON` and `AttachmentsJSON` are passed through as raw JSON strings so callers can construct rich Block Kit messages without taking a dependency on a Slack SDK. **Callers are responsible for valid JSON.** ### FileUpload ```go type FileUpload struct { Channels []string // channel IDs to share the upload into Filename string Title string InitialComment string Content []byte ContentType string // MIME type ThreadTimestamp string } ``` The API sender handles the 2-step `files.uploadV2` flow (get URL → PUT bytes → complete) under the hood. ### PostResult ```go type PostResult struct { OK bool Channel string // canonical channel ID Slack chose Timestamp string // message ts — unique per channel ProviderMsgID string // alias for Timestamp; included for parity with pkg/whatsapp RawResponseJSON string // entire response body, for debugging } ``` `Timestamp` is the message identifier within a channel — store it if you want to thread replies, edit, or delete the message later. ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewSlackSender` | `func NewSlackSender(cfg *config.SlackConfig, logger *slog.Logger) (Sender, error)` | Factory: returns the sender selected by `cfg.Provider`. Used by Wire DI. | | `NewWebhookSender` | `func NewWebhookSender(webhookURL string, logger *slog.Logger) *WebhookSender` | Direct constructor for the webhook sender. | | `NewAPISender` | `func NewAPISender(token string, logger *slog.Logger) *APISender` | Direct constructor for the bot-token sender. | ## Configuration ### `config.yaml` ```yaml slack: provider: webhook # "" (disabled), "webhook", or "api" # webhook mode webhook_url: https://hooks.slack.com/services/T0.../B0.../... # api mode bot_token: xoxb-your-bot-token # consumed by inbound interactivity handlers in your app code, # not by pkg/slack itself — but lives here so all slack config # is in one block. signing_secret: your-signing-secret ``` ### Env vars ```bash export GOFASTA_SLACK_PROVIDER=api export GOFASTA_SLACK_BOT_TOKEN=xoxb-... export GOFASTA_SLACK_SIGNING_SECRET=... ``` ## Usage ### Send a plain-text message (webhook) ```go import "github.com/gofastadev/gofasta/pkg/slack" _, err := sender.PostMessage(ctx, slack.Message{ Text: "Deploy started for v1.4.2", }) if err != nil { return fmt.Errorf("slack post: %w", err) } ``` ### Send a Block Kit message (api) ```go blocks := `[ {"type":"header","text":{"type":"plain_text","text":"Deploy succeeded"}}, {"type":"section","fields":[ {"type":"mrkdwn","text":"*Service:*\nuser-api"}, {"type":"mrkdwn","text":"*Version:*\nv1.4.2"} ]} ]` res, err := sender.PostMessage(ctx, slack.Message{ Channel: "C04JB471TQU", Text: "Deploy succeeded for user-api v1.4.2", // notification fallback BlocksJSON: blocks, }) if err != nil { return err } log.Info("slack message posted", "ts", res.Timestamp) ``` ### Reply in a thread ```go _, err := sender.PostMessage(ctx, slack.Message{ Channel: "C04JB471TQU", Text: "Health check OK at " + time.Now().Format(time.RFC3339), ThreadTimestamp: parentTimestamp, // from a prior PostResult.Timestamp }) ``` ### Upload a file (api only) ```go res, err := sender.UploadFile(ctx, slack.FileUpload{ Channels: []string{"C04JB471TQU"}, Filename: "report.csv", Title: "Weekly metrics", InitialComment: "Here is the weekly metrics CSV.", Content: csvBytes, ContentType: "text/csv", }) if err != nil { return err } ``` The webhook sender returns a clear error from `UploadFile` — incoming webhooks cannot upload files; pick the `api` provider if you need this. ### Disabled state (no provider selected) When `slack.provider` is empty, `NewSlackSender` returns a noop sender whose `PostMessage` and `UploadFile` both return `slack: not configured (set slack.provider in config.yaml to enable)`. Surface this in your tests so misconfigurations are loud rather than silent. ## Related Pages - [Mailer](/docs/api-reference/mailer) — outbound email - [Push Notifications](/docs/api-reference/push) — outbound mobile push - [WhatsApp](/docs/api-reference/whatsapp) — outbound WhatsApp messaging - [Notifications](/docs/api-reference/notifications) — multi-channel notification orchestration --- ## /docs/api-reference/whatsapp — WhatsApp > Outbound WhatsApp messaging via UltraMsg, Twilio, or Meta Cloud API — with media attachments and threaded replies. # WhatsApp The `whatsapp` package provides outbound WhatsApp messaging. It is the chat counterpart to [`pkg/mailer`](/docs/api-reference/mailer), [`pkg/slack`](/docs/api-reference/slack), and [`pkg/push`](/docs/api-reference/push). Three providers ship in the standard build: - **`ultramsg`** — third-party UltraMsg instance API. Simple form-encoded POSTs against an instance-scoped base URL. Good for dev / small deployments where the operator already runs a paid UltraMsg instance. - **`twilio`** — Twilio Programmable Messaging WhatsApp. HTTP Basic auth (Account SID + Auth Token), addresses formatted as `whatsapp:+E164`, sender numbers must be approved in the Twilio console. Production-grade. - **`meta`** — Meta WhatsApp Cloud API (Graph). Direct from Meta; requires an approved WhatsApp Business Account (WABA) and a phone-number-id. Bearer auth, JSON payloads, supports template messages and interactive components. All three implement the same `Sender` interface. Switching providers is a config-only change. Inbound webhook handlers (incoming messages, delivery receipts, read receipts) are intentionally **not** in this package — each provider has its own callback shape and signing scheme; the consumer should own that route directly. ## Import ```go import "github.com/gofastadev/gofasta/pkg/whatsapp" ``` ## Key Types ### Sender ```go type Sender interface { Name() string Send(ctx context.Context, msg Message) (*SendResult, error) DeleteMessage(ctx context.Context, providerMsgID string) error } ``` `DeleteMessage` is best-effort — only some providers (UltraMsg, Meta) expose it. Twilio does not. Implementations that don't support delete return `ErrUnsupported` so callers can log and move on. ### Message ```go type Message struct { To string // E.164 phone number (e.g. "+250788123456") Body string Media *MediaAttachment // optional attachment ReplyToProviderMsgID string // quote-reply / threaded reply PreviewURL *bool // tri-state — nil = provider default } ``` `To` is plain E.164 — the provider adapts to its own wire format (Twilio's `whatsapp:+250...`, UltraMsg's bare `250...`) inside the implementation. `PreviewURL` toggles link unfurls. `nil` = provider default. Only Meta and Twilio honor it; UltraMsg ignores. ### MediaAttachment One image / document / video / audio / sticker: ```go type MediaAttachment struct { Type string // "image" | "document" | "video" | "audio" | "sticker" URL string Content []byte Filename string // required for documents; ignored for image/video ContentType string // MIME type; required when sending via Content Caption string // image/video/document caption } ``` Two delivery modes: - **URL** — provider downloads the asset from a public URL. Fast, no upload step. The URL must be reachable from the provider's network and stable for at least the duration of the call. - **Content** — raw bytes. The provider handles the upload (Meta: `POST /media`; UltraMsg: separate media-upload endpoint then attaches the returned URL). At least **one** of `URL` or `Content` must be set; if both are present, providers prefer `URL` (cheaper). ### SendResult ```go type SendResult struct { OK bool ProviderMsgID string Status string RawResponseJSON string // entire response body, for debugging / support tickets } ``` `Status` reflects the provider's reported state at SEND time — **not** delivery confirmation (which arrives later via webhook). ## Errors ```go var ErrUnsupported = errors.New("whatsapp: operation not supported by this provider") ``` Returned by providers (and the noop sender) for verbs they don't implement — most commonly `DeleteMessage` on Twilio. ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewSender` | `func NewSender(cfg *config.WhatsAppConfig, logger *slog.Logger) (Sender, error)` | Factory: returns the sender selected by `cfg.Provider`. Used by Wire DI. | | `NewUltraMsgSender` | `func NewUltraMsgSender(cfg config.WhatsAppUltraMsgConfig, logger *slog.Logger) Sender` | Direct constructor for the UltraMsg sender. | | `NewTwilioSender` | `func NewTwilioSender(cfg config.WhatsAppTwilioConfig, logger *slog.Logger) Sender` | Direct constructor for the Twilio sender. | | `NewMetaSender` | `func NewMetaSender(cfg config.WhatsAppMetaConfig, logger *slog.Logger) Sender` | Direct constructor for the Meta Cloud API sender. | ## Configuration ### `config.yaml` ```yaml whatsapp: provider: meta # "" (disabled), "ultramsg", "twilio", or "meta" ultramsg: base_url: https://api.ultramsg.com instance_id: instance60301 token: your-instance-token twilio: account_sid: ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx auth_token: your-auth-token from_number: "+14155238886" meta: access_token: EAA... # Graph access token phone_number_id: "123456789012345" # WABA phone number id api_version: v20.0 # optional; defaults to v20.0 ``` ### Env vars ```bash export GOFASTA_WHATSAPP_PROVIDER=meta export GOFASTA_WHATSAPP_META_ACCESS_TOKEN=EAA... export GOFASTA_WHATSAPP_META_PHONE_NUMBER_ID=123456789012345 ``` ## Usage ### Send a plain text ```go import "github.com/gofastadev/gofasta/pkg/whatsapp" res, err := sender.Send(ctx, whatsapp.Message{ To: "+250788123456", Body: "Your code is 8421. It expires in 5 minutes.", }) if err != nil { return fmt.Errorf("whatsapp send: %w", err) } log.Info("whatsapp queued", "msg_id", res.ProviderMsgID, "status", res.Status) ``` ### Send an image with a caption ```go res, err := sender.Send(ctx, whatsapp.Message{ To: "+250788123456", Body: "Here is your receipt for order #42.", // sent as text + media caption Media: &whatsapp.MediaAttachment{ Type: "image", URL: "https://cdn.example.com/receipts/42.png", Caption: "Order #42 receipt", }, }) ``` ### Send a PDF document (raw bytes) ```go pdfBytes, _ := os.ReadFile("invoice.pdf") res, err := sender.Send(ctx, whatsapp.Message{ To: "+250788123456", Media: &whatsapp.MediaAttachment{ Type: "document", Content: pdfBytes, Filename: "invoice-042.pdf", ContentType: "application/pdf", Caption: "Your invoice for January", }, }) ``` ### Reply to an earlier message (quote) ```go res, err := sender.Send(ctx, whatsapp.Message{ To: "+250788123456", Body: "We received your photo — processing now.", ReplyToProviderMsgID: incomingMessageID, // wamid.../SID/UltraMsg id }) ``` ### Best-effort delete ```go err := sender.DeleteMessage(ctx, providerMsgID) if errors.Is(err, whatsapp.ErrUnsupported) { log.Info("provider does not expose delete — message stays in user's history") return nil } if err != nil { return err } ``` ### Disabled state (no provider selected) When `whatsapp.provider` is empty, `NewSender` returns a noop sender whose `Send` returns `whatsapp: not configured (set whatsapp.provider in config.yaml to enable)`. Tests that exercise message-sending paths will surface the misconfiguration immediately rather than silently dropping messages. ## Adding a new provider To add MessageBird / Vonage / GreenAPI / 360dialog / Bird / etc.: 1. Drop a `.go` file implementing `Sender` into `pkg/whatsapp`. Mirror the structure of `ultramsg.go` or `meta.go`. 2. Add a sub-config struct in `pkg/config/config.go` and reference it from `WhatsAppConfig`. 3. Add a switch case in `provider.go`'s `NewSender`. No code outside `pkg/whatsapp` needs to change. ## Related Pages - [Mailer](/docs/api-reference/mailer) — outbound email - [Slack](/docs/api-reference/slack) — outbound chat - [Push Notifications](/docs/api-reference/push) — outbound mobile push - [Notifications](/docs/api-reference/notifications) — multi-channel notification orchestration --- ## /docs/api-reference/push — Push Notifications > Outbound mobile push notifications via Firebase Cloud Messaging, with a provider-pluggable Sender interface. # Push The `push` package provides outbound mobile push notifications. It is the mobile counterpart to [`pkg/mailer`](/docs/api-reference/mailer), [`pkg/slack`](/docs/api-reference/slack), and [`pkg/whatsapp`](/docs/api-reference/whatsapp). One provider ships in the standard build (Firebase Cloud Messaging); the `Sender` interface is provider-agnostic so swapping providers is a config-only change. Inbound webhooks (delivery receipts, click events, APNs token-refresh) are intentionally **not** in this package — each provider has its own callback shape and signing scheme; the consumer should own that route directly. ## Import ```go import "github.com/gofastadev/gofasta/pkg/push" ``` ## Key Types ### Sender The interface every provider implements. Inject via DI; the concrete sender is selected at boot by config. ```go type Sender interface { Name() string SendToTokens(ctx context.Context, tokens []string, msg Message) ([]TokenResult, error) SendToTopic(ctx context.Context, topic string, msg Message) (*SendResult, error) SubscribeToTopic(ctx context.Context, topic string, tokens []string) (*TopicMembershipResult, error) UnsubscribeFromTopic(ctx context.Context, topic string, tokens []string) (*TopicMembershipResult, error) } ``` Why three send verbs instead of one polymorphic `Send`? Mobile push providers strictly distinguish *targeting modes*: - **Tokens** — addressed, multicast. The provider returns one per-token result so the caller can prune dead tokens. - **Topic** — fan-out by subscription. The provider returns one aggregate result; per-recipient outcomes are not visible. - **Subscribe / Unsubscribe** — round out the topic story. Providers that don't support a verb return `ErrUnsupported` so callers can log and move on. ### Message ```go type Message struct { Title string Body string Data map[string]string // structured payload the app reads on tap Priority Priority // PriorityNormal | PriorityHigh ImageURL string Sound string // "default" or empty for silent Badge *int // iOS badge count; nil = leave unchanged ClickAction string // Android-only intent name // Provider-specific escape hatches; rarely needed. AndroidOverride map[string]any AppleOverride map[string]any WebPushOverride map[string]any } ``` `Data` values are constrained to strings — that's the FCM data-message contract, and Apple/Expo follow similar shapes. Encode complex values as JSON if needed. ### Priority Maps to platform-specific priority enums: ```go type Priority string const ( PriorityNormal Priority = "normal" PriorityHigh Priority = "high" ) ``` - FCM Android: `PriorityNormal` → `"normal"`, `PriorityHigh` → `"high"` - APNs (via FCM): `PriorityNormal` → `"5"`, `PriorityHigh` → `"10"` `PriorityHigh` wakes the app immediately and shows the notification; `PriorityNormal` can be deferred by the OS to save battery. ### TokenResult Per-token outcome of a multicast send: ```go type TokenResult struct { Token string OK bool ProviderMsgID string Error string ErrorCode string // "UNREGISTERED", "INVALID_ARGUMENT", "QUOTA_EXCEEDED", ... } func (r TokenResult) IsTokenInvalidated() bool ``` `IsTokenInvalidated()` returns `true` when the per-token error indicates the token should be removed from your registry — drive automatic cleanup with this after every multicast. ### SendResult Provider acknowledgment of a topic-targeted send: ```go type SendResult struct { OK bool ProviderMsgID string Status string RawResponseJSON string } ``` `Status` reflects what the provider returned at SEND time — **not** delivery confirmation (which arrives later via webhook). ### TopicMembershipResult Aggregate outcome of a topic (un)subscribe call: ```go type TopicMembershipResult struct { SuccessCount int FailureCount int Errors []string } ``` ## Errors ```go var ErrUnsupported = errors.New("push: operation not supported by this provider") var ErrNotConfigured = errors.New("push: no provider configured (set PUSH_PROVIDER)") ``` `ErrNotConfigured` is what the noop sender returns when no provider is selected — surfacing the misconfig as a runtime error rather than a silent no-op so it shows up in your tests instead of in production. ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewSender` | `func NewSender(cfg *config.PushConfig, logger *slog.Logger) (Sender, error)` | Factory: returns the sender selected by `cfg.Provider`. Used by Wire DI. | | `NewFCMSender` | `func NewFCMSender(cfg FCMConfig, logger *slog.Logger) (*FCMSender, error)` | Direct constructor for the FCM sender. | ## Configuration ### `config.yaml` ```yaml push: provider: fcm # "" (disabled), or "fcm" fcm: credentials_file_path: configs/firebase-service-account.json # OR inline (useful for containerized deploys): # credentials_json: | # {"type":"service_account",...} project_id: "" # optional override; read from credentials when empty ``` ### Env vars ```bash export GOFASTA_PUSH_PROVIDER=fcm export GOFASTA_PUSH_FCM_CREDENTIALS_FILE_PATH=configs/firebase-service-account.json export GOFASTA_PUSH_FCM_PROJECT_ID=my-firebase-project ``` When both `credentials_json` (inline) and `credentials_file_path` are set, the inline JSON wins. ## Usage ### Send to a single device token ```go import "github.com/gofastadev/gofasta/pkg/push" results, err := sender.SendToTokens(ctx, []string{deviceToken}, push.Message{ Title: "Order shipped", Body: "Your order #42 just left the warehouse.", Priority: push.PriorityHigh, Data: map[string]string{ "screen": "shipment", "id": "42", }, Sound: "default", }) if err != nil { return fmt.Errorf("push call failed: %w", err) } // Per-token outcomes — the slice has one entry per input token, in order. for _, r := range results { if !r.OK { log.Warn("push failed", "token", r.Token, "code", r.ErrorCode, "err", r.Error) if r.IsTokenInvalidated() { // Revoke the token from your registry. tokenRepo.Delete(ctx, r.Token) } } } ``` ### Multicast with token cleanup ```go allTokens := userRepo.AllPushTokens(ctx, userID) results, err := sender.SendToTokens(ctx, allTokens, msg) if err != nil { return err } dead := []string{} for _, r := range results { if r.IsTokenInvalidated() { dead = append(dead, r.Token) } } if len(dead) > 0 { tokenRepo.DeleteMany(ctx, dead) } ``` ### Send to a topic ```go res, err := sender.SendToTopic(ctx, "promos", push.Message{ Title: "20% off this weekend", Body: "Tap to see eligible products.", Data: map[string]string{"campaign": "weekend-2026-02"}, }) if err != nil { return err } log.Info("topic push sent", "msg_id", res.ProviderMsgID, "status", res.Status) ``` ### Topic subscriptions ```go // Subscribe a batch of tokens server-side. Some providers don't expose this — // the noop sender does, and FCM does, but adding e.g. APNs-direct later may // require subscribing client-side via the SDK. res, err := sender.SubscribeToTopic(ctx, "promos", []string{tokenA, tokenB}) if errors.Is(err, push.ErrUnsupported) { log.Info("provider does not expose server-side subscribe — handle client-side") return nil } if err != nil { return err } log.Info("subscribed", "ok", res.SuccessCount, "fail", res.FailureCount) ``` ## Adding a new provider To add Expo / OneSignal / APNs-direct / AWS SNS / etc.: 1. Drop a `.go` file implementing `Sender` into `pkg/push`. Mirror the structure of `fcm.go`. 2. Add a sub-config struct in `pkg/config/config.go` and reference it from `PushConfig`. 3. Add a switch case in `factory.go`. No code outside `pkg/push` needs to change. ## Related Pages - [Mailer](/docs/api-reference/mailer) — outbound email - [Slack](/docs/api-reference/slack) — outbound chat - [WhatsApp](/docs/api-reference/whatsapp) — outbound WhatsApp messaging - [Notifications](/docs/api-reference/notifications) — multi-channel notification orchestration --- ## /docs/api-reference/notifications — Notifications > Multi-channel notification dispatch for email, SMS, Slack, and in-app (database) delivery. # Notifications The `notify` package provides a unified notification dispatch system that supports multiple delivery channels: **email** (via the `mailer` package), **SMS** (via Twilio), **Slack** (via incoming webhooks), and **database** (for in-app notification centers). Each channel is an independent struct that implements a small `ChannelSender` interface, so you can add your own channels without modifying the package. ## Import ```go import "github.com/gofastadev/gofasta/pkg/notify" ``` ## Key Types ### Channel ```go // Channel represents a notification delivery channel. type Channel string const ( ChannelEmail Channel = "email" ChannelSMS Channel = "sms" ChannelSlack Channel = "slack" ChannelDatabase Channel = "database" ) ``` ### Notification ```go // Notification holds the content to be sent across channels. type Notification struct { Subject string // email subject / SMS prefix Body string // plain text body HTMLBody string // HTML body (for email) Template string // email template name (optional) Data map[string]any // template data Channels []Channel // which channels to use (empty = all registered) } ``` ### Recipient ```go // Recipient represents who receives the notification. type Recipient struct { ID string // user ID (for database channel) Email string // for email channel Phone string // for SMS channel Name string // display name } ``` ### ChannelSender ```go // ChannelSender is implemented by each channel (email, SMS, Slack, database). type ChannelSender interface { Channel() Channel Send(ctx context.Context, recipient Recipient, notification Notification) error } ``` ### Notifier ```go // Notifier dispatches notifications to multiple channels. type Notifier struct { /* unexported fields */ } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewNotifier` | `func NewNotifier(logger *slog.Logger, senders ...ChannelSender) *Notifier` | Creates a notifier wired to the given channel senders | | `(n *Notifier).Send` | `func (n *Notifier) Send(ctx context.Context, recipient Recipient, notification Notification) error` | Dispatches the notification to every channel listed in `notification.Channels` (or all registered channels if empty) | | `(n *Notifier).RegisterChannel` | `func (n *Notifier) RegisterChannel(sender ChannelSender)` | Adds a channel sender at runtime | | `NewEmailChannel` | `func NewEmailChannel(sender mailer.EmailSender) *EmailChannel` | Email channel backed by the `mailer` package | | `NewSMSChannel` | `func NewSMSChannel(accountSID, authToken, fromNumber string) *SMSChannel` | Twilio-backed SMS channel | | `NewSlackChannel` | `func NewSlackChannel(webhookURL string) *SlackChannel` | Slack incoming-webhook channel | | `NewDatabaseChannel` | `func NewDatabaseChannel(db *gorm.DB) *DatabaseChannel` | Persists notifications to the `notifications` table for in-app delivery | ## Usage ### Setting Up a Notifier ```go import ( "log/slog" "github.com/gofastadev/gofasta/pkg/mailer" "github.com/gofastadev/gofasta/pkg/notify" ) // Build your channels first (each channel is independent). emailCh := notify.NewEmailChannel(smtpSender) // any mailer.EmailSender slackCh := notify.NewSlackChannel("https://hooks.slack.com/services/...") smsCh := notify.NewSMSChannel(accountSID, authToken, "+15551234567") dbCh := notify.NewDatabaseChannel(db) // *gorm.DB // Wire them into a notifier. notifier := notify.NewNotifier(slog.Default(), emailCh, slackCh, smsCh, dbCh) ``` ### Sending a Notification to All Registered Channels ```go err := notifier.Send(ctx, notify.Recipient{ ID: "user-123", Email: "user@example.com", Phone: "+15551234567", Name: "Jane Doe", }, notify.Notification{ Subject: "Order confirmed", Body: "Your order #12345 has been confirmed.", HTMLBody: "

Order confirmed

Your order #12345 has been confirmed.

", Data: map[string]any{"order_id": "12345"}, }, ) ``` When `Notification.Channels` is empty, the notifier fans out to every registered channel. Individual channel failures are logged and the last error is returned. ### Targeting Specific Channels ```go // Slack-only alert err := notifier.Send(ctx, notify.Recipient{Name: "ops"}, notify.Notification{ Subject: "Deploy started", Body: "Release v1.4.2 is deploying to production.", Channels: []notify.Channel{notify.ChannelSlack}, }, ) ``` ### Using an Email Template When `Notification.Template` is set, the email channel renders the named template from the mailer's template directory, passing `Data` as the template context. Otherwise it falls back to `HTMLBody`, then wraps `Body` in a `

` tag. ```go err := notifier.Send(ctx, recipient, notify.Notification{ Subject: "Welcome", Template: "welcome", Data: map[string]any{ "Name": recipient.Name, "AppName": "Acme", }, Channels: []notify.Channel{notify.ChannelEmail}, }) ``` ### Registering a Channel at Runtime ```go notifier.RegisterChannel(notify.NewSlackChannel(altWebhook)) ``` ### Writing a Custom Channel Implement `ChannelSender` — two methods — and register it with the notifier. ```go type WebhookChannel struct { url string hc *http.Client } func (c *WebhookChannel) Channel() notify.Channel { return "webhook" } func (c *WebhookChannel) Send(ctx context.Context, r notify.Recipient, n notify.Notification) error { body, _ := json.Marshal(map[string]any{ "to": r.Email, "subject": n.Subject, "body": n.Body, }) req, _ := http.NewRequestWithContext(ctx, http.MethodPost, c.url, bytes.NewReader(body)) req.Header.Set("Content-Type", "application/json") resp, err := c.hc.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode >= 400 { return fmt.Errorf("webhook returned %d", resp.StatusCode) } return nil } notifier.RegisterChannel(&WebhookChannel{url: "https://example.com/hook", hc: http.DefaultClient}) ``` ### Notification in a Service Layer ```go func (s *OrderService) ConfirmOrder(ctx context.Context, orderID string) error { order, err := s.repo.FindByID(ctx, orderID) if err != nil { return err } order.Status = "confirmed" if err := s.repo.Update(ctx, order); err != nil { return err } return s.notifier.Send(ctx, notify.Recipient{ID: order.UserID, Email: order.CustomerEmail}, notify.Notification{ Subject: "Order confirmed", Body: fmt.Sprintf("Your order #%s has been confirmed.", order.ID), Template: "order_confirmed", Data: map[string]any{"OrderID": order.ID, "Total": order.Total}, Channels: []notify.Channel{notify.ChannelEmail, notify.ChannelDatabase}, }, ) } ``` ## Database Channel Schema The database channel writes rows to a `notifications` table with this GORM model: ```go type DBNotification struct { ID uuid.UUID `gorm:"type:uuid;primary_key" json:"id"` UserID string `gorm:"not null;index" json:"userId"` Subject string `gorm:"not null" json:"subject"` Body string ` json:"body"` Data string `gorm:"type:text" json:"data"` // JSON-encoded ReadAt *time.Time ` json:"readAt,omitempty"` CreatedAt time.Time `gorm:"not null" json:"createdAt"` } ``` Run a migration to create this table before using the database channel. ## Related Pages - [Mailer](/docs/api-reference/mailer) — the email channel delegates to `mailer.EmailSender` - [Queue](/docs/api-reference/queue) — enqueue `Send` calls for async delivery - [WebSocket](/docs/api-reference/websocket) — push real-time notifications to connected clients - [Slack](/docs/api-reference/slack) — Slack channel delegates to `slack.Sender` - [WhatsApp](/docs/api-reference/whatsapp) — WhatsApp channel delegates to `whatsapp.Sender` - [Push Notifications](/docs/api-reference/push) — Mobile push channel delegates to `push.Sender` --- ## /docs/api-reference/websocket — WebSocket > WebSocket server with room management, broadcasting, client lifecycle, and message handling. # WebSocket The `websocket` package provides a WebSocket server with support for room-based messaging, broadcasting, client management, and message routing. It uses [gorilla/websocket](https://github.com/gorilla/websocket) under the hood and is built for real-time features such as chat, live notifications, and collaborative editing. ## Import ```go import "github.com/gofastadev/gofasta/pkg/websocket" ``` ## Key Types ### Hub The central coordinator that manages clients and rooms. ```go type Hub struct { Clients map[string]*Client Rooms map[string]*Room Register chan *Client Unregister chan *Client Broadcast chan Message } ``` ### Client ```go type Client struct { ID string UserID string Conn *ws.Conn Hub *Hub Rooms map[string]bool Send chan Message } ``` ### Room ```go type Room struct { ID string Clients map[string]*Client } ``` ### Message ```go type Message struct { Type string `json:"type"` Room string `json:"room,omitempty"` From string `json:"from,omitempty"` To string `json:"to,omitempty"` Payload map[string]interface{} `json:"payload"` } ``` ### MessageHandler ```go type MessageHandler func(client *Client, msg Message) ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewHub` | `func NewHub() *Hub` | Creates a new WebSocket hub | | `Run` | `func (h *Hub) Run(ctx context.Context)` | Starts the hub's event loop | | `HandleConnection` | `func (h *Hub) HandleConnection(w http.ResponseWriter, r *http.Request)` | Upgrades HTTP to WebSocket and registers the client | | `OnMessage` | `func (h *Hub) OnMessage(msgType string, handler MessageHandler)` | Registers a handler for a message type | | `BroadcastToRoom` | `func (h *Hub) BroadcastToRoom(room string, msg Message)` | Sends a message to all clients in a room | | `SendToClient` | `func (h *Hub) SendToClient(clientID string, msg Message) error` | Sends a message to a specific client | | `JoinRoom` | `func (c *Client) JoinRoom(roomID string)` | Adds a client to a room | | `LeaveRoom` | `func (c *Client) LeaveRoom(roomID string)` | Removes a client from a room | ## Usage ### Setting Up the WebSocket Server ```go hub := websocket.NewHub() go hub.Run(ctx) r.Get("/ws", hub.HandleConnection) ``` ### Handling Messages ```go hub.OnMessage("chat", func(client *websocket.Client, msg websocket.Message) { // Broadcast chat messages to the room hub.BroadcastToRoom(msg.Room, websocket.Message{ Type: "chat", Room: msg.Room, From: client.UserID, Payload: msg.Payload, }) }) hub.OnMessage("typing", func(client *websocket.Client, msg websocket.Message) { hub.BroadcastToRoom(msg.Room, websocket.Message{ Type: "typing", Room: msg.Room, From: client.UserID, Payload: map[string]interface{}{"is_typing": true}, }) }) ``` ### Room Management ```go hub.OnMessage("join_room", func(client *websocket.Client, msg websocket.Message) { roomID := msg.Payload["room_id"].(string) client.JoinRoom(roomID) hub.BroadcastToRoom(roomID, websocket.Message{ Type: "user_joined", Room: roomID, Payload: map[string]interface{}{ "user_id": client.UserID, }, }) }) hub.OnMessage("leave_room", func(client *websocket.Client, msg websocket.Message) { roomID := msg.Payload["room_id"].(string) client.LeaveRoom(roomID) hub.BroadcastToRoom(roomID, websocket.Message{ Type: "user_left", Room: roomID, Payload: map[string]interface{}{ "user_id": client.UserID, }, }) }) ``` ### Direct Messaging ```go hub.OnMessage("direct_message", func(client *websocket.Client, msg websocket.Message) { targetID := msg.To err := hub.SendToClient(targetID, websocket.Message{ Type: "direct_message", From: client.UserID, Payload: msg.Payload, }) if err != nil { client.Send <- websocket.Message{ Type: "error", Payload: map[string]interface{}{"message": "user not connected"}, } } }) ``` ### Client-Side Example ```javascript const ws = new WebSocket("ws://localhost:8080/ws?token=jwt-token-here"); ws.onmessage = (event) => { const msg = JSON.parse(event.data); console.log(msg.type, msg.payload); }; ws.send(JSON.stringify({ type: "chat", room: "room-123", payload: { text: "Hello, world!" } })); ``` ## Related Pages - [Auth](/docs/api-reference/auth) -- Authenticate WebSocket connections via JWT - [Middleware](/docs/api-reference/middleware) -- Apply middleware before WebSocket upgrade - [Notifications](/docs/api-reference/notifications) -- Real-time notification delivery via WebSocket --- ## /docs/api-reference/scheduler — Scheduler > Cron-based job scheduling with job registration, schedule expressions, and lifecycle management. # Scheduler The `scheduler` package provides cron-based job scheduling for recurring tasks. It uses [robfig/cron/v3](https://github.com/robfig/cron) under the hood with second-precision support, job registration, and graceful shutdown. ## Import ```go import "github.com/gofastadev/gofasta/pkg/scheduler" ``` ## Key Types ### Job The `Job` interface must be implemented by every cron job. ```go type Job interface { Name() string Run() } ``` ### Scheduler ```go type Scheduler struct { cron *cron.Cron logger *slog.Logger } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `New` | `func New(logger *slog.Logger) *Scheduler` | Creates a new scheduler with second-precision cron support | | `Register` | `func (s *Scheduler) Register(schedule string, job Job) error` | Registers a job with a cron schedule expression | | `Start` | `func (s *Scheduler) Start()` | Starts the scheduler (non-blocking) | | `Stop` | `func (s *Scheduler) Stop()` | Waits for running jobs to finish, then stops the scheduler | ## Schedule Expressions The scheduler supports standard cron expressions (with an optional seconds field) and predefined shortcuts. | Expression | Description | |------------|-------------| | `0 * * * * *` | Every minute (with seconds field) | | `0 0 * * * *` | Every hour | | `0 0 0 * * *` | Every day at midnight | | `0 */5 * * *` | Every 5 minutes (standard 5-field) | | `0 0 */6 * * *` | Every 6 hours (with seconds field) | | `0 9 * * 1-5` | Weekdays at 9:00 AM | ## Configuration Job schedules are defined in `config.yaml` under the `jobs:` section: ```yaml jobs: - name: cleanup-sessions schedule: "*/5 * * * *" enabled: true - name: generate-reports schedule: "0 2 * * *" enabled: true - name: health-ping schedule: "*/30 * * * * *" enabled: false ``` The corresponding config type: ```go type JobConfig struct { Name string `koanf:"name"` Schedule string `koanf:"schedule"` Enabled bool `koanf:"enabled"` } ``` ## Usage ### Implementing a Job ```go type CleanupSessionsJob struct { sessionStore *sessions.Store logger *slog.Logger } func (j *CleanupSessionsJob) Name() string { return "cleanup-sessions" } func (j *CleanupSessionsJob) Run() { j.logger.Info("cleaning up expired sessions") if err := j.sessionStore.Cleanup(); err != nil { j.logger.Error("session cleanup failed", "error", err) } } ``` ### Setting Up the Scheduler The scheduler is typically configured in `cmd/serve.go`: ```go sched := scheduler.New(logger) // Register jobs err := sched.Register("*/5 * * * *", &CleanupSessionsJob{ sessionStore: sessionStore, logger: logger, }) if err != nil { log.Fatalf("failed to register job: %v", err) } err = sched.Register("0 2 * * *", &GenerateReportsJob{ reportService: reportService, logger: logger, }) if err != nil { log.Fatalf("failed to register job: %v", err) } ``` ### Starting and Stopping ```go // Start the scheduler (non-blocking) sched.Start() // Graceful shutdown quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit sched.Stop() ``` ## Related Pages - [Queue](/docs/api-reference/queue) -- Queue long-running jobs instead of running them inline - [Logger](/docs/api-reference/logger) -- Log scheduler activity - [Health](/docs/api-reference/health) -- Monitor scheduler status in health checks --- ## /docs/api-reference/queue — Queue > Async task processing with Redis using hibiken/asynq for background workers, retry policies, and concurrency control. # Queue The `queue` package provides async task processing backed by Redis using [hibiken/asynq](https://github.com/hibiken/asynq). It supports enqueuing tasks for background processing, handler registration, configurable concurrency, and named queue priorities. ## Import ```go import "github.com/gofastadev/gofasta/pkg/queue" ``` ## Key Types ### QueueService ```go type QueueService interface { Enqueue(ctx context.Context, taskName string, payload []byte, opts ...asynq.Option) (*asynq.TaskInfo, error) RegisterHandler(pattern string, handler asynq.Handler) Start() error Shutdown() } ``` ### AsynqQueue ```go type AsynqQueue struct { client *asynq.Client server *asynq.Server mux *asynq.ServeMux } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewQueueService` | `func NewQueueService(cfg *config.QueueConfig, logger *slog.Logger) (QueueService, error)` | Creates a queue service from config; returns nil if disabled | | `NewAsynqQueue` | `func NewAsynqQueue(cfg *config.QueueConfig) (*AsynqQueue, error)` | Creates an asynq-based queue with Redis connection | | `Enqueue` | `func (q *AsynqQueue) Enqueue(ctx context.Context, taskName string, payload []byte, opts ...asynq.Option) (*asynq.TaskInfo, error)` | Enqueues a task for background processing | | `RegisterHandler` | `func (q *AsynqQueue) RegisterHandler(pattern string, handler asynq.Handler)` | Registers a handler for a task pattern | | `Start` | `func (q *AsynqQueue) Start() error` | Starts processing tasks | | `Shutdown` | `func (q *AsynqQueue) Shutdown()` | Gracefully shuts down the queue server and client | ## Task Handlers Task handlers implement the `asynq.Handler` interface with a `Handle(ctx context.Context, task *asynq.Task) error` method. You can also use `asynq.HandlerFunc` for simple cases. ```go // As a struct implementing asynq.Handler type SendEmailHandler struct { mailClient *mailer.Client } func (h *SendEmailHandler) Handle(ctx context.Context, task *asynq.Task) error { var payload EmailPayload if err := json.Unmarshal(task.Payload(), &payload); err != nil { return err } return h.mailClient.Send(ctx, payload.To, payload.Subject, payload.Body) } ``` Each task type typically has an `Enqueue` helper: ```go const TaskSendEmail = "email:send" func EnqueueSendEmail(q queue.QueueService, ctx context.Context, to, subject, body string) error { payload, _ := json.Marshal(EmailPayload{To: to, Subject: subject, Body: body}) _, err := q.Enqueue(ctx, TaskSendEmail, payload) return err } ``` ## Usage ### Setting Up the Queue ```go queueService, err := queue.NewQueueService(&cfg.Queue, logger) if err != nil { log.Fatalf("failed to create queue: %v", err) } if queueService == nil { logger.Info("queue is disabled") } ``` ### Registering Handlers ```go queueService.RegisterHandler("email:send", &SendEmailHandler{ mailClient: mailClient, }) queueService.RegisterHandler("image:process", asynq.HandlerFunc( func(ctx context.Context, task *asynq.Task) error { // process the image... return nil }, )) ``` ### Enqueuing Tasks ```go // Immediate execution payload, _ := json.Marshal(map[string]string{ "to": "user@example.com", "subject": "Welcome!", "body": "Thanks for signing up.", }) _, err := queueService.Enqueue(ctx, "email:send", payload) // With options (delay, max retries, queue name) _, err = queueService.Enqueue(ctx, "email:send_reminder", payload, asynq.ProcessIn(24*time.Hour), asynq.MaxRetry(5), asynq.Queue("critical"), ) ``` ### Starting and Stopping Workers ```go // Start processing tasks (blocks) go func() { if err := queueService.Start(); err != nil { log.Fatalf("queue error: %v", err) } }() // Graceful shutdown quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit queueService.Shutdown() ``` ### Configuration via config.yaml ```yaml queue: enabled: true concurrency: 10 queues: critical: 6 default: 3 low: 1 redis: host: localhost port: "6379" password: "" db: 0 ``` Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_QUEUE_CONCURRENCY`, `GOFASTA_QUEUE_REDIS_HOST`). ## Related Pages - [Scheduler](/docs/api-reference/scheduler) -- Cron-based scheduling pairs well with queued jobs - [Mailer](/docs/api-reference/mailer) -- Offload email sending to the queue - [Resilience](/docs/api-reference/resilience) -- Retry and circuit breaker for job handlers --- ## /docs/api-reference/resilience — Resilience > Circuit breaker, retry with backoff, and fault-tolerance patterns using failsafe-go. # Resilience The `resilience` package provides fault-tolerance patterns including circuit breakers and retry with exponential backoff. It uses [failsafe-go](https://github.com/failsafe-go/failsafe-go) under the hood. All patterns are plain Go functions -- no decorators or middleware wrappers. ## Import ```go import "github.com/gofastadev/gofasta/pkg/resilience" ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewRetryPolicy` | `func NewRetryPolicy[T any](maxRetries int, delay time.Duration) retrypolicy.RetryPolicy[T]` | Creates a retry policy with exponential backoff | | `NewCircuitBreaker` | `func NewCircuitBreaker[T any](failureThreshold uint, delay time.Duration) circuitbreaker.CircuitBreaker[T]` | Creates a circuit breaker that opens after a failure threshold | | `Execute` | `func Execute[T any](fn func() (T, error), policies ...failsafe.Policy[T]) (T, error)` | Runs a function with the given failsafe policies | ## Usage ### Retry with Exponential Backoff ```go retryPolicy := resilience.NewRetryPolicy[*http.Response](3, 100*time.Millisecond) resp, err := resilience.Execute(func() (*http.Response, error) { return externalAPI.Call(ctx, payload) }, retryPolicy) ``` The retry policy uses exponential backoff from `delay` up to `delay * 10`. For example, with a 100ms delay: 100ms, 200ms, 400ms, etc., capped at 1s. ### Circuit Breaker ```go cb := resilience.NewCircuitBreaker[*PaymentResult](5, 30*time.Second) result, err := resilience.Execute(func() (*PaymentResult, error) { return paymentClient.Charge(ctx, amount) }, cb) if err != nil { // Circuit may be open -- handle gracefully return err } ``` The circuit breaker opens after `failureThreshold` consecutive failures and stays open for `delay` before transitioning to half-open. ### Combining Policies You can compose retry and circuit breaker policies together. Policies are applied in order -- the innermost policy (last argument) wraps the function first. ```go retryPolicy := resilience.NewRetryPolicy[*PaymentResult](3, 200*time.Millisecond) cb := resilience.NewCircuitBreaker[*PaymentResult](5, 30*time.Second) result, err := resilience.Execute(func() (*PaymentResult, error) { return paymentClient.Charge(ctx, amount) }, cb, retryPolicy) ``` ### Using in a Service ```go func (s *PaymentService) Charge(ctx context.Context, amount float64) (*PaymentResult, error) { return resilience.Execute(func() (*PaymentResult, error) { return s.paymentClient.Charge(ctx, amount) }, s.circuitBreaker, s.retryPolicy) } ``` ## Related Pages - [Queue](/docs/api-reference/queue) -- Retry policies for queued jobs - [Health](/docs/api-reference/health) -- Health checks reflect circuit breaker states - [Observability](/docs/api-reference/observability) -- Track resilience metrics --- ## /docs/api-reference/validators — Validators > Input validation built on go-playground/validator/v10 with custom validators registered via struct tags. # Validators The `validators` package provides input validation built on [go-playground/validator/v10](https://github.com/go-playground/validator). It uses struct tag-based validation with custom validators registered at startup, and produces translated error messages. ## Import ```go import "github.com/gofastadev/gofasta/pkg/validators" ``` ## Key Types ### AppValidator ```go type AppValidator struct { Validate *validator.Validate Trans ut.Translator } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewAppValidator` | `func NewAppValidator(db *gorm.DB) *AppValidator` | Creates a validator with common validations registered | | `ValidateStruct` | `func (v *AppValidator) ValidateStruct(input interface{}) []*types.TCommonAPIErrorDto` | Validates the input struct and returns validation errors | | `RegisterCommonValidators` | `func RegisterCommonValidators(validate *validator.Validate, db *gorm.DB)` | Registers the gofasta library's common validators on an existing validator instance | | `RegisterTranslation` | `func RegisterTranslation(v *validator.Validate, trans ut.Translator, tag string, message string)` | Registers a custom validation error message | ## Built-in Custom Validators These validators are registered automatically when using `NewAppValidator` and can be used as struct tags: | Tag | Description | |-----|-------------| | `uuid4_valid` | Must be a valid UUID v4 | | `is_record_deletable` | Record must be deletable (checks `is_deletable` column) | | `is_record_exist_by_name_for_conflict` | Name must not already exist in the specified table | | `does_record_exist_by_id_for_verification` | ID must exist in the specified table | | `is_valid_url` | Must be a valid HTTP or HTTPS URL | In addition, all standard `go-playground/validator` tags are available (`required`, `email`, `min`, `max`, `gte`, `lte`, etc.). ## Usage ### Setting Up the Validator The `AppValidator` is created once at startup and shared across your application: ```go appValidator := validators.NewAppValidator(db) ``` ### Defining Validation with Struct Tags ```go type CreateUserInput struct { Name string `json:"name" validate:"required,min=2,max=100"` Email string `json:"email" validate:"required,email"` Password string `json:"password" validate:"required,min=8"` Role string `json:"role" validate:"required,oneof=admin editor viewer"` Website *string `json:"website" validate:"omitempty,is_valid_url"` } ``` ### Validating in a Controller ```go func (c *UserController) Create(w http.ResponseWriter, r *http.Request) { var input CreateUserInput if err := httputil.Bind(r, &input); err != nil { httputil.Error(w, errors.BadRequest(err.Error())) return } if valErrs := c.validator.ValidateStruct(input); len(valErrs) > 0 { httputil.JSON(w, http.StatusBadRequest, types.TCommonResponseDto{ Status: http.StatusBadRequest, Errors: valErrs, }) return } user, err := c.service.Create(r.Context(), input) if err != nil { httputil.Error(w, err) return } httputil.Created(w, user) } ``` The error response looks like: ```json { "status": 400, "errors": [ {"fieldName": "email", "message": "Email must be a valid email address"}, {"fieldName": "password", "message": "Password must be at least 8 characters in length"} ] } ``` ### Database Uniqueness Check Use the `is_record_exist_by_name_for_conflict` tag with the table name as a parameter: ```go type CreateCategoryInput struct { Name string `json:"name" validate:"required,is_record_exist_by_name_for_conflict=categories"` } ``` ### Record Existence Verification ```go type AssignRoleInput struct { RoleID string `json:"roleId" validate:"required,uuid4_valid,does_record_exist_by_id_for_verification=roles"` } ``` ### Registering Custom Validators Extend the validator by registering additional validations on the `Validate` field: ```go appValidator := validators.NewAppValidator(db) // Register a custom validation appValidator.Validate.RegisterValidation("strong_password", func(fl validator.FieldLevel) bool { password := fl.Field().String() hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password) hasLower := regexp.MustCompile(`[a-z]`).MatchString(password) hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password) return hasUpper && hasLower && hasDigit }) // Register a translation for the custom validation validators.RegisterTranslation(appValidator.Validate, appValidator.Trans, "strong_password", "{0} must contain uppercase, lowercase, and digit characters") ``` Then use it in struct tags: ```go type ResetPasswordInput struct { Password string `json:"password" validate:"required,min=8,strong_password"` } ``` ## Related Pages - [Errors](/docs/api-reference/errors) -- Validation errors map to structured error responses - [HTTP Utilities](/docs/api-reference/http-utilities) -- Bind and validate request input - [Types](/docs/api-reference/types) -- Shared DTO types including TCommonAPIErrorDto --- ## /docs/api-reference/i18n — i18n > Internationalization with YAML locale files, message translation, and Accept-Language header detection. # i18n The `i18n` package provides internationalization support using [go-i18n/v2](https://github.com/nicksnyder/go-i18n). It loads YAML locale files, translates messages with template data, and detects the preferred language from HTTP request headers. ## Import ```go import "github.com/gofastadev/gofasta/pkg/i18n" ``` ## Key Types ### I18nService ```go type I18nService struct { bundle *i18n.Bundle defaultLang string } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewI18nService` | `func NewI18nService(localesDir string, defaultLang string) *I18nService` | Loads locale YAML files from the given directory | | `T` | `func (s *I18nService) T(lang, messageID string, data map[string]interface{}) string` | Translates a message ID using the given language | | `LangFromRequest` | `func (s *I18nService) LangFromRequest(r *http.Request) string` | Extracts the preferred language from the Accept-Language header | | `CreateDefaultLocaleFile` | `func CreateDefaultLocaleFile(localesDir string)` | Creates a default English locale file if none exists | ## Translation Files Create YAML translation files in your locales directory. Each file is named by its locale code. `locales/en.yaml`: ```yaml welcome: other: "Welcome, {{.Name}}!" not_found: other: "Resource not found" unauthorized: other: "Authentication required" forbidden: other: "You don't have permission to access this resource" validation_failed: other: "Validation failed" internal_error: other: "An internal error occurred" ``` `locales/fr.yaml`: ```yaml welcome: other: "Bienvenue, {{.Name}} !" not_found: other: "Ressource introuvable" unauthorized: other: "Authentification requise" ``` ## Usage ### Basic Translation ```go translator := i18n.NewI18nService("locales/", "en") msg := translator.T("en", "welcome", map[string]interface{}{"Name": "Jane"}) // -> "Welcome, Jane!" msg = translator.T("fr", "welcome", map[string]interface{}{"Name": "Jane"}) // -> "Bienvenue, Jane !" ``` ### Fallback to Default Language When a message is not found in the requested language, the service falls back to the default language: ```go // If "internal_error" is not in fr.yaml, falls back to en.yaml msg := translator.T("fr", "internal_error", nil) // -> "An internal error occurred" ``` ### Language Detection from Request ```go func (c *UserController) Create(w http.ResponseWriter, r *http.Request) { lang := c.translator.LangFromRequest(r) user, err := c.service.Create(r.Context(), input) if err != nil { msg := c.translator.T(lang, "internal_error", nil) httputil.Error(w, errors.BadRequest(msg)) return } msg := c.translator.T(lang, "welcome", map[string]interface{}{"Name": user.Name}) httputil.Created(w, msg, user) } ``` ### Creating Default Locale Files On first run, generate a default English locale file: ```go i18n.CreateDefaultLocaleFile("locales/") ``` ### Configuration via config.yaml ```yaml i18n: default_language: en locales_dir: locales/ ``` Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_I18N_DEFAULT_LANGUAGE`). ## Related Pages - [Middleware](/docs/api-reference/middleware) -- Locale detection middleware - [Errors](/docs/api-reference/errors) -- Localized error messages - [Mailer](/docs/api-reference/mailer) -- Localized email templates --- ## /docs/api-reference/observability — Observability > Prometheus metrics and OpenTelemetry tracing for application instrumentation. # Observability The `observability` package provides instrumentation for monitoring and tracing Gofasta applications. It integrates Prometheus for metrics collection and OpenTelemetry for distributed tracing, using standard `net/http` middleware. ## Import ```go import "github.com/gofastadev/gofasta/pkg/observability" ``` ## Metrics ### Built-in Metrics The package automatically registers the following Prometheus metrics: | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `http_requests_total` | Counter | method, path, status | Total number of HTTP requests | | `http_request_duration_seconds` | Histogram | method, path | HTTP request duration in seconds | | `http_requests_in_flight` | Gauge | -- | Number of HTTP requests currently being processed | ### Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `MetricsMiddleware` | `func MetricsMiddleware(next http.Handler) http.Handler` | Records HTTP request metrics (count, duration, in-flight) | | `MetricsHandler` | `func MetricsHandler() http.Handler` | Returns the Prometheus metrics HTTP handler | | `InitTracer` | `func InitTracer(serviceName string) func()` | Initializes the OpenTelemetry tracer provider and returns a shutdown function | | `TracingMiddleware` | `func TracingMiddleware(serviceName string) func(http.Handler) http.Handler` | Creates spans for HTTP requests and propagates trace context | ## Usage ### Setting Up Metrics ```go // Expose the Prometheus metrics endpoint mux.Handle("GET /metrics", observability.MetricsHandler()) // Wrap your handler with metrics middleware handler := observability.MetricsMiddleware(mux) ``` The `MetricsMiddleware` is a plain `func(http.Handler) http.Handler` -- it wraps the handler directly without requiring a config struct. ### Setting Up Tracing ```go shutdown := observability.InitTracer("my-service") defer shutdown() // Add tracing middleware to create spans for HTTP requests handler := observability.TracingMiddleware("my-service")(mux) ``` The tracer uses OpenTelemetry with `AlwaysSample` by default. In production, configure an exporter (OTLP, Jaeger, Zipkin) via the OpenTelemetry SDK. ### Combining Metrics and Tracing ```go shutdown := observability.InitTracer("my-service") defer shutdown() mux.Handle("GET /metrics", observability.MetricsHandler()) // Apply both middleware handler := observability.MetricsMiddleware( observability.TracingMiddleware("my-service")(mux), ) http.ListenAndServe(":8080", handler) ``` ### Configuration via config.yaml ```yaml observability: metrics_enabled: true tracing_enabled: true metrics_path: /metrics service_name: my-service ``` Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_OBSERVABILITY_METRICS_ENABLED`, `GOFASTA_OBSERVABILITY_SERVICE_NAME`). ## Related Pages - [Logger](/docs/api-reference/logger) -- Structured logging complements metrics and traces - [Health](/docs/api-reference/health) -- Health endpoints alongside metrics - [Middleware](/docs/api-reference/middleware) -- Apply observability middleware to routes - [Resilience](/docs/api-reference/resilience) -- Retries and circuit breakers expose metrics here - [Feature Flags](/docs/api-reference/feature-flags) -- Flag evaluation surfaces in trace span attributes --- ## /docs/api-reference/feature-flags — Feature Flags > Feature flag evaluation through the OpenFeature Go SDK. Swap providers (in-memory, Flagd, LaunchDarkly, go-feature-flag, custom) without touching application code. # Feature Flags The `featureflag` package is a thin wrapper around the [OpenFeature Go SDK](https://github.com/open-feature/go-sdk) — the [CNCF](https://www.cncf.io/) standard for feature-flag evaluation. Your application code calls one method (`IsEnabled`), and the actual flag storage + rule engine lives in a **provider** that you register at startup. Swap providers (in-memory for dev, LaunchDarkly for production, go-feature-flag for self-hosted, or a custom implementation) by changing a single line. ## Import ```go import "github.com/gofastadev/gofasta/pkg/featureflag" ``` ## Key Types ### FeatureFlagService ```go type FeatureFlagService struct { // unexported fields } ``` A thin wrapper around an OpenFeature `*openfeature.Client`. Every evaluation delegates to whichever provider the application has registered globally via `openfeature.SetProvider`. ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewFeatureFlagService` | `func NewFeatureFlagService(logger *slog.Logger) *FeatureFlagService` | Returns a service bound to whichever OpenFeature provider is currently registered. Falls back to the SDK's `NoopProvider` if none has been set — flag checks then resolve to the caller-supplied default. | | `NewInMemoryService` | `func NewInMemoryService(flags map[string]memprovider.InMemoryFlag, logger *slog.Logger) (*FeatureFlagService, error)` | Bootstraps the SDK with an in-memory provider populated from `flags` and returns a service wired against it. Convenience for local development, tests, and small apps that don't need a rule engine. | | `IsEnabled` | `func (s *FeatureFlagService) IsEnabled(ctx context.Context, flagKey, userID string, attributes map[string]any) bool` | Evaluates a boolean flag for the given user context. Returns `false` on evaluation error (logged at debug level) — flag checks must never take the app down. | | `Close` | `func (s *FeatureFlagService) Close()` | Calls `openfeature.Shutdown` to release any provider-held resources. Safe to call without a registered provider. | ## The Provider Model OpenFeature is the **interface contract**. Providers are **adapters** that plug any flag system behind that contract. Think of the relationship the same way you think about `database/sql` + a driver: ``` your app → openfeature.Client → Provider (adapter) → flag source ``` - **SDK** — stable public API (`openfeature.Client.BooleanValue(...)`), never changes regardless of backend. - **Provider** — the swappable plugin that actually resolves flag values. Registered once at startup via `openfeature.SetProvider(someProvider)`. ## Usage ### Zero-Config: No Provider Registered With no provider registered, the SDK's default `NoopProvider` answers every evaluation with the caller-supplied default. This is safe — your application runs without a feature-flag backend; every flag simply takes its default branch. ```go svc := featureflag.NewFeatureFlagService(logger) if svc.IsEnabled(ctx, "dark-mode", userID, nil) { // never true — NoopProvider always returns the default (false) } ``` Useful for first-run scaffolds, test fixtures, or services where feature flagging is not yet wired up. ### In-Memory Provider (recommended default) For local development, CI, and small applications, the in-memory provider is the lowest-friction choice — flags are defined in Go code, evaluated instantly, zero network traffic: ```go import "github.com/open-feature/go-sdk/openfeature/memprovider" flags := map[string]memprovider.InMemoryFlag{ "dark-mode": { Key: "dark-mode", State: memprovider.Enabled, DefaultVariant: "on", Variants: map[string]any{"on": true, "off": false}, }, "new-checkout": { Key: "new-checkout", State: memprovider.Enabled, DefaultVariant: "off", Variants: map[string]any{"on": true, "off": false}, }, } svc, err := featureflag.NewInMemoryService(flags, logger) if err != nil { log.Fatalf("failed to init feature flags: %v", err) } defer svc.Close() ``` ### Swapping to a Production Provider For file-based rules + AB testing + usage tracking, register the [go-feature-flag provider](https://gofeatureflag.org/) before constructing the service: ```go import ( "github.com/open-feature/go-sdk/openfeature" gff "github.com/thomaspoignant/go-feature-flag/providers/go-feature-flag-in-process-provider" "github.com/thomaspoignant/go-feature-flag/retriever/fileretriever" ) provider, err := gff.NewProvider(gff.ProviderOptions{ Retrievers: []retriever.Retriever{ &fileretriever.Retriever{Path: "configs/features.yaml"}, }, PollingInterval: 60 * time.Second, }) if err != nil { log.Fatalf("init go-feature-flag provider: %v", err) } if err := openfeature.SetProviderAndWait(provider); err != nil { log.Fatalf("register provider: %v", err) } svc := featureflag.NewFeatureFlagService(logger) ``` For a commercial SaaS like [LaunchDarkly](https://launchdarkly.com), swap the provider constructor — the rest of the code stays identical: ```go import ldprovider "github.com/launchdarkly/openfeature-go-server-sdk-provider" ldClient, _ := ldclient.MakeClient("sdk-key", 5*time.Second) openfeature.SetProvider(ldprovider.NewProvider(ldClient)) svc := featureflag.NewFeatureFlagService(logger) ``` For [Flagd](https://flagd.dev) (CNCF, gRPC-based): ```go import "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg" openfeature.SetProvider(flagd.NewProvider()) svc := featureflag.NewFeatureFlagService(logger) ``` **In every case, application code is identical.** Only the provider registration line changes. ### Evaluating Flags ```go // Simple check — no user context if svc.IsEnabled(ctx, "dark-mode", userID, nil) { // serve dark mode UI } // With evaluation-context attributes for targeting rules attrs := map[string]any{ "plan": "premium", "country": "US", } if svc.IsEnabled(ctx, "new-dashboard", userID, attrs) { // show new dashboard } ``` The `userID` becomes the OpenFeature **targeting key**. Providers that support segment/percentage rollout use it (together with `attributes`) to decide which variant to return. ### Using in Controllers ```go func (c *DashboardController) Index(w http.ResponseWriter, r *http.Request) error { claims := auth.ClaimsFromContext(r.Context()) if c.ffService.IsEnabled(r.Context(), "new-dashboard", claims.UserID, nil) { return httputil.OK(w, newDashboardData) } return httputil.OK(w, legacyDashboardData) } ``` ### Configuration via config.yaml ```yaml feature_flag: enabled: true config_path: configs/features.yaml ``` `enabled` is a master switch your application reads before constructing the service. `config_path` is provider-specific — if the provider you register is file-driven (like go-feature-flag), point it at this path; other providers can ignore the field. Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_FEATURE_FLAG_ENABLED`, `GOFASTA_FEATURE_FLAG_CONFIG_PATH`). ### Writing a Custom Provider Any type that implements [`openfeature.FeatureProvider`](https://pkg.go.dev/github.com/open-feature/go-sdk/openfeature#FeatureProvider) is a valid provider. The interface is small — a few resolution methods plus metadata — so wiring flags sourced from a Postgres table, a Consul KV store, or an internal config service is ~20 lines: ```go type myProvider struct{ /* ... */ } func (p *myProvider) Metadata() openfeature.Metadata { /* ... */ } func (p *myProvider) BooleanEvaluation(ctx context.Context, flag string, defaultValue bool, evalCtx openfeature.FlattenedContext) openfeature.BoolResolutionDetail { // your logic here } // ... plus String, Float, Int, Object evaluation methods openfeature.SetProvider(&myProvider{}) ``` ## Why OpenFeature Gofasta wraps OpenFeature (not any one engine) to uphold its opt-out-default philosophy: the default provider is swappable without touching the application's flag-checking code. The same interface binds in-memory dev flags, self-hosted rule engines, commercial SaaS, and your own custom backends — you pick whichever fits this deployment without rewriting controllers. ## Related Pages - [Config](/docs/api-reference/config) -- Feature flag configuration - [Middleware](/docs/api-reference/middleware) -- Inject flag service into request context - [Observability](/docs/api-reference/observability) -- Track flag evaluation metrics --- ## /docs/api-reference/sessions — Sessions > Server-side HTTP session management with cookie and filesystem stores, backed by gorilla/sessions. # Sessions The `session` package wraps [gorilla/sessions](https://github.com/gorilla/sessions) to provide server-side HTTP session management. It supports two backing stores out of the box — **cookie-based** (all data signed into the browser cookie) and **filesystem-based** (data on disk, only the session ID in the cookie) — and exposes simple get/set/destroy helpers on top of the gorilla `*sessions.Session` object. ## Import ```go import "github.com/gofastadev/gofasta/pkg/session" ``` ## Key Types ### Store `Store` is the package's main type — a thin wrapper around `gorilla/sessions.Store` that remembers the session name and exposes convenience methods for reading and writing values. ```go type Store struct { /* unexported fields */ } ``` ### SessionConfig (from pkg/config) The session configuration lives on `config.AppConfig` and is loaded from `config.yaml` or the `GOFASTA_SESSION_*` environment variables. ```go type SessionConfig struct { Driver string `koanf:"driver"` // "cookie" or "filesystem" Secret string `koanf:"secret"` // 32 or 64 bytes for HMAC signing SessionName string `koanf:"session_name"` // cookie name FilesystemPath string `koanf:"filesystem_path"` // required when driver=filesystem } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewCookieStore` | `func NewCookieStore(secret, sessionName string) *Store` | Creates a cookie-based session store. All session data is serialized into the browser cookie, signed with `secret` via HMAC. | | `NewFilesystemStore` | `func NewFilesystemStore(path, secret, sessionName string) *Store` | Creates a filesystem-based session store. Only the session ID is stored in the cookie; the actual values live on disk under `path`. | ## Store Methods | Method | Signature | Description | |--------|-----------|-------------| | `Get` | `func (s *Store) Get(r *http.Request) (*sessions.Session, error)` | Returns the gorilla `*sessions.Session` for the current request. | | `Save` | `func (s *Store) Save(r *http.Request, w http.ResponseWriter, session *sessions.Session) error` | Persists the session to the response (writes the Set-Cookie header and/or flushes the filesystem file). | | `SetValue` | `func (s *Store) SetValue(r *http.Request, w http.ResponseWriter, key string, value interface{}) error` | Convenience helper: fetches the session, writes one key, saves. | | `GetValue` | `func (s *Store) GetValue(r *http.Request, key string) (interface{}, error)` | Convenience helper: fetches the session and returns the value under `key`. | | `Destroy` | `func (s *Store) Destroy(r *http.Request, w http.ResponseWriter) error` | Marks the session for deletion by setting `MaxAge = -1` and saving. | ## Usage ### Creating a Store Choose one of the two constructors. Both return a `*Store` you can use from any handler. ```go import "github.com/gofastadev/gofasta/pkg/session" // Cookie store — simplest, all data lives in the signed cookie. store := session.NewCookieStore( "change-me-in-production-32bytes!", "app_session", ) // Filesystem store — cookie carries only the ID, data lives on disk. store := session.NewFilesystemStore( "./sessions", "change-me-in-production-32bytes!", "app_session", ) ``` ### Writing Values on Login ```go func (c *AuthController) Login(w http.ResponseWriter, r *http.Request) error { // ...authenticate the user first... if err := c.store.SetValue(r, w, "user_id", user.ID.String()); err != nil { return apperrors.NewInternal("failed to save session", err) } if err := c.store.SetValue(r, w, "role", user.Role); err != nil { return apperrors.NewInternal("failed to save session", err) } return httputil.OK(w, map[string]string{"status": "logged in"}) } ``` ### Reading Values on a Protected Route ```go func (c *AuthController) Profile(w http.ResponseWriter, r *http.Request) error { userID, err := c.store.GetValue(r, "user_id") if err != nil || userID == nil { return apperrors.NewUnauthorized("not logged in", nil) } user, err := c.service.FindByID(r.Context(), userID.(string)) if err != nil { return err } return httputil.OK(w, user) } ``` ### Writing Multiple Values in One Save For efficiency, fetch the underlying `*sessions.Session` once, mutate its `Values` map, then save it manually. ```go sess, err := c.store.Get(r) if err != nil { return err } sess.Values["user_id"] = user.ID.String() sess.Values["email"] = user.Email sess.Values["role"] = user.Role if err := c.store.Save(r, w, sess); err != nil { return err } ``` ### Logout and Session Destruction ```go func (c *AuthController) Logout(w http.ResponseWriter, r *http.Request) error { if err := c.store.Destroy(r, w); err != nil { return apperrors.NewInternal("failed to destroy session", err) } return httputil.OK(w, map[string]string{"status": "logged out"}) } ``` ### Building the Store from Config Load `SessionConfig` from `config.AppConfig` and branch on the driver: ```go func buildStore(cfg config.SessionConfig) *session.Store { switch cfg.Driver { case "filesystem": return session.NewFilesystemStore(cfg.FilesystemPath, cfg.Secret, cfg.SessionName) default: return session.NewCookieStore(cfg.Secret, cfg.SessionName) } } ``` ## Configuration Add a `session:` block to your `config.yaml`: ```yaml session: driver: cookie # "cookie" or "filesystem" secret: "change-me-in-production-32bytes!" session_name: app_session filesystem_path: "./sessions" # only used when driver=filesystem ``` Any field can be overridden via environment variables with the `GOFASTA_` prefix: ```bash export GOFASTA_SESSION_DRIVER=filesystem export GOFASTA_SESSION_SECRET="my-secure-secret-32bytes!" export GOFASTA_SESSION_FILESYSTEM_PATH="/var/lib/myapp/sessions" ``` The `pkg/config` package's `LoadConfig()` populates these fields automatically and provides sane defaults (`driver: cookie`, `session_name: app_session`, `filesystem_path: ./sessions`) when they are missing. ## Sessions vs JWT The `session` and `auth` packages cover two different authentication styles: | Concern | Sessions (`session`) | JWT (`auth`) | |---|---|---| | **State** | Server-side (cookie or filesystem) | Stateless — the token is self-contained | | **Invalidation** | Destroy the server-side entry | Rotate the signing key or maintain a denylist | | **Cross-origin / mobile** | Harder — cookies are domain-scoped | Easy — clients send `Authorization: Bearer …` | | **Best for** | Server-rendered web apps on one domain | REST / GraphQL APIs consumed by SPAs or mobile | A gofasta project can use either, or both, depending on the surface area of the API. ## Related Pages - [Auth](/docs/api-reference/auth) — JWT-based authentication for stateless APIs - [Config](/docs/api-reference/config) — where `SessionConfig` lives - [Middleware](/docs/api-reference/middleware) — wrap your router with logging, CORS, etc. --- ## /docs/api-reference/encryption — Encryption > AES-256-GCM encryption and decryption for securing sensitive data. # Encryption The `encryption` package provides AES-256-GCM encryption and decryption for securing sensitive data. Ciphertext is base64-encoded for safe storage and transport. ## Import ```go import "github.com/gofastadev/gofasta/pkg/encryption" ``` ## Key Types ### Encrypter ```go type Encrypter struct { gcm cipher.AEAD } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewEncrypter` | `func NewEncrypter(key string) (*Encrypter, error)` | Creates an AES-256-GCM encrypter with a 32-byte key | | `Encrypt` | `func (e *Encrypter) Encrypt(plaintext string) (string, error)` | Encrypts plaintext and returns a base64-encoded ciphertext | | `Decrypt` | `func (e *Encrypter) Decrypt(encoded string) (string, error)` | Decrypts a base64-encoded ciphertext back to plaintext | ## Usage ### AES-256-GCM Encryption and Decryption ```go enc, err := encryption.NewEncrypter("a-32-byte-secret-key-for-aes256") // must be exactly 32 bytes if err != nil { log.Fatalf("failed to create encrypter: %v", err) } // Encrypt ciphertext, err := enc.Encrypt("sensitive data") if err != nil { log.Fatalf("encryption failed: %v", err) } // Decrypt plaintext, err := enc.Decrypt(ciphertext) if err != nil { log.Fatalf("decryption failed: %v", err) } fmt.Println(plaintext) // "sensitive data" ``` ### Encrypting Structured Data ```go data, _ := json.Marshal(creditCard) encrypted, err := enc.Encrypt(string(data)) if err != nil { return err } // Store encrypted string in the database ``` ### Configuration via config.yaml ```yaml encryption: key: "your-32-byte-secret-key-here!!" ``` Environment variables use the `GOFASTA_` prefix (e.g., `GOFASTA_ENCRYPTION_KEY`). ## Note on Password Hashing and HMAC Password hashing (bcrypt) and HMAC signing are handled in the `auth` and `session` packages respectively, not in this package. The `encryption` package focuses solely on AES-256-GCM symmetric encryption. ## Related Pages - [Auth](/docs/api-reference/auth) -- Password hashing and JWT token signing - [Storage](/docs/api-reference/storage) -- Encrypt files before storage - [Config](/docs/api-reference/config) -- Encryption configuration loading --- ## /docs/api-reference/seeds — Seeds > Database seeding with GORM using a simple registry of seed functions with ordered execution. # Seeds The `seeds` package provides a database seeding system for populating initial or test data using GORM. It uses a simple registry pattern where seed functions are registered and then executed in order. ## Import ```go import "github.com/gofastadev/gofasta/pkg/seeds" ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `Register` | `func Register(fn func(*gorm.DB) error)` | Registers a seed function to the global registry | | `RunAll` | `func RunAll(db *gorm.DB) error` | Executes all registered seed functions in order | ## Usage ### Registering Seeds Seed functions take a `*gorm.DB` and return an error. Register them using `seeds.Register`: ```go func init() { seeds.Register(func(db *gorm.DB) error { roles := []Role{ {Name: "admin", Description: "Administrator"}, {Name: "editor", Description: "Content Editor"}, {Name: "viewer", Description: "Read-only Viewer"}, } for _, role := range roles { if err := db.FirstOrCreate(&role, Role{Name: role.Name}).Error; err != nil { return err } } return nil }) seeds.Register(func(db *gorm.DB) error { admin := User{ Name: "Admin", Email: "admin@example.com", Password: hashedPassword, Role: "admin", } return db.FirstOrCreate(&admin, User{Email: admin.Email}).Error }) } ``` ### Running Seeds ```go if err := seeds.RunAll(db); err != nil { log.Fatalf("seeding failed: %v", err) } ``` ### Seed Ordering Seeds run in the order they are registered. To control execution order, register them in the desired sequence -- typically using `init()` functions with careful package import ordering, or by calling `Register` explicitly in your setup code: ```go func registerSeeds() { seeds.Register(seedRoles) // runs first seeds.Register(seedAdminUser) // runs second seeds.Register(seedCategories) // runs third } ``` ### Idempotent Seeds Use GORM's `FirstOrCreate` to make seeds idempotent: ```go seeds.Register(func(db *gorm.DB) error { categories := []Category{ {Name: "Technology"}, {Name: "Science"}, {Name: "Art"}, } for _, cat := range categories { if err := db.FirstOrCreate(&cat, Category{Name: cat.Name}).Error; err != nil { return err } } return nil }) ``` ### Integration with Application Startup ```go func main() { cfg, _ := config.LoadConfig() db, _ := setupDatabase(cfg.Database) registerSeeds() if err := seeds.RunAll(db); err != nil { log.Fatalf("seeding failed: %v", err) } // Start server... } ``` ## Related Pages - [Config](/docs/api-reference/config) -- Database setup used by the seeder - [Models](/docs/api-reference/models) -- Models being seeded - [Encryption](/docs/api-reference/encryption) -- Encrypt sensitive seed data --- ## /docs/api-reference/types — Types > Shared DTO types, pagination inputs, sorting, and common API response types used across Gofasta packages. # Types The `types` package provides shared DTO (Data Transfer Object) types used across Gofasta packages for pagination, sorting, API responses, and error reporting. ## Import ```go import "github.com/gofastadev/gofasta/pkg/types" ``` ## Key Types ### TPaginationInputDto Input type for pagination parameters. ```go type TPaginationInputDto struct { Limit *int `json:"limit,omitempty" schema:"limit" validate:"gte=1"` Page *int `json:"page,omitempty" schema:"page" validate:"gte=1"` } ``` ### TSortingInputDto Input type for sorting parameters. ```go type TSortingInputDto struct { SortByField string `json:"sortByField" schema:"sortByField" validate:"required"` SortOrientation *SortOrientation `json:"sortOrientation,omitempty" schema:"sortOrientation" validate:"omitempty"` } ``` ### SortOrientation ```go type SortOrientation string const ( SortOrientationAsc SortOrientation = "ASC" SortOrientationDesc SortOrientation = "DESC" ) ``` ### TCommonAPIErrorDto Standard error structure returned in API responses. ```go type TCommonAPIErrorDto struct { FieldName *string `json:"fieldName,omitempty"` Message string `json:"message"` } ``` ### TCommonResponseDto Standard API response wrapper. ```go type TCommonResponseDto struct { Status int `json:"status"` Message *string `json:"message,omitempty"` Errors []*TCommonAPIErrorDto `json:"errors,omitempty"` } ``` ### TPaginationObjectDto Pagination metadata returned in list responses. ```go type TPaginationObjectDto struct { TotalRecords *int `json:"totalRecords,omitempty"` RecordsPerPage *int `json:"recordsPerPage,omitempty"` TotalPages *int `json:"totalPages,omitempty"` CurrentPage *int `json:"currentPage,omitempty"` } ``` ## Usage ### Pagination in Controllers ```go func (c *UserController) List(w http.ResponseWriter, r *http.Request) { var pagination types.TPaginationInputDto if limitStr := r.URL.Query().Get("limit"); limitStr != "" { limit, _ := strconv.Atoi(limitStr) pagination.Limit = &limit } if pageStr := r.URL.Query().Get("page"); pageStr != "" { page, _ := strconv.Atoi(pageStr) pagination.Page = &page } users, total, err := c.service.List(r.Context(), &pagination) if err != nil { httputil.Error(w, err) return } // Build pagination metadata paginationObj := types.TPaginationObjectDto{ TotalRecords: &total, CurrentPage: pagination.Page, } httputil.JSON(w, http.StatusOK, map[string]interface{}{ "data": users, "pagination": paginationObj, }) } ``` ### Error Responses ```go func handleValidationErrors(w http.ResponseWriter, errs []*types.TCommonAPIErrorDto) { msg := "Validation failed" httputil.JSON(w, http.StatusBadRequest, types.TCommonResponseDto{ Status: http.StatusBadRequest, Message: &msg, Errors: errs, }) } ``` ### Sorting ```go sorting := types.TSortingInputDto{ SortByField: "createdAt", SortOrientation: &types.SortOrientationDesc, } ``` `SortOrientation` implements JSON and GQL marshaling/unmarshaling, making it compatible with both REST and GraphQL APIs. ## Related Pages - [Validators](/docs/api-reference/validators) -- ValidateStruct returns []*TCommonAPIErrorDto - [HTTP Utilities](/docs/api-reference/http-utilities) -- Pagination helpers complement these types - [Utils](/docs/api-reference/utils) -- Utility functions for working with these types --- ## /docs/api-reference/utils — Utils > Pagination helpers, string conversion utilities, query building, and common helper functions. # Utils The `utils` package provides a collection of general-purpose utility functions including pagination math, string case conversion, struct-to-map conversion, search query building, and random password generation. ## Import ```go import "github.com/gofastadev/gofasta/pkg/utils" ``` ## Pagination ### PreparePaginating A helper struct that computes offset, limit, page, and sort order from pagination and sorting DTOs. ```go type PreparePaginating struct { PageFilters *types.TPaginationInputDto Sorting *types.TSortingInputDto } ``` | Method | Signature | Description | |--------|-----------|-------------| | `GetOffset` | `func (p *PreparePaginating) GetOffset() int` | Calculates the database offset from page and limit | | `GetLimit` | `func (p *PreparePaginating) GetLimit() int` | Returns the limit (default: 10) | | `GetPage` | `func (p *PreparePaginating) GetPage() int` | Returns the current page (default: 1) | | `GetSort` | `func (p *PreparePaginating) GetSort() string` | Returns the SQL ORDER BY clause (e.g., `"created_at DESC"`) | ## String Helpers | Function | Signature | Description | |----------|-----------|-------------| | `CamelToSnake` | `func CamelToSnake(s string) string` | Converts CamelCase to snake_case (e.g., `"UserProfile"` to `"user_profile"`) | | `ConvertUpperCamelToLowerCamel` | `func ConvertUpperCamelToLowerCamel(input string) string` | Converts PascalCase to camelCase (e.g., `"UserName"` to `"userName"`) | | `ParseIdStringIsValidUUID` | `func ParseIdStringIsValidUUID(u string) (uuid.UUID, error)` | Parses and validates a UUID string | ## Other Utilities | Function | Signature | Description | |----------|-----------|-------------| | `GeneratePassword` | `func GeneratePassword(length int) (string, error)` | Generates a random password with mixed characters | | `ConvertStructToMap` | `func ConvertStructToMap(obj interface{}) map[string]interface{}` | Converts a struct to a map using snake_case keys (non-nil pointer fields only) | | `BuildQueryForAnyModel` | `func BuildQueryForAnyModel(db *gorm.DB, filters map[string]interface{}) (*gorm.DB, error)` | Builds a GORM query with ILIKE filters for string pointer fields | ## Usage ### Pagination Calculations ```go paginator := utils.PreparePaginating{ PageFilters: &types.TPaginationInputDto{ Page: utils.Ptr(3), Limit: utils.Ptr(20), }, Sorting: &types.TSortingInputDto{ SortByField: "createdAt", SortOrientation: &types.SortOrientationDesc, }, } offset := paginator.GetOffset() // 40 limit := paginator.GetLimit() // 20 page := paginator.GetPage() // 3 sort := paginator.GetSort() // "created_at DESC" ``` ### String Case Conversion ```go snake := utils.CamelToSnake("UserProfileSettings") // -> "user_profile_settings" camel := utils.ConvertUpperCamelToLowerCamel("UserName") // -> "userName" // Special case for "ID" camel = utils.ConvertUpperCamelToLowerCamel("ID") // -> "id" ``` ### UUID Validation ```go id, err := utils.ParseIdStringIsValidUUID("550e8400-e29b-41d4-a716-446655440000") if err != nil { // invalid UUID } ``` ### Building Search Queries ```go name := "john" filters := map[string]interface{}{ "name": &name, "email": (*string)(nil), // nil pointers are skipped } query, err := utils.BuildQueryForAnyModel(db, filters) // Produces: WHERE name ILIKE '%john%' ``` ### Random Password Generation ```go password, err := utils.GeneratePassword(16) if err != nil { return err } // e.g., "aB3$kL9!mN2@pQ7&" ``` ### Struct to Map Conversion ```go user := UserUpdate{ Name: utils.Ptr("Jane"), Email: utils.Ptr("jane@example.com"), Phone: nil, // nil fields are excluded } m := utils.ConvertStructToMap(user) // -> {"name": "Jane", "email": "jane@example.com"} ``` ## Related Pages - [Types](/docs/api-reference/types) -- Type definitions that utilities operate on - [HTTP Utilities](/docs/api-reference/http-utilities) -- Higher-level pagination helpers - [Validators](/docs/api-reference/validators) -- String validation rules --- ## /docs/api-reference/health — Health > Liveness and readiness probe endpoints for load balancers, uptime monitors, and orchestrator health checks, with dependency status reporting. # Health The `health` package provides HTTP endpoints for monitoring application status. It exposes standard liveness and readiness probe URLs with dependency checks against the database and cache. The endpoints work with any orchestrator, load balancer, or uptime monitor that polls HTTP — load balancers expecting a 200/5xx signal, uptime services like Pingdom or UptimeRobot, and the probe-style interfaces used by Kubernetes, Nomad, or container platforms that honor the same convention. ## Import ```go import "github.com/gofastadev/gofasta/pkg/health" ``` ## Key Types ### Controller ```go type Controller struct { DB *gorm.DB Cache cache.CacheService } ``` ## Key Functions | Function | Signature | Description | |----------|-----------|-------------| | `NewController` | `func NewController(db *gorm.DB, cacheService cache.CacheService) *Controller` | Creates a new health controller; cache can be nil | | `Check` | `func (h *Controller) Check(w http.ResponseWriter, r *http.Request) error` | Basic liveness check (`GET /health`) | | `Live` | `func (h *Controller) Live(w http.ResponseWriter, r *http.Request) error` | Liveness probe (`GET /health/live`) | | `Ready` | `func (h *Controller) Ready(w http.ResponseWriter, r *http.Request) error` | Readiness probe with dependency checks (`GET /health/ready`) | ## Endpoints | Path | Method | Handler | Description | |------|--------|---------|-------------| | `/health` | GET | `Check` | Basic liveness -- returns `{"status": "up"}` | | `/health/live` | GET | `Live` | Liveness probe -- process is alive | | `/health/ready` | GET | `Ready` | Readiness probe -- checks database and cache dependencies | ## Usage ### Setting Up Health Checks ```go healthController := health.NewController(db, cacheService) mux.HandleFunc("GET /health", healthController.Check) mux.HandleFunc("GET /health/live", healthController.Live) mux.HandleFunc("GET /health/ready", healthController.Ready) ``` If you do not use a cache, pass `nil`: ```go healthController := health.NewController(db, nil) ``` ### Response Format `GET /health` and `GET /health/live` return: ```json { "status": "up" } ``` `GET /health/ready` returns detailed dependency status: ```json { "status": "up", "uptime": "72h30m15s", "checks": [ { "name": "database", "status": "up", "duration": "2.1ms" }, { "name": "cache", "status": "up", "duration": "1.3ms" } ] } ``` When a dependency is down, the endpoint returns HTTP 503: ```json { "status": "down", "uptime": "72h30m15s", "checks": [ { "name": "database", "status": "up", "duration": "2.1ms" }, { "name": "cache", "status": "down", "error": "connection refused", "duration": "5000ms" } ] } ``` ### Built-in Checks The readiness endpoint automatically checks: - **Database** -- Pings the database using the underlying `sql.DB` connection - **Cache (Redis)** -- Pings the cache service (if configured) ### Probe Endpoint Reference Use these endpoints with your load balancer, uptime monitor, or orchestrator. The scheme below is the one most platforms use to distinguish "is the process running" (liveness) from "can this instance safely take traffic" (readiness): - `GET /health/live` — returns 200 when the process is running. Used as a restart signal; should almost always be 200 unless the process itself is dead. - `GET /health/ready` — returns 200 when the process can serve traffic (database and cache reachable). Returns 503 when dependencies are unavailable; a load balancer pulling this instance out of rotation temporarily will restore it when the next poll succeeds. Health poll guidance: - **Liveness** — poll every 10–30 s with a short timeout (≤5 s). A failing liveness probe should typically trigger a restart. - **Readiness** — poll every 5–10 s. A failing readiness probe should remove the instance from traffic but **not** restart it. ## Related Pages - [Config](/docs/api-reference/config) -- Database and cache configuration for checks - [Observability](/docs/api-reference/observability) -- Metrics complement health checks - [Resilience](/docs/api-reference/resilience) -- Circuit breaker states reflected in health --- ## /docs/api-reference/test-utilities — Test Utilities > Test helpers for database setup with testcontainers, migrations, and PostgreSQL test databases. # Test Utilities The `testutil` package provides testing utilities for Gofasta applications, including test database setup using [testcontainers-go](https://github.com/testcontainers/testcontainers-go) with PostgreSQL, automatic migration, and cleanup. ## Import ```go import "github.com/gofastadev/gofasta/pkg/testutil/testdb" ``` ## Key Functions ### Database Helpers | Function | Signature | Description | |----------|-----------|-------------| | `SetupTestDB` | `func SetupTestDB(t *testing.T) *gorm.DB` | Spins up a PostgreSQL container, runs migrations, and returns a connected `*gorm.DB` | | `RunMigrations` | `func RunMigrations(db *gorm.DB) error` | Applies base SQL migrations to the test database | ## Usage ### Integration Test with Test Database `SetupTestDB` starts a PostgreSQL container using testcontainers, runs base migrations, and returns a `*gorm.DB`. The container is automatically cleaned up when the test finishes via `t.Cleanup`. ```go func TestUserRepository_Create(t *testing.T) { db := testdb.SetupTestDB(t) // Auto-migrate your models db.AutoMigrate(&User{}) repo := NewUserRepository(db) user := &User{ Name: "Jane Doe", Email: "jane@example.com", Role: "editor", } err := repo.Create(context.Background(), user) assert.NoError(t, err) assert.NotEmpty(t, user.ID) // Verify the record exists var found User db.First(&found, "email = ?", "jane@example.com") assert.Equal(t, "editor", found.Role) } ``` ### Base Migrations `SetupTestDB` automatically runs the following base migrations: - Creates the `citext` extension - Creates an `update_updated_at_column()` trigger function - Creates a `prevent_delete_non_deletable()` trigger function - Creates an `increment_record_version()` trigger function These match the trigger functions that gofasta-generated models expect (updated_at maintenance, soft-delete guards, record versioning). You then add your own model migrations with `db.AutoMigrate(...)`. ### HTTP Integration Test ```go func TestUserController_Create(t *testing.T) { db := testdb.SetupTestDB(t) db.AutoMigrate(&User{}) handler := setupRouter(db) // your router setup function req := httptest.NewRequest(http.MethodPost, "/api/users", strings.NewReader(`{ "name": "Jane Doe", "email": "jane@example.com", "role": "editor" }`)) req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+testToken) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) assert.Equal(t, http.StatusCreated, rec.Code) var found User db.First(&found, "email = ?", "jane@example.com") assert.Equal(t, "Jane Doe", found.Name) } ``` ### Table-Driven Tests ```go func TestUserController_GetUser(t *testing.T) { db := testdb.SetupTestDB(t) db.AutoMigrate(&User{}) handler := setupRouter(db) tests := []struct { name string userID string wantStatus int }{ {"existing user", "valid-uuid", http.StatusOK}, {"non-existing user", "missing-uuid", http.StatusNotFound}, {"invalid uuid", "not-a-uuid", http.StatusBadRequest}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/users/"+tt.userID, nil) req.Header.Set("Authorization", "Bearer "+testToken) rec := httptest.NewRecorder() handler.ServeHTTP(rec, req) assert.Equal(t, tt.wantStatus, rec.Code) }) } } ``` ## Related Pages - [Config](/docs/api-reference/config) -- Test database configuration - [Auth](/docs/api-reference/auth) -- JWT token generation for test requests - [Models](/docs/api-reference/models) -- Models used in test database setup --- ## /docs/white-paper — White Paper > Gofasta technical white paper — design principles, architecture, code generation strategy, dependency injection with Google Wire, database layer with GORM, HTTP routing with chi, authentication, background processing, real-time communication, email and notifications, file storage, internationalization, feature flags, observability, resilience patterns, security defaults, testing, deployment, and adoption path for Go web services. # Gofasta: Code Generation and Composable Packages for Go Backend Services **Technical Whitepaper** *Version 4.0.0 — April 2026* *Published by the Gofasta Authors — MIT License* --- ## Abstract Go is an excellent language for building backend services, but starting a new production project still involves significant repetitive setup: wiring routers, configuring databases, setting up authentication, writing CRUD boilerplate, organizing project structure, and preparing deployment manifests. Each team reinvents these patterns independently, leading to inconsistency and wasted effort. Gofasta is an open-source toolkit that eliminates this setup overhead. It consists of two components: a **CLI tool** (`gofastadev/cli`) that scaffolds projects and generates idiomatic Go code, and a **library** (`gofastadev/gofasta`) of production-ready packages covering common backend concerns. Generated code is plain Go — no custom syntax, no transpilation, no runtime magic. Developers own every line of output and can modify or replace any component independently. Gofasta is also **agent-native by default**. Every CLI command emits structured JSON output, every error carries a stable machine-readable code and a remediation hint, every resource is inspectable as structured data, and every scaffolded project ships with the configuration files AI coding agents read at startup. The design goal is that an AI agent working on a Gofasta project should be measurably faster and more accurate than one working on a hand-rolled Go project — not as an afterthought but as a first-class constraint on every feature. This paper describes the technical architecture, design principles, agent-ergonomics model, and feature set of Gofasta, and explains how it fits into the Go ecosystem. --- ## Table of Contents 1. [Introduction](#1-introduction) — setup tax, what Gofasta is and is not, curated defaults 2. [Design Principles](#2-design-principles) — including §2.7 Agent-Native by Default 3. [Architecture](#3-architecture) 4. [The CLI Tool](#4-the-cli-tool) 5. [The Gofasta Library](#5-the-gofasta-library) 6. [Code Generation Strategy](#6-code-generation-strategy) 7. [Dependency Injection](#7-dependency-injection) 8. [Database Layer](#8-database-layer) 9. [HTTP and API Layer](#9-http-and-api-layer) 10. [Authentication and Authorization](#10-authentication-and-authorization) 11. [Security Defaults](#11-security-defaults) 12. [Background Processing](#12-background-processing) 13. [Real-Time Communication](#13-real-time-communication) 14. [Email and Notifications](#14-email-and-notifications) 15. [File Storage](#15-file-storage) 16. [Internationalization](#16-internationalization) 17. [Feature Flags](#17-feature-flags) 18. [Observability](#18-observability) 19. [Resilience](#19-resilience) 20. [Testing](#20-testing) 21. [Deployment](#21-deployment) 22. [Adoption and Migration Path](#22-adoption-and-migration-path) 23. [Comparison with Existing Tools](#23-comparison-with-existing-tools) 24. [Roadmap](#24-roadmap) 25. [Contributing](#25-contributing) --- ## 1. Introduction ### 1.1 The Setup Tax Go's standard library and ecosystem provide excellent building blocks for backend services. The language is fast, memory-efficient, compiles to a single binary, and handles concurrency natively. These properties make it a strong technical choice for backend services at any scale — from a solo developer's first SaaS to a large organization's internal platform. However, assembling these building blocks into a production-ready application imposes a cost that is largely independent of the application's business logic. Before writing the first line of domain code, a developer must: - **Choose and integrate a set of libraries.** Go does not ship a canonical answer for HTTP routing, database access, authentication, caching, email, background jobs, or configuration management. Each concern requires evaluating competing libraries, reading their documentation, and writing integration code. For a typical backend, this involves 15–25 separate library selections. - **Design a project layout.** Go does not prescribe a project structure. Teams must decide where to put models, handlers, services, repositories, DTOs, and configuration — and these decisions vary across projects within the same organization. - **Write structural wiring.** Connecting routers, middleware, database connections, authentication, logging, and health checks requires hundreds of lines of setup code that looks nearly identical across projects. This code is not business logic — it is infrastructure glue. - **Produce CRUD boilerplate.** For each resource, developers write a model, migration, repository, service, DTO, controller, and route registration file. The pattern is the same every time; only the field names change. - **Prepare deployment artifacts.** Dockerfiles, systemd unit files, CI/CD workflows, and reverse proxy configurations are needed before the first deploy, and they follow predictable patterns. This overhead is measurable. A production-ready Go backend — with structured logging, JWT authentication, role-based access control, database migrations, input validation, health checks, Swagger documentation, Docker configuration, and CI/CD workflows — typically requires 2,000–4,000 lines of non-business-logic code and 40–80 files before the first domain endpoint can be implemented. For an experienced Go developer, this represents days of work. For a developer new to Go, it can represent weeks. This is not a deficiency of the language. Go's explicitness — the property that every behavior is visible in source code, that there is no hidden control flow, no auto-discovery, no runtime magic — is precisely why engineers choose it. The setup cost is the price of a codebase where every line is readable and every dependency is traceable. That price is worth paying. The question is whether each team needs to pay it independently, or whether the structurally identical portions can be generated once and then owned by the developer. ### 1.2 What Gofasta Is Gofasta is two things, plus an audience commitment: 1. **A CLI tool** (`github.com/gofastadev/cli`) installed globally via `go install github.com/gofastadev/cli/cmd/gofasta@latest`. It creates new projects, generates resource code (models, services, controllers, migrations), runs migrations, manages seeds, regenerates dependency injection wiring, and provides introspection + verification commands that serve humans and AI coding agents equally well. It does not import the gofasta library; it only manipulates files on disk. 2. **A Go library** (`github.com/gofastadev/gofasta`) containing multiple packages under `pkg/`. These packages provide configuration loading, structured logging, error handling, HTTP utilities, middleware, authentication, caching, storage, email, notifications, WebSockets, scheduling, queues, resilience patterns, validation, internationalization, observability, feature flags, encryption, health checks, and test utilities. Each package is independently importable and has no side effects. 3. **A toolkit designed to be driven by AI coding agents as first-class users.** Every scaffolded project ships with an `AGENTS.md` briefing, a JSON-Schema-aware config helper, a structured-error protocol, and an opt-in installer (`gofasta ai `) that wires per-agent configuration for Claude Code, OpenAI Codex, Cursor, Aider, and Windsurf. The documentation site publishes [`/llms.txt`](https://gofasta.dev/llms.txt) and [`/llms-full.txt`](https://gofasta.dev/llms-full.txt) so agents reason about current features rather than stale training data. This is not a bolt-on layer — it is a design constraint that shapes every CLI command and every scaffold default. See [§2.7 Agent-Native by Default](#27-agent-native-by-default) and [§4.5 Agent Ergonomics](#45-agent-ergonomics). The CLI generates plain Go code — the same code a senior engineer would write by hand — and then gets out of the way. After generation, there is no gofasta runtime, no application wrapper, no hidden lifecycle. The project is standard Go: `main.go` calls `net/http`, handlers return errors, models are GORM structs, dependency injection is resolved at compile time by Google Wire. A developer can read every line top to bottom and know exactly what runs. An AI agent can introspect every line structurally (`gofasta inspect`, `gofasta routes --json`), verify every change holistically (`gofasta verify`), and detect drift before committing (`gofasta status`). ### 1.3 What Gofasta Is Not - **Not a custom language.** All generated code is standard Go. There is no custom syntax, no transpiler, no preprocessor, and no special file extensions. - **Not a runtime wrapper.** There is no global application object, no lifecycle hooks, and no inversion-of-control container at runtime. Dependency wiring happens at compile time via Google Wire. Gofasta is a toolkit — a CLI plus a library of independent packages — not a runtime layer your code sits inside. - **Not a lock-in.** Every generated file is owned by the developer. The scaffold imports a curated set of `pkg/*` packages as its defaults — configuration, logging, HTTP utilities, authentication, and so on — the same way a typical Go backend imports a curated set of community libraries. These are *defaults, not requirements*. Each `pkg/*` package is independently importable, so a developer who prefers a different library for any single concern (a different config loader, a different cache, a different JWT implementation) can delete the default import, `go get` their preferred alternative, and swap it in without touching the rest of the project. "Not a lock-in" means any package can be replaced — not that no package is used. - **Not an all-or-nothing commitment.** A project can use `pkg/cache` without `pkg/mailer`, or `pkg/resilience` without `pkg/queue`. There is no umbrella import that drags everything in. The CLI also generates code once and does not manage or update generated code after the fact — once a project is scaffolded, the CLI is optional. - **Not an AI runtime library.** While Gofasta is *agent-native* at the development-tooling level (the CLI, the scaffold, and the docs are designed for AI agents to drive), it does not include runtime LLM SDKs, prompt-engineering abstractions, vector-database wrappers, or agent-orchestration frameworks. Applications that need to call LLMs at runtime import a provider SDK (`anthropic-sdk-go`, `openai-go`, the Google Generative AI SDK) directly, the same way they'd import any other third-party library. Agent-native refers to the **development experience** of building Gofasta apps, not to the runtime capabilities of the apps themselves. ### 1.4 Curated Defaults, Not Lock-In A scaffolded project imports packages from `gofastadev/gofasta/pkg/*` — configuration loading, structured logging, JWT authentication, caching, and so on. These are standard Go library imports, not framework bindings. Each package is independently importable, has no side effects, and does not reference any other `pkg/*` package unless functionally necessary (for example, `pkg/health` accepts a `*gorm.DB` and a `cache.CacheService` to check their connectivity). The relationship between a project and `pkg/cache` is identical to the relationship between a project and `redis/go-redis` — it is a dependency the developer chose, and it can be removed with a single import deletion. No other file in the project needs to change. The toolkit provides these packages as curated defaults: a tested, documented set of libraries that work well together and cover the concerns most backends share. A developer who prefers a different JWT library, a different cache client, or a different email provider can swap any individual package without touching the rest. The value is not lock-in. The value is that someone already made the selection, wrote the tests, and documented the integration — so each team does not have to. --- ## 2. Design Principles ### 2.1 Explicit Over Implicit All behavior in a Gofasta project is visible in the source code. There is no convention-based discovery, no auto-registration, no struct-tag-driven routing, and no runtime reflection for dependency injection. If a route exists, there is a line of code registering it. If a dependency is injected, there is a Wire provider set declaring it. A developer reading the code can trace the full request path without needing to know any toolkit-specific conventions — everything that happens at runtime is a plain Go function call they can follow. ### 2.2 Generate, Then Own The CLI generates idiomatic Go code that the developer owns completely. Generated files are committed to version control and can be modified freely. The CLI does not maintain hidden state or require re-running to keep generated code in sync. After generation, the code is yours — modify it, delete it, refactor it. Gofasta gets out of the way. ### 2.3 Standard Library Compatibility The `pkg/*` packages build on Go's standard interfaces wherever possible. HTTP routing uses chi, which implements `http.Handler` and works with standard `net/http` middleware (`func(http.Handler) http.Handler`). Configuration uses struct tags and environment variables. Logging uses `slog` from the standard library. Database access uses GORM, which builds on `database/sql`. None of these choices prevent using the standard library directly, and each is a swappable default — for example, the scaffold's router can be replaced with gorilla/mux or stdlib `http.ServeMux` without touching any `pkg/*` code. A Gofasta project compiles with `go build`, tests with `go test`, lints with `golangci-lint`, and debugs with Delve. It does not require a custom compiler, a patched Go runtime, or a proprietary build command. Any tool that works with standard Go works with a Gofasta project — including IDE support, CI/CD pipelines, and profiling tools. This is a deliberate design constraint, not a limitation. ### 2.4 Compile-Time Safety Gofasta prefers compile-time checks over runtime checks. Dependency injection is resolved at compile time by Google Wire. Type mismatches, missing providers, and circular dependencies are caught by the Go compiler, not at application startup. Generated code uses concrete types and interfaces — not `interface{}` or reflection. ### 2.5 Composable Packages The packages in `pkg/` are independently importable. A project can use `pkg/cache` without `pkg/mailer`, or `pkg/resilience` without `pkg/queue`. There is no umbrella import that pulls in everything — no single `gofasta.App` type a project has to construct, no meta-package that transitively drags in every other package. Each `pkg/*` is self-contained, declares its own minimal dependencies, and can be replaced individually. ### 2.6 No Shortcuts Gofasta does not optimize for the shortest possible demo. It optimizes for codebases that remain readable and maintainable after six months of active development by a team. Patterns like interface-based repositories, separate DTOs, and explicit route registration add a small amount of initial code but pay dividends in testability, clarity, and flexibility. ### 2.7 Agent-Native by Default Gofasta treats AI coding agents (Claude Code, OpenAI Codex, Cursor, Aider, Windsurf, and successors) as first-class users of the CLI and the scaffold — not as an afterthought layered on top of an otherwise human-only tool. The practical consequences: - **Every CLI command that emits structured output honors a global `--json` flag.** The JSON shape is stable API. Agents parse it mechanically; they do not regex-scan English text. - **Every CLI error carries a stable machine-readable code, a one-line message, a remediation hint, and a documentation URL.** Agents pattern-match on the code (e.g., `WIRE_MISSING_PROVIDER`, `HEALTH_CHECK_FAILED`), read the hint for the exact fix, and link to the docs when they need deeper context. No error propagation relies on string parsing. - **Every scaffolded project ships with `AGENTS.md`** at the project root. This file — the emerging universal format adopted by every major AI coding agent — tells agents the directory layout, the command surface, the conventions to follow, the failure modes to avoid (most critically: *do not edit `wire_gen.go`*), and a pre-commit self-check list. Agents read it automatically at startup. - **Every scaffolded project ships with `cmd/schema/main.go`** — a small helper that emits the JSON Schema for `config.yaml` by reflecting over the `AppConfig` type in the version of `pkg/config` pinned in the project's `go.mod`. Invoked by `gofasta config schema`; callable directly as `go run ./cmd/schema`. Agents validate proposed config edits before writing them, instead of guessing key names from stale training data. - **The documentation site publishes `/llms.txt` and `/llms-full.txt`** following the [llmstxt.org](https://llmstxt.org) spec. Agents asking "how does gofasta's `pkg/featureflag` work?" fetch one file and read current, version-matched documentation instead of relying on training-set recall that may be months out of date. - **The `gofasta ai ` command** installs per-agent configuration (permission allowlists, pre-commit hooks, project slash commands, conventions files) without cluttering projects that don't use that particular agent. Opt-in, idempotent, and trivially extensible to new agents as they emerge. - **Higher-level commands like `gofasta verify`, `gofasta status`, `gofasta inspect `, and `gofasta do ` exist specifically to collapse what would otherwise be multi-round-trip agent workflows into single calls.** An agent asked to "add an Invoice resource and confirm the project still compiles" runs two commands (`gofasta do new-rest-endpoint Invoice ...` + `gofasta verify`), not seven. This is *not* a claim that Gofasta generates better code when an agent writes it — generated code is identical whether a human or an agent typed the command. The claim is about *development throughput*: an agent working in a Gofasta codebase has structured-output commands, stable error codes, resource introspection, drift detection, and named workflow chains available, all of which reduce the number of wrong turns and tool calls it takes to complete a task correctly. The whitepaper's [§4.5 Agent Ergonomics](#45-agent-ergonomics) documents each capability in detail. The design constraint follows from a simple observation: AI-assisted development is no longer a niche workflow, and Go toolchains that treat it as one will be slower to use for a large and growing fraction of developers. Gofasta builds for this explicitly, the same way Go's standard library builds for `go build` + `go test` + `go vet` as the assumed tooling surface. --- ## 3. Architecture ### 3.1 Project Structure A Gofasta project follows a layered architecture with clear separation of concerns: ``` myapp/ ├── app/ # Application code │ ├── main/main.go # Entry point │ ├── models/ # GORM database models │ ├── dtos/ # Request/response data transfer objects │ ├── repositories/ # Data access layer │ │ └── interfaces/ # Repository contracts │ ├── services/ # Business logic layer │ │ └── interfaces/ # Service contracts │ ├── rest/ │ │ ├── controllers/ # HTTP handlers │ │ └── routes/ # Route registration │ ├── graphql/ # Only with --graphql │ │ ├── schema/ # .gql schema files │ │ └── resolvers/ # GraphQL resolvers │ ├── validators/ # Input validation rules │ ├── di/ # Dependency injection (Google Wire) │ │ ├── container.go # Container struct │ │ ├── wire.go # Wire build configuration │ │ ├── wire_gen.go # Generated wiring (do not edit) │ │ └── providers/ # Provider sets per resource │ └── jobs/ # Background jobs and scheduled tasks ├── cmd/ # CLI commands (Cobra) │ ├── root.go │ ├── serve.go # HTTP server startup │ └── seed.go # Database seeding ├── db/ │ ├── migrations/ # SQL migration pairs (.up.sql / .down.sql) │ └── seeds/ # Seed data functions ├── configs/ # RBAC policies, feature flag definitions ├── deployments/ # Docker, CI/CD workflows, nginx, systemd ├── templates/emails/ # HTML email templates ├── locales/ # Translation files (i18n) ├── config.yaml # Application configuration ├── compose.yaml # Docker Compose ├── Dockerfile # Production image ├── Makefile # Development shortcuts └── gqlgen.yml # GraphQL code generation config (only with --graphql) ``` ### 3.2 Request Flow The request flow is linear and traceable: ``` HTTP Request → Router (chi) → Middleware (auth, CORS, logging, rate limiting) → Controller (parses request, calls service) → Service (business logic, calls repository) → Repository (data access via GORM) → Database ``` Each layer depends only on the layer below it via interfaces. Controllers never access the database directly. Services never parse HTTP requests. Repositories never contain business logic. ### 3.3 Optional GraphQL Support Gofasta projects are REST-first by default. GraphQL support can be added at project creation with `gofasta new myapp --graphql`, which generates gqlgen schema files, resolvers, and mounts the `/graphql` endpoint alongside the REST API. Both API styles share the same services and repositories — they differ only in the entry point (controllers vs. resolvers). This avoids logic duplication and ensures consistency between APIs. When GraphQL is enabled, the project includes: - `app/graphql/schema/` — GraphQL schema files (`.gql`) - `app/graphql/resolvers/` — Resolver implementations - `gqlgen.yml` — gqlgen code generation configuration - `/graphql` and `/graphql-playground` HTTP endpoints --- ## 4. The CLI Tool ### 4.1 Installation ```bash # Option A: Go install (recommended for Go developers) go install github.com/gofastadev/cli/cmd/gofasta@latest # Option B: Pre-built binary (no Go toolchain needed) curl -fsSL https://raw.githubusercontent.com/gofastadev/cli/main/dist/install.sh | sh ``` **Requirement:** Go 1.25.0 or later (Option A only). Option B downloads a self-contained binary. ### 4.2 Commands The CLI exposes ~40 commands and subcommands, grouped below by purpose. Every command is invokable as `gofasta [flags]` and supports `--help` for detailed usage. #### Project lifecycle | Command | Description | |---------|-------------| | `gofasta new ` | Bootstrap a new project (~78 files) with full structure, Go module, and starter User resource. Accepts a plain name (`myapp`) or a full module path (`github.com/myorg/myapp`). Add `--graphql` / `--gql` to include gqlgen schema and resolvers. | | `gofasta init` | Initialize a freshly cloned project: create `.env` from `.env.example`, run `go mod tidy`, generate Wire DI, generate GraphQL code (if `gqlgen.yml` exists), run database migrations, and verify the build. | | `gofasta doctor` | Check system prerequisites (Go, migrate, Docker, air, wire, gqlgen, swag) and project health (`config.yaml`, database connectivity). Useful for bug reports. | | `gofasta upgrade` | Self-update to the latest CLI release. Detects whether the binary was installed via `go install` or a pre-built binary and runs the right upgrade path. | | `gofasta version` | Print detailed version information including CLI version, Go runtime version, and OS/arch. | #### Development workflow | Command | Description | |---------|-------------| | `gofasta dev` | Bring the full local environment up with one command: preflight → start compose services (db, optional cache + queue profiles) → wait for healthchecks → run pending migrations → optionally seed → launch Air for hot reload → teardown on exit. Optional `--dashboard` starts a local HTML debug panel on `:9090` (app health, services, routes with request/response types, metrics, recent requests, recent SQL, and a per-request **trace waterfall** with span-level call-stack snapshots plus a one-click **replay** button — all gated behind the `devtools` build tag so production binaries pay zero cost). Every step accepts `--json` for structured NDJSON output; every failure surfaces a stable `DEV_*` error code. Each stage can be opted out individually (`--no-services`, `--no-db`, `--no-cache`, `--no-queue`, `--no-migrate`, `--no-teardown`, `--fresh`, `--dry-run`). | | `gofasta serve` | Start the HTTP server (delegates to the project's own `serve` command). | | `gofasta console` | Launch an interactive Go REPL in the project directory using [yaegi](https://github.com/traefik/yaegi). Requires yaegi to be installed separately. | | `gofasta routes` | Display all registered API routes from `app/rest/routes/` in a formatted table (method, path, source file). Structured JSON available via `--json`. | | `gofasta swagger` | Generate OpenAPI/Swagger documentation from code annotations via `swag init`. | | `gofasta wire` | Regenerate Wire dependency injection code. | | `gofasta verify` | Run the full preflight gauntlet — gofmt, vet, golangci-lint, tests with the race detector, build, Wire drift, routes sanity. Structured per-check JSON output; non-zero exit on any failure. The single "am I done?" check for humans and AI agents alike. | | `gofasta status` | Report project drift — Wire-derived code out of sync, Swagger docs stale, pending migrations, uncommitted generated files, `go.sum` freshness. Complementary to `verify` — reports artifacts that need regeneration rather than quality gates. | | `gofasta inspect ` | AST-parse a resource's model, DTOs, service interface, controller, and routes; emit a single structured report. Agents planning a modification get the full picture without opening six files. | | `gofasta config schema` | Emit a JSON Schema (Draft 7) describing `config.yaml` — feed to VS Code YAML / JetBrains editors for autocomplete, or to CI for validation. Shells out to a project-local helper so the schema always matches the gofasta version pinned in `go.mod`. | | `gofasta do ` | Run a named chain of gofasta commands — `new-rest-endpoint` (scaffold + migrate + swagger), `rebuild` (wire + swagger), `fresh-start` (init + migrate + seed), `clean-slate` (db reset + seed), `health-check` (verify + status). Pass `--dry-run` to preview the chain. | | `gofasta ai ` | Install project-specific configuration for an AI coding agent — Claude Code, Cursor, OpenAI Codex, Aider, or Windsurf. Opt-in (only `AGENTS.md` ships by default); idempotent; tracked in `.gofasta/ai.json`. | #### Database | Command | Description | |---------|-------------| | `gofasta migrate up` | Apply all pending SQL migrations. | | `gofasta migrate down` | Roll back the last migration. | | `gofasta seed [--fresh]` | Run database seed functions. Use `--fresh` to drop all tables, re-migrate, then seed. | | `gofasta db reset [--skip-seed]` | Drop all tables, re-apply migrations, and seed. Use `--skip-seed` to skip seeding. | #### Code generation The `gofasta generate` command (shorthand `g`) scaffolds resources. Every generated file is auto-wired into the DI container, routes, and GraphQL schema where applicable. | Command | Description | |---------|-------------| | `gofasta g scaffold [field:type ...]` | Generate a complete REST resource: model, migration, repository (+ interface), service (+ interface), DTOs, Wire provider, controller, routes. Add `--graphql` for GraphQL schema + resolver wiring. | | `gofasta g model [field:type ...]` | Generate a GORM model and SQL migration pair. | | `gofasta g repository [field:type ...]` | Generate model + migration + repository interface + repository implementation. Aliased `gofasta g repo`. | | `gofasta g service [field:type ...]` | Generate through the service layer: model, repo, service interface + implementation, DTOs, Wire provider. Add `--graphql` for a resolver too. Aliased `gofasta g svc`. | | `gofasta g controller [field:type ...]` | Generate everything up through the REST controller + routes, auto-wired end-to-end. Add `--graphql` for GraphQL support. Aliased `gofasta g ctrl`. | | `gofasta g dto [field:type ...]` | Generate request/response DTOs only (standalone). | | `gofasta g migration [field:type ...]` | Generate only the `.up.sql` / `.down.sql` migration pair. | | `gofasta g route ` | Generate only the route registration file (assumes a controller already exists). | | `gofasta g provider ` | Generate a Wire provider and auto-patch the container and wire.go. | | `gofasta g resolver ` | Patch the GraphQL resolver to add a new service dependency. | | `gofasta g job [cron-expr]` | Generate a **cron-scheduled** background job, register it in the scheduler, and add its schedule to `config.yaml`. Uses 6-field cron syntax (with seconds). | | `gofasta g task ` | Generate an **async task handler** for the `pkg/queue` (asynq-backed) queue system. | | `gofasta g email-template ` | Generate an HTML email template in `templates/emails/`. Aliased `gofasta g email`. | Supported field types are `string`, `text`, `int`, `float`, `bool`, `uuid`, and `time`. SQL types are automatically adapted per database driver (PostgreSQL, MySQL, SQLite, SQL Server, ClickHouse) based on `database.driver` in `config.yaml`. #### Deployment All deploy commands read configuration from the `deploy:` section of `config.yaml` and support `--host`, `--method` (`docker` or `binary`), `--port`, `--path`, `--arch`, and `--dry-run` flags. | Command | Description | |---------|-------------| | `gofasta deploy` | Deploy the application to a remote server via SSH. Defaults to the `docker` method (transfers a built Docker image); `--method binary` cross-compiles a Go binary and manages it via systemd. | | `gofasta deploy setup` | Prepare a fresh VPS for deployment: install system packages, Docker (for docker method) or service user (for binary method), create directory structure, install nginx reverse proxy config. | | `gofasta deploy status` | Check the status of the deployed application (current release, container/service state). | | `gofasta deploy logs` | Tail application logs from the remote server (streams `docker compose logs -f` or `journalctl -u -f`). | | `gofasta deploy rollback` | Roll back to the previous release on the remote server. | #### Shell integration | Command | Description | |---------|-------------| | `gofasta completion ` | Generate the shell completion script for bash, zsh, fish, or PowerShell. Output is shell code meant to be `source`d. | | `gofasta help [command]` | Show help for any command. Equivalent to `gofasta --help`. | #### Global flags | Flag | Description | |------|-------------| | `-h, --help` | Show help for the current command. | | `-v, --version` | Print the CLI version (machine-parseable — no banner). | | `--no-banner` | Suppress the branded banner. Also honored via `GOFASTA_NO_BANNER=1` environment variable. The banner is also suppressed automatically for `gofasta completion` (to keep shell scripts valid) and `--version` (to keep output parseable), and when `NO_COLOR` is set the banner renders without ANSI colors. | | `--json` | Emit machine-parseable JSON output (and suppress the banner). Intended for AI agents and CI automation. Every structured-output command honors it. | ### 4.3 Project Creation ```bash gofasta new myapp ``` This command: 1. Creates the project directory with the full structure 2. Runs `go mod init` 3. Copies ~92 template files (REST-only by default) 4. Installs dependencies (`go mod tidy`) 5. Generates Wire dependency injection code 6. Includes a starter `User` resource so the project compiles and runs immediately The command accepts a project name or a full Go module path (e.g., `github.com/myorg/myapp`). If the argument contains `/`, it is used as the module path; otherwise the project name is used as both. The database driver defaults to PostgreSQL and is configured in `config.yaml` after project creation. **Flag:** `--graphql` (or `--gql`) — adds GraphQL support alongside REST. When passed, the project also includes gqlgen configuration, schema files, resolvers, and the `/graphql` endpoint. GraphQL is additive — REST controllers and routes are always present regardless of this flag. ```bash gofasta new myapp # REST-only gofasta new myapp --graphql # REST + GraphQL ``` ### 4.4 Scaffold Generation ```bash gofasta g scaffold Product name:string price:float description:text active:bool ``` This single command generates 11 new files and patches 4 existing files, producing a complete CRUD resource: **Generated files:** - Database model with UUID primary key, timestamps, and soft delete - Up and down SQL migrations - Repository interface and GORM implementation - Service interface and implementation - Request/response DTOs with validation tags - REST controller with Swagger annotations - Route registration - Wire provider set **Patched files:** - `app/di/container.go` — Adds service and controller fields - `app/di/wire.go` — Adds provider set to Wire build - `app/rest/routes/index.routes.go` — Registers routes - `cmd/serve.go` — Wires controller into route configuration **Supported field types:** | Type | Go Type | Postgres | MySQL | SQLite | GraphQL | |------|---------|----------|-------|--------|---------| | `string` | `string` | `VARCHAR(255)` | `VARCHAR(255)` | `TEXT` | `String` | | `text` | `string` | `TEXT` | `LONGTEXT` | `TEXT` | `String` | | `int` | `int` | `INTEGER` | `INT` | `INTEGER` | `Int` | | `float` | `float64` | `DECIMAL(10,2)` | `DECIMAL(10,2)` | `REAL` | `Float` | | `bool` | `bool` | `BOOLEAN` | `TINYINT(1)` | `INTEGER` | `Boolean` | | `uuid` | `uuid.UUID` | `UUID` | `CHAR(36)` | `TEXT` | `ID` | | `time` | `time.Time` | `TIMESTAMP` | `DATETIME` | `DATETIME` | `DateTime` | SQL types are automatically adapted per database driver. Every scaffold run auto-verifies the generated code compiles by invoking `go build ./...` as a final step (skippable with `--no-verify`). A starter `.controller_test.go` is emitted alongside the controller so the project is green on `go test` out of the box — and any template regression shows up immediately instead of silently producing broken code. ### 4.5 Agent Ergonomics Every new project ships first-class integration with AI coding agents — Claude Code, OpenAI Codex, Cursor, Aider, Windsurf, and any MCP-aware successor. The support is layered: universal files ship by default, agent-specific configuration is opt-in, and every CLI command has a machine-parseable mode. **Default artifacts in every scaffolded project:** - **`AGENTS.md`** at the project root. The emerging universal format picked up by every modern coding agent. Ships with project overview, every command, conventions to follow, a "things NOT to do" list (most notably *don't edit `wire_gen.go`*), a Wire walkthrough covering the most common failure mode, and a pre-commit self-check list. Agents read it automatically at startup. - **`cmd/schema/main.go`** — a small helper that reflects over the `AppConfig` type in the project's pinned `pkg/config` version and emits the resulting JSON Schema (Draft 7). Invoked by `gofasta config schema`; callable directly as `go run ./cmd/schema` for CI or IDE extensions. Because it runs in the project, the schema always matches the exact library version the project builds against — no version skew between the CLI and the installed `pkg/config`. **CLI-level agent features:** - **Structured errors.** Every `gofasta` error carries a stable machine-readable code, a one-line message, a remediation hint, and a link to the relevant docs page. In `--json` mode errors serialize as `{code, message, hint, docs}` — agents pattern-match on the code rather than regex-parsing English. - **Universal `--json` flag.** Every structured-output command (`routes`, `version`, `verify`, `status`, `inspect`, `config schema`, `ai status`, `do `, `g scaffold`, …) emits a single-line JSON document when the flag is set. The shapes are stable API. - **`gofasta ai ` installer.** Opt-in, idempotent installation of per-agent configuration (permission allowlists, pre-commit hooks, project slash commands, conventions files). Shipping every agent's configuration in the scaffold would clutter projects for developers who don't use agents; this installer is the middle path. **Documentation-side agent artifacts:** - **`/llms.txt`** and **`/llms-full.txt`** on the documentation site. The first is a structured markdown index of every docs page following the [llmstxt.org](https://llmstxt.org) spec; the second is the entire documentation concatenated into one file for agents that prefer single-shot context loading. Both regenerated on every deploy via a build-time Node script that walks the MDX tree — no hand maintenance. These decisions together make gofasta projects materially smoother to develop with AI assistance: agents land with immediate context (AGENTS.md), navigate the docs efficiently (llms.txt), run commands and parse results mechanically (`--json`), and get their own tooling configured with a single command (`gofasta ai `). After generation, run `gofasta wire` to regenerate the DI container and `gofasta migrate up` to create the table. --- ## 5. The Gofasta Library The library (`github.com/gofastadev/gofasta`) provides packages under `pkg/`. Each package is independently importable — there is no central "gofasta" import that pulls in everything. | Package | Purpose | |---------|---------| | `pkg/config` | YAML configuration loading with environment variable overrides | | `pkg/logger` | Structured logging via Go's `slog` with configurable format and level | | `pkg/errors` | Typed application errors mapped to HTTP status codes and GraphQL error presentation | | `pkg/models` | Base model with UUID, timestamps, soft delete, optimistic locking | | `pkg/types` | Common DTOs for pagination, sorting, API responses | | `pkg/httputil` | JSON binding, response helpers, error-returning handler adapter | | `pkg/middleware` | CORS, rate limiting, logging, recovery, security headers, request ID, content-type validation | | `pkg/health` | Liveness and readiness health check endpoints | | `pkg/auth` | JWT generation/validation, Casbin RBAC, authentication and authorization middleware | | `pkg/encryption` | AES-256-GCM encryption/decryption for data at rest | | `pkg/session` | Cookie and filesystem session management (wraps gorilla/sessions) | | `pkg/cache` | In-memory and Redis caching with a unified interface | | `pkg/storage` | Local filesystem and S3-compatible file storage | | `pkg/seeds` | Database seeding with ordered execution | | `pkg/mailer` | Email delivery via SMTP, SendGrid, or Brevo with HTML template rendering | | `pkg/notify` | Multi-channel notifications (email, SMS via Twilio, Slack webhooks, database) | | `pkg/websocket` | WebSocket hub with rooms, broadcast, and connection management | | `pkg/scheduler` | Cron-based job scheduling with second-precision expressions | | `pkg/queue` | Async task queue backed by Redis via hibiken/asynq | | `pkg/resilience` | Retry policies and circuit breakers via failsafe-go | | `pkg/validators` | Struct validation via go-playground/validator/v10 with custom rules | | `pkg/i18n` | Internationalization with YAML locale files via go-i18n | | `pkg/observability` | Prometheus metrics and OpenTelemetry distributed tracing | | `pkg/featureflag` | Feature flag evaluation via the OpenFeature Go SDK with a swappable provider (in-memory, Flagd, LaunchDarkly, go-feature-flag, or custom) | | `pkg/utils` | Pagination math, string helpers, generic slice/map operations | | `pkg/testutil/testdb` | PostgreSQL container setup via testcontainers-go for integration tests | ### 5.1 Interface-Driven Design Every package that admits multiple implementations defines an interface. This enables swapping implementations without changing consuming code: ```go // pkg/cache defines the interface type CacheService interface { Get(ctx context.Context, key string, dest interface{}) error Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error Delete(ctx context.Context, key string) error Flush(ctx context.Context) error Ping(ctx context.Context) error } // Two implementations are provided cache.NewMemoryCache() // In-memory with TTL cache.NewRedisCache(cfg) // Redis-backed ``` The same pattern applies to `StorageService` (local filesystem / S3), `EmailSender` (SMTP / SendGrid / Brevo), and `QueueService` (asynq / Redis). ### 5.2 Configuration All packages read configuration from a central `config.yaml` file with environment variable overrides: ```yaml app: name: myapp port: 8080 env: development database: driver: postgres host: localhost port: 5432 name: myapp_db user: postgres password: postgres ssl_mode: disable max_open_conns: 25 max_idle_conns: 5 conn_max_lifetime: 5m jwt: secret: change-me-in-production expiration: 24h refresh_expiration: 168h issuer: myapp cache: driver: memory ttl: 5m mail: driver: smtp from_name: MyApp from_address: noreply@myapp.com queue: driver: memory workers: 5 retry_attempts: 3 retry_delay: 30s storage: driver: local local: path: ./uploads observability: metrics: enabled: true path: /metrics tracing: enabled: false exporter: jaeger rate_limit: enabled: true max: 100 window: 1m log: level: info format: json ``` Every value can be overridden via environment variables using the prefix `GOFASTA_` with dot-separated keys mapped to underscores (e.g., `GOFASTA_DATABASE_HOST=db`). This allows committing safe development defaults while managing production secrets through the environment. --- ## 6. Code Generation Strategy ### 6.1 Philosophy Gofasta's code generation follows a principle common among well-accepted Go tools like `sqlc`, `Wire`, `oapi-codegen`, and `go-blueprint`: **generate readable Go code, then get out of the way.** The generated output is: - **Standard Go** — no custom syntax, no build tags (except Wire's `wireinject` tag), no preprocessing - **Readable** — generated code looks like code a developer would write by hand - **Committed** — generated files are checked into version control - **Editable** — developers can and should modify generated code to fit their needs - **CLI-independent** — once a project is generated, the `gofasta` CLI is not needed to build, run, migrate, or deploy it. A developer can uninstall the CLI and the project keeps working. The CLI is a generation tool, not a runtime dependency. Generated projects do import the toolkit's `pkg/*` packages as their default building blocks (for config loading, logging, JWT, caching, mailing, and so on), just as any Go backend imports a set of community libraries. Those defaults are *opt-out*: any single `pkg/*` import can be deleted and replaced with an alternative library or a local implementation without touching the rest of the project. The toolkit never installs a runtime wrapper around your code — your `main.go` calls standard `net/http`, your HTTP handlers return plain errors, your models are plain GORM structs. ### 6.2 What Gets Generated The CLI uses Go's `text/template` package to produce source files. Templates are embedded in the CLI binary. The generation process is deterministic: the same input always produces the same output. | Generator | Input | Output | |-----------|-------|--------| | `new` | Project name, module path, driver | ~78 files: full project structure | | `scaffold` | Resource name, field definitions | 11 new files + 4 patched files | | `model` | Resource name, field definitions | Model struct + migration pair | | `migration` | Migration name, optional fields | SQL migration pair (up/down) | | `repository` | Resource name, field definitions | Interface + GORM implementation | | `service` | Resource name, field definitions | Interface + implementation | | `controller` | Resource name, field definitions | REST controller with Swagger annotations | | `dto` | Resource name, field definitions | Create/Update/Response DTOs | | `route` | Resource name | Route registration function | | `resolver` | Resource name | GraphQL schema + resolver stubs | | `provider` | Resource name | Wire provider set | | `job` | Job name, optional cron expression | Background job with scheduler registration | | `task` | Task name | Async task handler for asynq | | `email-template` | Template name | Responsive HTML email template | ### 6.3 Name Conventions The CLI automatically converts resource names across naming conventions: | Input | PascalCase | snake_case | camelCase | Plural | |-------|-----------|------------|-----------|--------| | `Product` | `Product` | `product` | `product` | `products` | | `OrderItem` | `OrderItem` | `order_item` | `orderItem` | `order_items` | This ensures consistent naming across Go types (PascalCase), database tables (snake_case), JSON fields (camelCase), and URL paths (plural snake_case). --- ## 7. Dependency Injection ### 7.1 Google Wire Gofasta uses [Google Wire](https://github.com/google/wire) for compile-time dependency injection. Wire generates concrete Go code that wires all dependencies — there is no runtime container, no reflection, and no service locator pattern. ### 7.2 How It Works Each resource defines a **provider set** that declares how to construct its components: ```go // app/di/providers/product.go var ProductSet = wire.NewSet( repositories.NewProductRepository, wire.Bind(new(repoInterfaces.ProductRepository), new(*repositories.ProductRepositoryImpl)), services.NewProductService, wire.Bind(new(svcInterfaces.ProductService), new(*services.ProductServiceImpl)), controllers.NewProductController, ) ``` An **injector function** in `app/di/wire.go` assembles all provider sets: ```go //go:build wireinject func InitializeContainer(cfg *config.AppConfig, db *gorm.DB) (*Container, error) { wire.Build( providers.UserSet, providers.AuthSet, providers.ProductSet, wire.Struct(new(Container), "*"), ) return nil, nil } ``` Running `gofasta wire` (which invokes `wire ./app/di/...`) produces `wire_gen.go` — a concrete function that constructs the entire dependency graph. If any dependency is missing or circular, the Go compiler reports it as a build error. ### 7.3 Why Wire - **Compile-time resolution.** Missing dependencies are caught at build time, not at startup. - **No reflection.** The generated code is plain Go function calls. - **Readable output.** `wire_gen.go` shows exactly how every dependency is constructed. - **No framework coupling.** Wire is a standalone tool maintained by the Go team at Google. --- ## 8. Database Layer ### 8.1 GORM Gofasta uses [GORM](https://gorm.io) as its database abstraction. GORM provides an active-record-style ORM with support for multiple database drivers. ### 8.2 Multi-Database Support A single `database.driver` field in `config.yaml` controls which database the project uses. The CLI generates driver-specific SQL migrations, and the `pkg/config` package connects to the correct driver at startup. Switching databases requires changing one configuration value and regenerating migrations — no application code changes. | Driver | Config value | Connection pooling | Notes | |--------|-------------|-------------------|-------| | PostgreSQL | `postgres` | Yes | Default. Full feature support including CITEXT, UUID generation via `gen_random_uuid()`. | | MySQL | `mysql` | Yes | UTF-8 (`utf8mb4`), timezone-aware connections. | | SQLite | `sqlite` | No | File-based or `:memory:`. Suitable for development, testing, and embedded use. | | SQL Server | `sqlserver` | Yes | Microsoft SQL Server. | | ClickHouse | `clickhouse` | Yes | Column-oriented, for analytics workloads. | Generated migrations adapt SQL types per driver automatically. For example, a `uuid` field produces `UUID` on PostgreSQL, `CHAR(36)` on MySQL, and `TEXT` on SQLite. A `bool` field produces `BOOLEAN` on PostgreSQL, `TINYINT(1)` on MySQL, and `INTEGER` on SQLite. Because the `postgres` driver uses GORM's standard PostgreSQL dialect, projects can point it at any PostgreSQL wire-compatible backend — including managed Postgres services (Neon, Supabase, Amazon RDS, Google Cloud SQL) and distributed SQL engines (CockroachDB, YugabyteDB) — by changing the DSN in `config.yaml`. Driver-specific features (CockroachDB's `gen_random_uuid()` replacement, serialization levels, etc.) are the responsibility of the target database and are configured through its own tooling; gofasta does not ship a dedicated driver entry for each wire-compatible backend. ### 8.3 Base Model All models embed `models.BaseModelImpl` from the `pkg/models` package, which provides: - **UUID primary key** — auto-generated via GORM's `BeforeCreate` hook - **Timestamps** — `CreatedAt` and `UpdatedAt`, managed automatically by GORM - **Soft delete** — `DeletedAt` field; deleted records are excluded from queries by default - **Optimistic locking** — `RecordVersion` field incremented on each update for concurrent write safety - **Active flag** — `IsActive` and `IsDeletable` fields for business-level record control ```go type Product struct { models.BaseModelImpl Name string `gorm:"type:varchar(255);not null" json:"name"` Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` } ``` ### 8.4 Migrations Migrations are plain SQL files in `db/migrations/`, organized as numbered pairs: ``` db/migrations/ ├── 000001_create_users.up.sql ├── 000001_create_users.down.sql ├── 000002_create_products.up.sql └── 000002_create_products.down.sql ``` The `up` file applies the change; the `down` file reverses it. Migrations are run with `gofasta migrate up` and rolled back with `gofasta migrate down`. Migration status is tracked in the database. A new project includes base migrations that create database-level functions for auto-updating `updated_at` timestamps, enforcing soft-delete protection on non-deletable records, and incrementing record versions — all as database triggers, not application code. ### 8.5 Repository Pattern Data access is abstracted behind repository interfaces: ```go // app/repositories/interfaces/product_repository.go type ProductRepository interface { Create(ctx context.Context, product *models.Product) error FindAll(ctx context.Context, page, perPage int) ([]models.Product, int64, error) FindByID(ctx context.Context, id string) (*models.Product, error) Update(ctx context.Context, product *models.Product) error Delete(ctx context.Context, id string) error } ``` The implementation uses GORM directly. This separation allows mocking the repository in service tests without a database connection. ### 8.6 Seeds Database seeds in `db/seeds/` populate initial data (default roles, admin users, test data). Seeds are registered via `pkg/seeds` and executed in order: ```bash gofasta seed # Run all seeds gofasta seed --fresh # Drop tables, re-migrate, then seed ``` --- ## 9. HTTP and API Layer ### 9.1 Routing Gofasta uses [chi](https://github.com/go-chi/chi) as its default HTTP router, which builds on Go's standard `net/http` interfaces. The router is an opt-out default — it can be replaced with gorilla/mux, stdlib `http.ServeMux`, or any `http.Handler`-compatible router without touching the rest of the project. Routes are registered explicitly in Go code: ```go func ProductRoutes(r chi.Router, ctrl *controllers.ProductController) { r.Get("/products", httputil.Handle(ctrl.List)) r.Post("/products", httputil.Handle(ctrl.Create)) r.Get("/products/{id}", httputil.Handle(ctrl.GetByID)) r.Put("/products/{id}", httputil.Handle(ctrl.Update)) r.Delete("/products/{id}", httputil.Handle(ctrl.Archive)) } ``` Every route is visible in the source code. There is no auto-discovery based on file names or struct methods. The `httputil.Handle()` wrapper adapts error-returning controller methods to standard `http.HandlerFunc`. ### 9.2 Controllers Controllers return errors instead of writing responses directly on failure. The `httputil.Handle()` wrapper converts returned errors to appropriate HTTP responses: ```go func (c *ProductController) Create(w http.ResponseWriter, r *http.Request) error { var req dtos.CreateProductRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { return apperrors.BadRequest("invalid request body") } result, err := c.service.Create(r.Context(), req) if err != nil { return err } return httputil.Created(w, result) } ``` Path parameters are extracted via `chi.URLParam(r, "id")`. Request context is available via `r.Context()`. ### 9.3 Request Binding The `httputil` package provides generic bind functions that parse and validate request bodies in a single call: ```go // Bind JSON body and validate struct tags dto, err := httputil.BindJSON[dtos.CreateProductDto](r) // Bind query parameters query, err := httputil.BindQuery[dtos.ListProductsQuery](r) // Bind form data form, err := httputil.BindForm[dtos.UploadRequest](r) ``` If validation fails, the bind functions return structured error responses automatically. ### 9.4 DTOs Request and response types are separated from database models via DTOs: ```go type TCreateProductDto struct { Name string `json:"name" validate:"required"` Price float64 `json:"price" validate:"required"` } type TUpdateProductDto struct { Name *string `json:"name,omitempty"` Price *float64 `json:"price,omitempty"` } type ProductResponse struct { ID string `json:"id"` Name string `json:"name"` Price float64 `json:"price"` RecordVersion int `json:"recordVersion"` IsActive bool `json:"isActive"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } ``` Update DTOs use pointer fields to distinguish between "not provided" (nil) and "set to zero value." Validation uses `go-playground/validator/v10` struct tags. ### 9.5 Response Helpers The `httputil` package provides consistent JSON response formatting: ```go httputil.OK(w, data) // 200 httputil.Created(w, data) // 201 httputil.JSON(w, status, data) // Custom status httputil.Handle(handlerFn) // Wraps error-returning handlers into http.HandlerFunc ``` ### 9.6 Middleware The `middleware` package provides standard HTTP middleware using the `func(http.Handler) http.Handler` signature, composable via `middleware.Chain()`: | Middleware | Function | |-----------|----------| | `middleware.RequestLogging(logger)` | Structured request logging with method, path, status, duration, and request ID | | `middleware.Recovery()` | Panic recovery with error logging | | `middleware.CORS(config)` | Cross-origin resource sharing | | `middleware.SecurityHeaders(config)` | HSTS, CSP, X-Frame-Options, X-Content-Type-Options via unrolled/secure | | `middleware.RateLimit(config)` | Per-IP rate limiting with configurable store (memory or Redis) | | `middleware.RequestID()` | Generates and stores a unique request ID in context | | `middleware.ContentTypeValidation()` | Validates request Content-Type header | | `middleware.Chain(handler, middlewares...)` | Compose multiple middleware in order | ### 9.7 GraphQL (Opt-In) GraphQL support is available when a project is created with `gofasta new myapp --graphql`. It is powered by [gqlgen](https://gqlgen.com) and is additive — REST controllers and routes remain fully functional. Schema files live in `app/graphql/schema/`, and resolvers are generated from the schema. Resolvers call the same services used by REST controllers, ensuring logic consistency: ```go func (r *queryResolver) Products(ctx context.Context, page *int, limit *int) (*dtos.PaginatedProductResponse, error) { return r.productService.FindAll(*page, *limit) } ``` Available endpoints: - `/graphql` — GraphQL endpoint - `/graphql-playground` — Interactive GraphQL explorer (development only) ### 9.8 Swagger Controllers include Swagger annotation comments. Running `gofasta swagger` generates OpenAPI spec files (`docs/swagger.json`, `docs/swagger.yaml`) and a Swagger UI endpoint at `/swagger/index.html`. --- ## 10. Authentication and Authorization ### 10.1 JWT Authentication The `pkg/auth` package provides JWT token management: ```go jwtService := auth.NewJWTService(cfg.JWT) // Generate tokens token, err := jwtService.GenerateToken(userID, role) refreshToken, err := jwtService.GenerateRefreshToken(userID) // Validate and refresh claims, err := jwtService.ValidateToken(tokenString) newToken, err := jwtService.RefreshToken(refreshToken) ``` Configuration via `config.yaml`: ```yaml jwt: secret: change-me-in-production expiration: 24h refresh_expiration: 168h issuer: myapp ``` Generated projects include pre-built auth endpoints: register (`POST /api/v1/auth/register`), login (`POST /api/v1/auth/login`), and token refresh (`POST /api/v1/auth/refresh`). ### 10.2 RBAC with Casbin Role-based access control uses [Casbin](https://casbin.org) with policy files: ```csv # configs/rbac_policy.csv p, admin, /api/v1/*, * p, user, /api/v1/products, GET p, user, /api/v1/profile, (GET)|(PUT) g, admin, moderator g, moderator, user ``` The `auth.RBACMiddleware()` middleware checks permissions on each request. Policies can also be modified at runtime via the Casbin enforcer API. ### 10.3 Authentication Middleware The `pkg/auth` package provides two middleware functions: ```go // Extract and validate JWT from Authorization: Bearer header auth.JWTAuth(jwtService) // Enforce RBAC permissions based on request path and method auth.RBACMiddleware(rbacService, resource, action) ``` Both middleware functions follow the standard `func(http.Handler) http.Handler` signature. --- ## 11. Security Defaults A scaffolded Gofasta project includes security protections out of the box, configured as middleware in the HTTP pipeline. These are not hidden behaviors — each protection is a visible middleware call in the route configuration, and each can be modified or removed by editing the relevant source file. ### 11.1 What Ships by Default | Protection | Implementation | What it does | |-----------|---------------|-------------| | **Security headers** | `middleware.SecurityHeaders()` | Sets HSTS, Content-Security-Policy, X-Frame-Options (DENY), X-Content-Type-Options (nosniff), Referrer-Policy, and Permissions-Policy via the unrolled/secure library. | | **CORS** | `middleware.CORS(config)` | Configurable allowed origins, methods, and headers. Defaults to restrictive settings. | | **Rate limiting** | `middleware.RateLimit(config)` | Token bucket algorithm, configurable per-IP limits and time window. Supports memory and Redis stores. | | **JWT authentication** | `auth.JWTAuth()` | HS256-signed tokens with configurable expiry. Refresh token support. | | **Role-based access control** | `auth.RBACMiddleware()` | Casbin-backed permission enforcement per route. | | **Password hashing** | bcrypt | Passwords are never stored in plaintext. | | **Encryption at rest** | `pkg/encryption` | AES-256-GCM for sensitive data fields. | | **Request ID** | `middleware.RequestID()` | Unique ID per request for audit trails and log correlation. | | **Panic recovery** | `middleware.Recovery()` | Catches panics, logs the stack trace, returns 500 without exposing internals. | | **Content-type validation** | `middleware.ContentTypeValidation()` | Rejects requests with unexpected Content-Type headers. | | **Input validation** | `pkg/validators` | Struct-level validation on all incoming DTOs via go-playground/validator. | | **Soft deletes** | `models.BaseModelImpl` | Records are marked deleted, never physically removed. Database triggers prevent deletion of non-deletable records. | | **Optimistic locking** | `models.BaseModelImpl` | `RecordVersion` field prevents silent overwrites from concurrent updates. | ### 11.2 No Hidden Magic Every protection listed above corresponds to a line of code in the generated project. Security headers are applied because `middleware.SecurityHeaders(cfg)` is called in the middleware chain. Rate limiting works because `middleware.RateLimit(cfg)` is registered. A developer can read the middleware stack top to bottom and know exactly what runs on every request. There is no implicit security layer that activates behind the scenes. --- ## 12. Background Processing ### 12.1 Scheduled Jobs The `pkg/scheduler` package runs cron-based recurring tasks: ```go s := scheduler.New(logger) s.Register(myJob) // myJob implements scheduler.Job interface: Name() string, Run() error s.Start() defer s.Stop() ``` The `Job` interface requires two methods: `Name()` (returns a human-readable identifier for logging) and `Run()` (executes the job logic). The scheduler uses 6-field cron syntax with second precision via robfig/cron. The CLI generates job scaffolding with `gofasta g job cleanup-tokens "0 0 * * * *"`, which creates the job file, registers it in the scheduler, and adds the cron expression to `config.yaml`. ### 12.2 Async Task Queues The task queue is built on [hibiken/asynq](https://github.com/hibiken/asynq), a Redis-based distributed task queue: ```go queue := queue.NewAsynqQueue(cfg, logger) // Register handler queue.RegisterHandler("send_welcome_email", handleSendWelcomeEmail) // Enqueue a task from application code queue.Enqueue("send_welcome_email", payload) // Start processing queue.Start() defer queue.Shutdown() ``` Tasks are enqueued from application code and processed asynchronously by worker goroutines. Failed tasks are retried automatically by asynq with configurable retry policies and exponential backoff. The CLI generates task scaffolding with `gofasta g task send-welcome-email`. **Backend:** Redis (via asynq). --- ## 13. Real-Time Communication The `pkg/websocket` package provides a WebSocket hub for real-time bidirectional communication: ```go hub := websocket.NewHub(logger) go hub.Run() // In an HTTP handler — upgrade the connection router.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) { websocket.ServeWS(hub, w, r) }) ``` ### 13.1 Rooms Clients can be organized into rooms for scoped messaging: ```go hub.JoinRoom(client, "chat:lobby") hub.LeaveRoom(client, "chat:lobby") ``` ### 13.2 Broadcasting Messages can be sent to all connected clients or to a specific room: ```go // Broadcast to all clients hub.Broadcast(websocket.Message{ Event: "notification", Payload: payload, }) // Broadcast to a specific room hub.Broadcast(websocket.Message{ Room: "chat:lobby", Event: "new_message", Payload: payload, }) ``` ### 13.3 Connection Management The hub tracks all connected clients with goroutine-safe operations: ```go count := hub.ClientCount() ``` Clients support configurable ping periods and write deadlines via functional options: ```go websocket.ServeWS(hub, w, r, websocket.WithPingPeriod(30 * time.Second), websocket.WithWriteWait(10 * time.Second), ) ``` --- ## 14. Email and Notifications ### 14.1 Email The `pkg/mailer` package provides email delivery through three providers behind a single interface: ```go type EmailSender interface { Send(ctx context.Context, msg EmailMessage) error } ``` **Providers:** - `SMTPSender` — SMTP with optional STARTTLS - `SendGridSender` — SendGrid API v3 - `BrevoSender` — Brevo (formerly Sendinblue) HTTP API The active provider is selected by `mail.driver` in `config.yaml`. A factory function creates the correct implementation: ```go sender, err := mailer.NewEmailSender(cfg, renderer, logger) ``` ### 14.2 HTML Email Templates The `TemplateRenderer` loads and caches Go HTML templates from the `templates/emails/` directory: ```go renderer := mailer.NewTemplateRenderer("templates/emails", "MyApp") html, err := renderer.Render("welcome", map[string]any{ "Name": "Alice", "AppName": "MyApp", }) ``` The CLI generates new email templates with `gofasta g email-template welcome`, which creates an HTML file extending the base layout with header and footer. ### 14.3 Multi-Channel Notifications The `pkg/notify` package dispatches notifications across multiple channels: ```go notifier := notify.NewNotifier(logger, notify.NewEmailChannel(emailSender), notify.NewSMSChannel(twilioSID, twilioToken, fromNumber), notify.NewSlackChannel(webhookURL), notify.NewDatabaseChannel(db), ) err := notifier.Send(ctx, notify.Recipient{ ID: "user-123", Email: "alice@example.com", Phone: "+1234567890", Name: "Alice", }, notify.Notification{ Subject: "Order Shipped", Body: "Your order #456 has been shipped.", Channels: []notify.Channel{notify.ChannelEmail, notify.ChannelSMS}, }) ``` Each channel implements the `ChannelSender` interface: | Channel | Backend | What it sends | |---------|---------|-------------| | `EmailChannel` | Delegates to `pkg/mailer` | Email via SMTP, SendGrid, or Brevo | | `SMSChannel` | Twilio REST API | SMS text messages | | `SlackChannel` | Incoming Webhook URL | Slack messages | | `DatabaseChannel` | GORM (any supported database) | Persisted notification records with read/unread tracking | If no channels are specified in the notification, the notifier dispatches to all registered channels. --- ## 15. File Storage The `pkg/storage` package provides file storage behind a unified interface: ```go type StorageService interface { Upload(ctx context.Context, path string, reader io.Reader, size int64) error Download(ctx context.Context, path string) (io.ReadCloser, error) Delete(ctx context.Context, path string) error URL(path string) string } ``` **Backends:** | Backend | Config value | Implementation | |---------|-------------|---------------| | Local filesystem | `local` | Stores files at a configured directory path. `URL()` returns the joined filesystem path. | | S3-compatible | `s3` | AWS S3, MinIO, DigitalOcean Spaces, or any S3-compatible provider via the minio-go client. `URL()` returns the HTTPS object URL. | The active backend is selected by `storage.driver` in `config.yaml`: ```yaml storage: driver: local # or "s3" local: path: ./uploads s3: endpoint: s3.amazonaws.com bucket: myapp-uploads region: us-east-1 access_key: ${AWS_ACCESS_KEY_ID} secret_key: ${AWS_SECRET_ACCESS_KEY} use_ssl: true ``` Application code uses the interface, making the backend swappable without code changes: ```go store, err := storage.NewStorageService(cfg, logger) err := store.Upload(ctx, "avatars/user-123.jpg", file, fileSize) reader, err := store.Download(ctx, "avatars/user-123.jpg") url := store.URL("avatars/user-123.jpg") err := store.Delete(ctx, "avatars/user-123.jpg") ``` --- ## 16. Internationalization The `pkg/i18n` package provides message translation using YAML locale files: ```go i18nService := i18n.NewI18nService("locales", "en") // Translate a message greeting := i18nService.T("fr", "welcome_message", map[string]interface{}{ "Name": "Alice", }) // Extract language from HTTP request (Accept-Language header) lang := i18nService.LangFromRequest(r) msg := i18nService.T(lang, "validation.required", nil) ``` Locale files live in the `locales/` directory as YAML: ```yaml # locales/en.yaml welcome_message: "Welcome, {{.Name}}!" validation: required: "This field is required." email: "Please enter a valid email address." ``` The function `i18n.CreateDefaultLocaleFile("locales")` generates a starter English locale file if none exists. A scaffolded project includes the `locales/` directory with an `en.yaml` file. --- ## 17. Feature Flags The `pkg/featureflag` package is a thin wrapper around the [OpenFeature Go SDK](https://github.com/open-feature/go-sdk) — the CNCF-standard flag-evaluation contract. Application code calls one method (`IsEnabled`); the actual flag storage and rule engine live in a **provider** the application registers at startup. Providers are swappable: an in-memory provider for local development, Flagd or go-feature-flag for self-hosted rule engines, LaunchDarkly for commercial SaaS, or a custom implementation — the application's flag-checking code never changes. ```go // Register any OpenFeature-compatible provider at startup. // Example: in-memory provider for local dev. flags := map[string]memprovider.InMemoryFlag{ "new_checkout_flow": { Key: "new_checkout_flow", State: memprovider.Enabled, DefaultVariant: "off", Variants: map[string]any{"on": true, "off": false}, }, } flagService, _ := featureflag.NewInMemoryService(flags, logger) defer flagService.Close() // Evaluation is provider-independent. if flagService.IsEnabled(ctx, "new_checkout_flow", userID, map[string]any{ "plan": "premium", "country": "US", }) { // New checkout logic } ``` Swapping to a production rule engine is a one-line change: call `openfeature.SetProvider(...)` with the target provider before constructing the service. Every call site that evaluates flags continues to work unchanged. This keeps `pkg/featureflag` consistent with gofasta's opt-out-default philosophy — the package ships a contract, not a locked-in backend. --- ## 18. Observability ### 18.1 Prometheus Metrics The `pkg/observability` package exposes Prometheus metrics via HTTP middleware: ```go router.Use(observability.MetricsMiddleware) router.Handle("/metrics", observability.MetricsHandler()) ``` **Exported metrics:** | Metric | Type | Labels | Description | |--------|------|--------|-------------| | `http_requests_total` | Counter | method, path, status | Total HTTP requests processed | | `http_request_duration_seconds` | Histogram | method, path | Request latency distribution | | `http_requests_in_flight` | Gauge | — | Number of requests currently being processed | These metrics are automatically recorded by the `MetricsMiddleware` for every HTTP request. The `/metrics` endpoint returns Prometheus-formatted output compatible with any Prometheus scraper, Grafana dashboard, or alerting system. ### 18.2 Distributed Tracing OpenTelemetry tracing is available via two functions: ```go // Initialize the tracer provider shutdown := observability.InitTracer("myapp") defer shutdown() // Apply tracing middleware to the router router.Use(observability.TracingMiddleware("myapp")) ``` `InitTracer` sets up an OTLP exporter with an AlwaysSample strategy and registers trace context and baggage propagation. `TracingMiddleware` creates spans for each HTTP request with method, path, and status attributes. When tracing is not configured, both functions are no-ops with zero overhead. ### 18.3 Structured Logging The `pkg/logger` package creates a structured logger using Go's standard `slog` package: ```go log := logger.NewLogger(cfg.Log) ``` The returned `*slog.Logger` supports JSON or text output format and configurable log levels (debug, info, warn, error). Because it returns a standard `slog.Logger`, it is compatible with any library or middleware that accepts `slog`. ### 18.4 Health Checks The `pkg/health` package provides a controller with three endpoints for liveness and readiness probes: ```go healthCtrl := health.NewController(db, cacheService) router.HandleFunc("/health", httputil.Handle(healthCtrl.Check)) router.HandleFunc("/health/live", httputil.Handle(healthCtrl.Live)) router.HandleFunc("/health/ready", httputil.Handle(healthCtrl.Ready)) ``` | Endpoint | Purpose | Checks | |----------|---------|--------| | `/health` | Basic liveness | Process is running, reports uptime | | `/health/live` | Liveness probe | Process is alive | | `/health/ready` | Readiness probe | Database connectivity (with latency), cache connectivity (if configured) | The readiness endpoint checks database and cache health with timing information, making it suitable for load balancer health checks, uptime monitors, and orchestrator-style probes. --- ## 19. Resilience The `pkg/resilience` package provides composable fault tolerance patterns using [failsafe-go](https://github.com/failsafe-go/failsafe-go) generics: ### 19.1 Retry with Backoff ```go retryPolicy := resilience.NewRetryPolicy[*http.Response](3, 100*time.Millisecond) response, err := resilience.Execute(func() (*http.Response, error) { return http.Get("https://api.example.com/data") }, retryPolicy) ``` `NewRetryPolicy[T]` creates a retry policy with the specified maximum attempts and delay between retries. Failsafe-go applies exponential backoff automatically. ### 19.2 Circuit Breaker ```go cb := resilience.NewCircuitBreaker[*PaymentResult](5, 30*time.Second) result, err := resilience.Execute(func() (*PaymentResult, error) { return paymentClient.Charge(ctx, amount) }, cb) ``` `NewCircuitBreaker[T]` creates a circuit breaker that opens after the specified number of failures and remains open for the specified delay before transitioning to half-open. States: Closed (normal) → Open (fail-fast) → Half-Open (test recovery). ### 19.3 Composing Policies Retry and circuit breaker policies can be composed in a single `Execute` call: ```go retryPolicy := resilience.NewRetryPolicy[*Result](3, 100*time.Millisecond) cb := resilience.NewCircuitBreaker[*Result](5, 30*time.Second) result, err := resilience.Execute(func() (*Result, error) { return externalService.Call(ctx, req) }, retryPolicy, cb) ``` Failsafe-go applies the policies in order: the circuit breaker wraps the retry policy, so retries only happen when the circuit is closed or half-open. --- ## 20. Testing ### 20.1 Integration Test Database The `pkg/testutil/testdb` package provides a real PostgreSQL database for integration tests using testcontainers-go: ```go func TestProductRepository(t *testing.T) { db := testdb.SetupTestDB(t) // db is a *gorm.DB connected to a real PostgreSQL 16 container // Base migrations are already applied (UUID generation, timestamps, soft delete triggers) // Container is automatically cleaned up when the test ends via t.Cleanup } ``` `SetupTestDB(t)` spins up a PostgreSQL 16 Alpine container, applies the base DDL migrations (CITEXT extension, `updated_at` triggers, soft-delete protection, record version increment), and returns a connected `*gorm.DB`. The container is automatically destroyed when the test function exits. For running additional migrations: ```go db := testdb.SetupTestDB(t) testdb.RunMigrations(db) // Apply project-specific migrations ``` ### 20.2 Unit Testing Services Services depend on repository interfaces, making them easy to test with mock implementations: ```go func TestProductService_Create(t *testing.T) { mock := &mocks.MockProductRepository{ CreateFn: func(ctx context.Context, product *models.Product) error { product.ID = uuid.New() return nil }, } svc := services.NewProductService(mock) result, err := svc.Create(ctx, dtos.CreateProductRequest{Name: "Widget", Price: 9.99}) assert.NoError(t, err) assert.Equal(t, "Widget", result.Name) } ``` The scaffold generates mock implementations for each resource's repository and service interfaces in `testutil/mocks/`. ### 20.3 Testing Approach Gofasta projects use [testify](https://github.com/stretchr/testify) for assertions and test suites. The recommended testing strategy: - **Unit tests** for services: mock the repository interface, test business logic in isolation. - **Integration tests** for repositories: use `testdb.SetupTestDB(t)` with a real PostgreSQL container to verify actual database queries. - **Unit tests** for controllers: mock the service interface, test HTTP handler logic. - **End-to-end tests**: start the full HTTP server and make real HTTP requests against it. Run unit tests with `go test -short ./...` and integration tests with `go test ./...`. --- ## 21. Deployment ### 21.1 Generated Deployment Files Every Gofasta project includes deployment configurations out of the box, scoped to the deployment target gofasta natively supports — a Linux VPS running Docker or systemd, reached over SSH: ``` deployments/ ├── ci/ # GitHub Actions workflow templates (test, release, deploy-vps) ├── docker/ # Production compose file + dev Dockerfile with hot reload ├── nginx/ # Reverse proxy configuration └── systemd/ # Service unit and reference deploy script ``` Cloud-managed platforms (ECS, Cloud Run, App Service) are out of scope for the current release. Projects that require them are expected to import their own infrastructure-as-code manifests — `pkg/*` packages impose no runtime assumptions that prevent this. ### 21.2 Docker The generated `Dockerfile` uses a multi-stage build: ```dockerfile # Build stage FROM golang:1.25.0-alpine AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o server ./app/main # Production stage FROM alpine:3.20 RUN apk add --no-cache ca-certificates tzdata COPY --from=builder /app/server /server COPY --from=builder /app/config.yaml /config.yaml EXPOSE 8080 CMD ["/server", "serve"] ``` `compose.yaml` includes the application, PostgreSQL, Redis (cache profile), and asynqmon (queue profile) with health checks. The development loop is driven by a single command: ```bash gofasta dev # db + cache + queue + Air on the host, auto-on profiles gofasta dev --all-in-docker # everything inside Docker, including Air gofasta dev --no-services # Air only, bring your own database ``` `gofasta dev` orchestrates the full pipeline — preflight, service-start with health-wait, migration apply, optional seed, Air hot-reload — and exposes interactive controls (`r` to restart from scratch, `q` to quit, `h` for help) plus a `--dashboard` flag that mounts a live request inspector with trace waterfalls, EXPLAIN-on-click, N+1 detection, and HAR export. ### 21.3 VPS Deployment The `gofasta deploy` command deploys directly to a remote Linux server via SSH: ```bash gofasta deploy setup # Prepare server (Docker, nginx, directories) gofasta deploy # Deploy current version gofasta deploy status # Check deployment status gofasta deploy logs # Tail application logs gofasta deploy rollback # Roll back to previous release ``` Two deployment methods are supported: - **Docker** (default): builds a Docker image locally, transfers it via SSH (`docker save | ssh docker load` — no registry required), runs via Docker Compose on the server. - **Binary**: cross-compiles a Go binary, transfers it via SCP, manages it via systemd. Both methods use a Capistrano-style release-directory layout (`/opt//releases//` plus a `current` symlink atomically flipped after the health check passes), support zero-downtime rollback via `gofasta deploy rollback`, and optionally front the application with the generated nginx reverse-proxy config. ### 21.4 CI/CD GitHub Actions workflow templates ship under `deployments/ci/` as inert YAML — they do not run until the developer copies them into `.github/workflows/`. This opt-in pattern prevents a first push from triggering deploys against unset secrets. The templates provided: - **Test** — runs on push/PR with a PostgreSQL service container; uploads coverage to Codecov. - **Release** — on `v*` tag push, builds and pushes a Docker image to GHCR using the default `GITHUB_TOKEN`. - **Deploy (VPS)** — on push to `main` or manual dispatch, runs `gofasta deploy` against the configured VPS. Requires `DEPLOY_HOST`, `DEPLOY_SSH_KEY`, and `DEPLOY_PORT` repository secrets. --- ## 22. Adoption and Migration Path ### 22.1 Starting a New Project The fastest path: run `gofasta new myapp`, then start adding resources with `gofasta g scaffold`. The project compiles and runs immediately with a starter User resource, database migrations, authentication, health checks, Swagger docs, and Docker configuration. ### 22.2 Adopting the Library in an Existing Project The `pkg/*` packages can be imported individually into any existing Go project. There is no initialization ceremony and no required package ordering: ```bash go get github.com/gofastadev/gofasta/pkg/cache go get github.com/gofastadev/gofasta/pkg/resilience go get github.com/gofastadev/gofasta/pkg/middleware ``` Each package is self-contained. Importing `pkg/cache` does not pull in `pkg/mailer` or any other unrelated package. ### 22.3 Replacing a Default Package Every `pkg/*` import in a scaffolded project can be replaced independently: 1. Delete the import of the package you want to replace (e.g., `pkg/cache`). 2. `go get` your preferred alternative (e.g., `github.com/eko/gocache`). 3. Update the Wire provider to construct your alternative implementation. 4. Run `gofasta wire` to regenerate the DI container. No other files in the project need to change. The service layer depends on interfaces, not concrete types, so swapping the underlying implementation is a single-provider change. ### 22.4 Ejecting Entirely A scaffolded project has no runtime dependency on the `gofasta` CLI. The CLI is a development tool — the project builds and runs without it. To fully remove the `pkg/*` library dependencies: 1. Replace each `pkg/*` import with an alternative library or a local implementation. 2. Remove `github.com/gofastadev/gofasta` from `go.mod`. 3. The project is now a standalone Go application with zero gofasta dependencies. This is possible because every `pkg/*` package implements a standard pattern (interface + constructor) with no global state, no init functions, and no implicit coupling between packages. --- ## 23. Comparison with Existing Tools ### 23.1 Positioning Gofasta occupies a specific niche in the Go ecosystem: it sits between a bare HTTP router (which leaves every project-level decision to the developer) and a monolithic runtime wrapper (which dictates every project-level decision). It is a *toolkit* — a CLI that generates standard Go code plus a library of independently importable packages — and the developer is always in control of which pieces they use. The goal is to compress the repetitive parts of starting a backend project (wiring, CRUD boilerplate, deployment scaffolding) without introducing a runtime layer the developer's code has to live inside. Gofasta also occupies a second niche that no other Go toolkit currently targets as a first-class audience: **AI coding agents**. The design principles in §2.7 and the agent-ergonomics surface in §4.5 treat agents as peer developers of the project — every command accepts `--json`, every error carries a stable machine-parseable code, the scaffold ships an `AGENTS.md` briefing, and per-agent editor configuration (Claude Code, Cursor, OpenAI Codex, Aider, Windsurf) installs with one command (`gofasta ai `, e.g. `gofasta ai claude`). High-level commands (`verify`, `status`, `inspect`, `do`) collapse long multi-step procedures into single calls with structured output. This is what distinguishes Gofasta's positioning from every comparable tool in the matrix below: the matrix lists capabilities an application needs; the agent-native design decides *how an agent interacts with the toolchain to build and maintain that application*. The rest of this section evaluates feature breadth against other tools; the agent-native distinction cuts across all of them. ### 23.2 Comparison Matrix The following matrix compares Gofasta against the tools a Go backend developer is most likely to evaluate. The tools are grouped by category: HTTP routers, project scaffolders, backend development platforms, microservice toolkits, and design-first generators. #### vs. Routers and Scaffolders | Capability | mux/chi/Gin/Echo | go-blueprint | Buffalo | Gofasta | |-----------|-------------|-------------|---------|---------| | HTTP routing | Yes | Yes | Yes | Yes | | Project scaffolding | No | Yes | Yes | Yes | | Resource code generation | No | No | Yes | Yes | | Compile-time DI | No | No | No | Yes (Wire) | | Database migrations | No | No | Yes (Pop) | Yes (SQL files) | | Multi-database support | N/A | Yes | Yes (Pop) | Yes (5 drivers) | | GraphQL support | No | No | No | Yes (opt-in, gqlgen) | | Background jobs | No | No | Yes | Yes | | Email / notifications | No | No | No | Yes | | WebSocket support | No | No | No | Yes | | File storage | No | No | No | Yes (local, S3) | | Observability | No | No | No | Yes (Prometheus, OpenTelemetry) | | Security middleware | Partial | No | Partial | Yes (headers, CORS, rate limit, auth) | | Deployment manifests | No | No | No | Yes (Docker, CI/CD, VPS) | | Standard `go build` / `go test` | Yes | Yes | Yes | Yes | | Runtime wrapper required | Yes | No | Yes | No | #### vs. Backend Platforms and Full Toolkits | Capability | Encore | go-zero | GoFrame | Gofasta | |-----------|--------|---------|---------|---------| | HTTP routing | Yes (annotations) | Yes | Yes | Yes | | Project scaffolding | Yes | Yes (`goctl`) | Yes (`gf`) | Yes | | Resource code generation | No | Yes (from `.api` files) | Yes (from DB schemas) | Yes (from CLI args) | | Compile-time DI | No | No | No | Yes (Wire) | | Database migrations | Yes (auto-provisioned) | No | Yes | Yes (SQL files) | | Multi-database support | No (PostgreSQL only) | Yes (MySQL, PostgreSQL, MongoDB) | Yes (6 drivers) | Yes (5 drivers) | | GraphQL support | No | No | No | Yes (opt-in, gqlgen) | | Background jobs | Yes (cron) | No | Yes | Yes | | Pub/Sub | Yes (cloud-agnostic) | No | No | No (roadmap) | | Email / notifications | No | No | No | Yes (SMTP, SendGrid, Brevo, SMS, Slack) | | WebSocket support | No | No | Yes | Yes | | File storage | Yes (S3/GCS) | No | No | Yes (local, S3) | | Internationalization | No | No | Yes | Yes | | Feature flags | No | No | No | Yes | | Resilience patterns | No | Yes (circuit breaker, load shedding) | No | Yes (retry, circuit breaker) | | Observability | Yes (auto-instrumented) | Yes (OpenTelemetry) | Yes | Yes (Prometheus, OpenTelemetry) | | Service discovery | Yes (automatic) | Yes (etcd, Consul, K8s) | Yes (etcd, ZooKeeper) | No | | Security middleware | Partial (auth handler) | Yes (JWT) | Yes | Yes (headers, CORS, rate limit, auth) | | Health checks | No | No | No | Yes | | Test utilities | Yes (auto test DB) | No | No | Yes (testcontainers) | | Deployment manifests | No (cloud-managed) | No | No | Yes (Docker, CI/CD, VPS) | | VPS deployment command | No | No | No | Yes | | Auto infrastructure provisioning | Yes | No | No | No | | Local dev dashboard | Yes | No | No | Yes | | Standard `go build` / `go test` | No (custom compiler) | Yes | Yes | Yes | | `net/http` middleware compatible | No | No (custom middleware) | Partial | Yes | | Runtime wrapper required | Yes (custom runtime) | Yes | Yes | No | | Custom syntax or annotations | Yes (`//encore:api`) | Yes (`.api` DSL) | No | No | | API definition format | Annotations in Go | Proprietary `.api` files | Standard Go | Standard Go | | Free and open source | Partial (cloud is paid) | Yes | Yes | Yes | #### vs. Microservice Toolkits and Design-First Generators | Capability | go-kit | Kratos | Goa | Gofasta | |-----------|--------|--------|-----|---------| | HTTP routing | Yes (transport layer) | Yes (HTTP + gRPC) | Yes (generated) | Yes | | Project scaffolding | No | Yes (from protobuf) | Yes (from Go DSL) | Yes | | Resource code generation | No | Yes (from `.proto`) | Yes (from DSL) | Yes (from CLI args) | | Compile-time DI | No | Yes (Wire) | No | Yes (Wire) | | API definition format | Go interfaces | Protocol Buffers | Go DSL | Standard Go | | gRPC support | Yes | Yes (first-class) | Yes (generated) | No | | Multi-database support | N/A | N/A | N/A | Yes (5 drivers) | | Database migrations | No | No | No | Yes (SQL files) | | Background jobs | No | No | No | Yes | | Email / notifications | No | No | No | Yes | | Resilience patterns | Yes (circuit breaker) | Yes | No | Yes (retry, circuit breaker) | | Service discovery | Yes (etcd, Consul, DNS) | Yes (etcd, Consul, K8s) | No | No | | Observability | Yes (OpenTracing) | Yes (OpenTelemetry) | No | Yes (Prometheus, OpenTelemetry) | | OpenAPI generation | No | No | Yes (from DSL) | Yes (from code annotations) | | Standard `go build` / `go test` | Yes | Yes | Yes | Yes | | Runtime wrapper required | No (pattern library) | Yes | No (generated code) | No | | Deployment manifests | No | No | No | Yes (Docker, CI/CD, VPS) | | Actively maintained | Maintenance mode | Yes | Yes | Yes | ### 23.3 Key Differentiators **vs. every other Go toolkit (on agent-native support):** None of the tools in the matrix above treats AI coding agents as a first-class audience. Routers expose no structured command surface at all. Scaffolders (go-blueprint, Buffalo) emit projects with no embedded briefing for agents, no `--json` on their generators, and unstructured error strings that agents have to regex to understand. Platform toolkits (Encore, go-zero, GoFrame, Kratos) are built around proprietary runtimes, DSLs, or annotation systems that existing agents do not have stable priors for — an agent working inside these toolchains is guessing about non-Go syntax instead of applying its considerable knowledge of standard Go. Gofasta inverts this: it generates standard Go code (so agents can rely on their existing Go knowledge), ships a scaffolded `AGENTS.md` that briefs any agent on project conventions, installs editor-specific rule files for Claude Code / Cursor / Codex / Aider / Windsurf via `gofasta ai ` (e.g. `gofasta ai claude`), adds a universal `--json` flag across every command, returns structured errors with stable codes (see §4.5), and exposes higher-level commands (`verify`, `status`, `inspect`, `do`) that collapse multi-step procedures into one call. The result: an agent opening a fresh Gofasta project has a machine-readable entry point (`AGENTS.md` + `llms.txt`), a machine-readable operational surface (every command), and machine-readable failure modes (error codes). This is a category distinction, not a feature comparison. **vs. HTTP routers (chi, gorilla/mux, Gin, Echo):** These are HTTP routers. Gofasta uses chi (which builds on `net/http`) as its default router but adds project structure, code generation, database management, authentication, background processing, and utility packages. The router itself is a swappable default — a developer can replace it with gorilla/mux, stdlib `http.ServeMux`, or any other `http.Handler`-compatible router without touching `pkg/*` code. A developer using any router still needs to set all of the surrounding structure up manually. **vs. go-blueprint:** go-blueprint generates project scaffolding with a choice of router and database driver. Gofasta goes further with resource-level code generation (`gofasta g scaffold`), a library of production packages, and generated deployment manifests. go-blueprint generates the starting point; Gofasta generates the starting point and the ongoing CRUD boilerplate. **vs. Buffalo:** Buffalo bundled a full set of components — its own ORM (Pop), template engine (Plush), and asset pipeline — as a single runtime layer a project had to buy into all at once. Gofasta takes the opposite approach: it generates standalone Go code and lets the developer import only the `pkg/*` packages they actually need. There is no monolithic layer to maintain, and if any single package stops fitting a project's needs it can be replaced in isolation without touching the rest of the code. **vs. Encore:** Encore is a backend development platform that provides automatic infrastructure provisioning, zero-config distributed tracing, and cloud deployment via a custom Go compiler fork. It trades standard toolchain compatibility for reduced boilerplate — projects cannot use `go build` or `go test` directly, must use Encore's proprietary `//encore:api` annotations for endpoint definition, and are limited to PostgreSQL as the only supported database. Encore's infrastructure declarations use proprietary APIs (`encore.dev/storage/sqldb`, `encore.dev/pubsub`, `encore.dev/storage/cache`) that create a compile-time dependency on the Encore toolchain; migrating away requires rewriting all API endpoint definitions, replacing infrastructure declarations with direct client libraries, and setting up independent observability. Gofasta takes the opposite approach: it generates standard Go code that compiles with `go build`, tests with `go test`, and deploys with any CI/CD pipeline. Generated projects use chi with standard `net/http` middleware — the entire Go middleware ecosystem is compatible. Gofasta supports five database drivers, and every `pkg/*` import is a standard Go dependency that can be individually replaced without touching the rest of the project. The tradeoff is explicit: Encore automates more at the cost of toolchain control and vendor dependency; Gofasta automates the generation step and then returns full control to the developer. **vs. go-zero:** go-zero is a microservices toolkit with a powerful CLI (`goctl`) that generates complete services from `.api` definition files or Protocol Buffers. It includes built-in resilience patterns (circuit breaking, adaptive load shedding), service discovery, and distributed tracing — capabilities proven at massive scale at Tal Education. However, go-zero requires a proprietary `.api` definition format that is not OpenAPI, generates code tightly coupled to the go-zero runtime, and uses custom middleware signatures incompatible with standard `net/http` middleware. Its project structure is opinionated and difficult to deviate from. Gofasta generates standard Go code from CLI arguments (no proprietary definition file), uses `net/http`-compatible middleware, and produces projects that compile without any toolkit-specific runtime. go-zero is purpose-built for high-scale microservice architectures with service discovery and inter-service communication; Gofasta targets the broader case of production-ready backend services — monoliths, modular monoliths, or services that may grow into microservices later — with a wider feature set (email, notifications, file storage, i18n, feature flags, deployment manifests) and no commitment to a specific architectural pattern. **vs. GoFrame:** GoFrame is a comprehensive runtime toolkit originating from the Chinese Go community. It provides an HTTP server, ORM (`gdb`), configuration, logging, caching, i18n, and a CLI (`gf`) that can reverse-engineer database schemas into Go code. Its feature breadth is comparable to Gofasta's, and it supports six database drivers. The key difference is architectural: GoFrame is a runtime toolkit — the HTTP server, ORM, config loader, and logger are all GoFrame-specific APIs that your application imports and runs inside. Replacing one component (swapping the ORM, or using a different config loader) means fighting the integration between modules. Gofasta generates standalone Go code that uses well-known community libraries (chi, GORM, slog) rather than toolkit-specific equivalents. Each `pkg/*` package can be replaced individually because it builds on standard Go interfaces — swapping `pkg/cache` does not require changing `pkg/config` or `pkg/middleware`. GoFrame's English documentation is significantly weaker than its Chinese documentation, which limits adoption outside Chinese-speaking teams. **vs. Kratos:** Kratos is a microservices toolkit from Bilibili that shares several design choices with Gofasta: both use Google Wire for compile-time dependency injection, and both enforce layered architecture with clean separation between transport, service, and data layers. The fundamental difference is the API definition workflow. Kratos requires Protocol Buffers for service definitions and generates both HTTP and gRPC transport from `.proto` files — this is powerful for teams that need dual-protocol support but adds significant ceremony for REST-only projects. Gofasta defines APIs in standard Go code with explicit route registration, which requires no schema language and no code generation step for the HTTP layer itself. Kratos provides first-class service discovery (etcd, Consul, Kubernetes) and is built for distributed microservice deployments; Gofasta provides first-class deployment automation (Docker, Kubernetes manifests, VPS deployment) and a wider set of application-level packages (email, notifications, file storage, i18n, feature flags) for teams building complete backend services rather than individual microservice components. **vs. Goa:** Goa takes a design-first approach: developers describe their API using a Go DSL, and the `goa` CLI generates server handlers, client packages, and OpenAPI documentation from that single source of truth. This ensures documentation and code never drift apart — a genuine advantage for API-first organizations. Goa generates both HTTP and gRPC transport from one design, and its generated code is clean and idiomatic. However, Goa's scope is limited to the API layer. It does not provide database access, migrations, authentication, caching, background jobs, email, file storage, or deployment manifests — the developer must assemble these independently. Gofasta covers the full backend stack, from database migrations and GORM repositories through authentication and background processing to deployment automation. Goa's DSL also has a significant learning curve; Gofasta uses standard Go code for everything, so the developer's existing Go knowledge applies directly. **vs. go-kit:** go-kit is a pattern library for building microservices, providing a three-layer architecture (transport → endpoint → service) with pluggable middleware for logging, metrics, rate limiting, and circuit breaking. It is not a code generator or a scaffolding tool — it is a set of Go packages that embody microservice best practices. go-kit's flexibility comes at the cost of extreme verbosity: even a simple service requires many files and significant boilerplate for transport encoding, endpoint wiring, and middleware composition. go-kit is in maintenance mode — the maintainer has stated it is "done" and no major features are planned. Gofasta eliminates the boilerplate that go-kit leaves to the developer while preserving the same architectural principles (explicit wiring, interface-based design, middleware composition). Where go-kit provides the patterns, Gofasta generates the code that implements them. **vs. Ent:** Ent is a graph-based ORM and code generation tool from Meta that generates a complete type-safe data access layer from schema definitions in Go. It is not a direct competitor — it solves a different layer of the stack (data access rather than full backend tooling) — but teams evaluating Gofasta's default choice of GORM will often consider Ent as an alternative. Ent's strengths are compile-time query safety (no `interface{}` in query results), graph-based relationship traversal, first-class GraphQL integration via `entgql`, and a schema-level privacy layer for multi-tenant authorization. Gofasta uses GORM as its default ORM because GORM has broader database driver support (five drivers vs. Ent's three), a larger community, and a lower learning curve for developers familiar with active-record patterns. Ent could be used in a Gofasta project by replacing the generated GORM repository implementations with Ent clients — the service and controller layers would remain unchanged because they depend on repository interfaces, not on GORM directly. This is the opt-out-defaults design in practice: the ORM is a swappable default, not a locked-in requirement. --- ## 24. Roadmap Gofasta is under active development. The roadmap is organized by impact on production readiness for SaaS, enterprise, and platform workloads. ### Near-term — Strengthening the Core - **Multi-tenancy** (`pkg/tenant`) — Row-level tenant isolation with middleware (extracts tenant from JWT/subdomain/header), GORM scopes (auto-filters every query by `tenant_id`), and tenant-aware cache key prefixing. This is the single highest-value addition for SaaS: every multi-customer product needs it, cross-tenant data leaks are catastrophic, and the plumbing is tedious to build correctly from scratch. - **Audit logging** (`pkg/audit`) — GORM callback-based, append-only audit trail recording entity, field, old value, new value, actor, and timestamp. Required by regulated industries (fintech, healthcare, govtech) and expected by enterprise buyers. - **Outgoing webhooks** (`pkg/webhook`) — Event registration, HMAC-signed HTTP delivery with exponential backoff retries, delivery logging, and dead-letter handling. Every SaaS eventually needs to notify external systems, and the reliability logic (idempotency, timeout handling, retry budgets) is genuinely hard to get right. - **OAuth2 provider integration** — Extend `pkg/auth` with OAuth2 flows for Google, GitHub, and Microsoft. "Sign in with Google" is table stakes for any consumer-facing product; enterprise SSO (via OpenID Connect) is required for B2B sales. - **Tenant-aware rate limiting** — Extend `pkg/middleware` rate limiter with per-tenant keys and configurable tier limits (e.g., free plan: 100 req/min, pro plan: 1000 req/min). Prevents noisy-neighbor problems in multi-tenant deployments. - **Pub/Sub** (`pkg/pubsub`) — Typed publish/subscribe with two backends: an in-process dispatcher for monoliths and a Redis Pub/Sub implementation for distributed deployments. Defines a `Publisher` and `Subscriber` interface so developers can swap backends or replace the package entirely with a purpose-built library like Watermill. Covers the 80% of event-driven use cases (send welcome email after signup, update search index after edit, log analytics event after purchase) without introducing Kafka or NATS. - **Generated observability instrumentation** — Extend the scaffold generators to emit OpenTelemetry span creation at each layer boundary (controller, service, repository) in generated code. Since the instrumentation is generated Go code the developer owns, it can be customized, extended, or removed. This delivers zero-config distributed tracing for scaffolded resources without requiring a custom compiler or runtime — the tracing is visible in the source code, not injected at build time. ### Medium-term — Developer Experience and Tooling - **Expand `pkg/resilience`** — Add bulkhead (concurrent request limits) and timeout patterns alongside existing retry and circuit breaker. - **Expand `pkg/testutil`** — HTTP test client, response assertion helpers (`AssertStatus`, `AssertJSON`), fixture loading from JSON/YAML files, and database assertion helpers (`AssertDBHas`, `AssertDBMissing`). Additionally, scaffold generators should produce a `_test.go` file per resource with the test database wired in via testcontainers, so every generated resource ships with a working integration test from day one. - **Expand `pkg/observability`** — Additional Prometheus metrics: `http_response_size_bytes` histogram, `db_query_duration_seconds` histogram, `cache_hits_total` / `cache_misses_total` counters, `queue_jobs_processed_total` counter. - **`gofasta g middleware` generator** — Scaffold custom middleware with the correct `func(http.Handler) http.Handler` signature, logging, and test file. - **Interactive project creation wizard** — Guide developers through database driver, auth strategy, and optional feature selection when running `gofasta new`. - **Plugin system** — Allow community-contributed generators to be installed and invoked via the CLI. - **Service architecture diagram generation** — `gofasta diagram` command that parses route files, the DI container, and model relationships to generate a Mermaid or DOT architecture diagram. Output as SVG, Markdown, or embeddable in Swagger UI. Purely a CLI analysis tool with zero runtime cost. - **Machine-readable route output** — Extend `gofasta routes` with a `--json` flag for CI/CD integration, API gateway configuration, and tooling that consumes route definitions programmatically. - **Secrets management** — `gofasta secret set ` command that encrypts secrets locally (`.env.enc`) for safe version control, with decryption at startup via a master key from the environment. Integration with cloud secret managers (AWS Secrets Manager, GCP Secret Manager) as optional deployment targets. The CLI manages the workflow; the project reads secrets through standard environment variables at runtime — no proprietary secrets API in application code. - **PaaS adapter generators** (`gofasta deploy init `) — Generate the configuration file each popular self-hosted VPS PaaS expects so teams already running one can adopt gofasta without rebuilding their deploy pipeline: `app.json` + `CHECKS` for Dokku, `captain-definition` for CapRover, a Coolify-flavored Compose file with domain labels, a Dokploy-flavored Compose file with Swarm deploy keys, and `config/deploy.yml` + `.kamal/secrets.example` for Kamal. Each adapter writes files to `deployments//` (or the path the tool requires at repo root) and a local `README.md` listing the exact follow-up commands to run with that PaaS's native tooling. Gofasta does not wrap these platforms' CLIs or call their APIs — the generated files are standard, ownable config the developer commits and deploys with the PaaS itself. This makes `gofasta deploy` expandable to the broader open-source VPS ecosystem without turning gofasta into a PaaS competitor; the direct-SSH `gofasta deploy` path remains the default, zero-dependency option. ### Long-term — Scale and Enterprise - **Admin dashboard generation** — Generate a web-based admin panel for managing resources, viewing audit logs, and monitoring system health. - **OpenAPI-first code generation** — Generate Go handlers, DTOs, and route registration from an existing OpenAPI specification (spec-first, complementing the current code-first Swagger generation). - **Multi-service project support** — Monorepo layout with shared packages, per-service scaffolding, and cross-service type generation. - **IDE extensions** — VS Code and GoLand extensions for running generators, viewing routes, and navigating the project structure from the editor. - **MongoDB support** — First-class document-database driver as a peer to the existing SQL drivers. Because MongoDB is not a SQL dialect, this is a larger shift than adding another GORM driver: `pkg/config` gains a parallel `SetupMongo()` alongside `SetupDB()`, the scaffold generators emit BSON-tagged structs and collection/index setup in place of GORM models and SQL migrations, and `gofasta migrate` becomes an index-and-collection setup runner for Mongo projects. The intended client is the official `go.mongodb.org/mongo-driver/v2` (no ODM) so generated code stays plain, explicit, and swappable. Targeted at teams whose domain is a natural fit for document storage (flexible schemas, nested documents, event/log stores) without forcing them off gofasta's scaffolding and generator ergonomics. - **Kubernetes manifests** — Generated `deployment.yaml`, `service.yaml`, and `ingress.yaml` scoped to the project's configured database and cache backends, as an optional complement to the existing VPS deploy flow for teams running Kubernetes clusters. - **Cloud deployment manifests** — `gofasta deploy aws` and `gofasta deploy gcp` commands that generate Terraform or CDK definitions for ECS/Fargate, Cloud Run, or equivalent managed container services. Following the "generate then own" principle, these commands would produce infrastructure-as-code files the developer commits and manages — not a managed cloud platform. ### Explicitly out of scope - **Custom compiler or runtime fork** — Gofasta will never require a patched Go compiler, a custom build command, or a proprietary runtime. Projects compile with `go build`, test with `go test`, and work with every standard Go tool. This is a permanent design constraint. - **Managed cloud platform** — Gofasta is a toolkit, not a hosting service. Any future deployment automation will generate configuration files the developer owns and deploys to their own infrastructure. There is no Gofasta-hosted cloud, no per-seat pricing, and no telemetry phone-home. - **CQRS / Event Sourcing** — These are architectural patterns, not toolkit concerns. They are deeply domain-specific, and a generic implementation is either too abstract to be useful or too opinionated to be swappable. Projects that need CQRS should use [Watermill](https://github.com/ThreeDotsLabs/watermill) directly. A toolkit that provides CQRS pushes users toward a specific architecture, which conflicts with gofasta's identity as composable, opt-out-default packages. - **Message broker abstraction** — Abstracting over Kafka, NATS, and RabbitMQ is Watermill's job. Gofasta's `pkg/pubsub` will provide in-process and Redis-backed pub/sub; distributed messaging beyond Redis is left to purpose-built libraries. - **SAML implementation** — SAML is an extremely complex protocol. Enterprise SSO is better delegated to specialized providers (WorkOS, Auth0, Clerk). Gofasta will provide integration guides rather than a direct SAML implementation. - **Billing SDK wrapper** — Billing is deeply product-specific. Wrapping Stripe's SDK adds indirection without value. This will be addressed as a documentation guide showing the integration pattern, not as a package. The roadmap is driven by community feedback. Feature requests and contributions are welcome via GitHub Issues and Pull Requests. --- ## 25. Contributing Gofasta is open source under the [MIT License](https://opensource.org/licenses/MIT). **Repositories:** - **Library:** `github.com/gofastadev/gofasta` — imported by your projects - **CLI tool:** `github.com/gofastadev/cli` — installed via `go install github.com/gofastadev/cli/cmd/gofasta@latest` - **Documentation website:** `github.com/gofastadev/website` **How to contribute:** 1. Open an issue to discuss the change before starting work 2. Fork the repository and create a feature branch 3. Write tests for new functionality 4. Submit a pull request with a clear description **Community:** - GitHub Issues for bug reports and feature requests - GitHub Discussions for questions and ideas --- ## References 1. Go Developer Survey 2024 Results — The Go Blog, https://go.dev/blog/survey2024-h2-results 2. Google Wire — Compile-time Dependency Injection for Go, https://github.com/google/wire 3. GORM — The fantastic ORM library for Go, https://gorm.io 4. go-chi/chi — Lightweight, idiomatic HTTP router for Go, https://github.com/go-chi/chi 5. gqlgen — Go library for building GraphQL servers, https://gqlgen.com 6. go-playground/validator — Struct and field validation for Go, https://github.com/go-playground/validator 7. hibiken/asynq — Distributed task queue for Go, https://github.com/hibiken/asynq 8. golang-migrate — Database migrations for Go, https://github.com/golang-migrate/migrate 9. Casbin — Authorization library, https://casbin.org 10. OpenTelemetry Go — Observability framework, https://opentelemetry.io/docs/languages/go/ 11. Air — Live reload for Go apps, https://github.com/air-verse/air 12. Cobra — CLI library for Go, https://github.com/spf13/cobra 13. swaggo/swag — Swagger documentation generator for Go, https://github.com/swaggo/swag 14. failsafe-go — Fault tolerance for Go, https://github.com/failsafe-go/failsafe-go 15. OpenFeature — Vendor-neutral feature flagging specification (CNCF), https://openfeature.dev 16. testcontainers-go — Docker containers for integration testing, https://github.com/testcontainers/testcontainers-go 17. unrolled/secure — HTTP security headers for Go, https://github.com/unrolled/secure --- *This document describes Gofasta as of version 4.0.0. For the latest documentation, visit [gofasta.dev](https://gofasta.dev).* *Copyright 2025–2026 Gofasta Authors. Released under the MIT License.* ## Related ---