fiber-skeleton
July 8, 2026 · View on GitHub
Unlike chi-skeleton, this skeleton uses the incredibly fast Fiber framework, which is generally recommended.
Features
- Fiber v3 with sensible server hardening (timeouts, body/header limits) and a global middleware stack
- Dependency injection via samber/do: lazy generic container, reverse-order shutdown of resources, health checks feeding
/readyz— and no code generation - Strongly-typed configuration (koanf) with
APP_*environment overrides, validated at startup - Graceful shutdown on SIGINT/SIGTERM (drains requests and cron jobs) and zero-downtime upgrades on SIGHUP (graceful)
- Structured logging with slog on a rotating file writer (logrotate), stdout, or both
- Request binding + validation (validator) with a boolean rule DSL, i18n messages and custom database rules (gormrules)
- Scheduled jobs (cron) with panic recovery, overlap skipping and a dedicated
internal/jobpackage - GORM + SQLite (swap in MySQL/PostgreSQL freely) with versioned migrations (migrate): schema as Go code, automatic rollbacks,
migrate status/rollbackcommands - Code generator (
cmd/gen) that scaffolds a full CRUD module in one command - OpenAPI 3.1 docs from validate tags — schemas and constraints generated from the validator rules, served with a Scalar UI at
/docs - Tests included: handler tests against a mocked repo (mockery), validate-tag linting, SQL rule tests
Quick start
Requires Go 1.25+.
git clone https://github.com/libtnb/fiber-skeleton my-app && cd my-app
make init # copies config/config.example.yml to config/config.yml
make run # or `make dev` for hot reload via air
The API listens on :3000 by default: curl localhost:3000/users.
Design
- The cmd directory stores the entry file of the application, one file for each application
- The config directory stores configuration files, which can have multiple configuration files
- The internal directory stores various codes of the application
- The mocks directory stores the generated mock code for testing
- The pkg directory stores some packages that can be reused by the application
- The storage directory stores files generated when the application is running
- The web directory stores the front-end code of the application
- go.mod and go.sum are used to manage dependencies
The internal directory follows the three-layer design of Kratos:
- biz holds domain models, repository interfaces and usecases — transport-independent business logic
- data implements the repositories against the database
- service adapts HTTP: binds/validates requests, delegates to usecases, shapes responses
Because usecases are transport-independent, the HTTP handlers, the CLI commands (internal/command) and the cron jobs (internal/job) all share the same business logic instead of each talking to the database on their own.
Wiring follows a contribution model: every package exposes a Package list of lazy providers, and transports (routes, CLI commands, jobs) are registered under naming conventions (routes:*, commands:*, jobs:*) that assemblers collect at startup — adding a module never touches shared files beyond one line per Package list.
Configuration
config/config.yml is loaded first (override the path with APP_CONFIG), then any APP_* environment variable wins over the file. A double underscore separates nesting levels:
APP_HTTP__ADDRESS=:8080 APP_LOG__OUTPUT=stdout ./app
Configuration is parsed into a struct and validated at startup — a missing key or a bad value fails fast instead of panicking mid-request.
Scheduled jobs
Add jobs in internal/job: one JobFn contribution per job, registered with one do.LazyNamed line in the package's Package list. Specs support an optional seconds field, @every 30s descriptors and per-entry timezones. Jobs receive a context.Context that is cancelled on shutdown; panics are recovered and overlapping runs are skipped.
Code generation
make gen name=article # or: go run ./cmd/gen article
generates the biz entity + repo interface, data repository, service handlers, route contribution, request structs and a migration for a new module, then prints the remaining wiring: one line per Package list.
Development
make help # list all targets
make generate # regenerate mocks after changing interfaces
make lint # golangci-lint
make test # go test -race with coverage
make build # static binaries in bin/ with the version injected
A Dockerfile is included; mount config/ and storage/ when running.
OpenAPI documentation
Every documented endpoint declares Request/Response samples in its route
contribution; schemas, parameters and constraints are generated from the very
same validate tags that enforce them (validator/contrib/openapi) — min:3 && max:255 becomes minLength/maxLength, in:a,b becomes an enum, and the two can never drift apart. With http.docs: true the app serves the OpenAPI 3.1 document at /openapi.json and a Scalar UI at /docs.
Observability
/healthz(liveness) and/readyz(readiness, pings the DB) are wired for containers and load balancers; the Dockerfile ships a matchingHEALTHCHECK.- Access logs and application logs share one slog logger, one format and one
request_id, so a request can be traced across both. - Set
http.debug_address(e.g.127.0.0.1:6060) to servenet/http/pprofandexpvaron a separate private port — profiling in production without exposing it on the API port. - Errors returned by handlers and by the framework itself (404, 405, 413, panics) all leave through one handler in the same JSON shape; 5xx details go to the log, not the client.
Serving a frontend
Put your built frontend under web/ and serve it with fiber's static middleware in bootstrap/http.go:
r.Get("/*", static.New("./web/dist"))
Graceful lifecycle
| Signal | Behavior |
|---|---|
| SIGINT / SIGTERM | stop accepting connections, drain in-flight requests and cron jobs (30s cap), close DB and log writer |
| SIGHUP (non-Windows) | zero-downtime binary upgrade via graceful |
Credits
The development of this project refers to the following projects, I would like to express my gratitude: