One cohesive runtime. Explicit dependency wiring. Local-first drivers. Production-ready primitives for everything an application needs.
Start
forj new renders a complete Go project - the components you choose, nothing more. forj dev brings it alive. Built for Go developers shipping services, workers, CLIs, and full products.
forj new, added later as the App grows.$ forj newProject » photodropStarter kit » VueComponents » CLI, Docker, Mail, Auth, OAuth, Web API, Web UI, Metrics, Observability, Grafana, Database (MySQL), Scheduler, Jobs✔ Project render complete (created: 736, skipped: 0)$ cd photodrop && forj dev✔ 4/4 go build Built app in 2s· Running pre-dev setup[+] up 12/12✔ migrations complete (7)✔ Dev ready → App: http://localhost:3000 → Lighthouse: http://localhost:3000/lighthouse → Swagger: http://localhost:3000/swagger → Mailpit (inbox): http://localhost:8025 → Grafana (admin / admin): http://localhost:1300123:51:32.257 HTTP Starting HTTP server → addr=0.0.0.0:300023:51:32.256 Jobs Queue worker started → driver=redis · workers=3023:51:32.256 Scheduler Scheduler started$ curl localhost:3000/-/health{"status":"ok"}Infrastructure
Services depend on contracts. Configuration selects the backend. When infrastructure changes, your code does not.
Your service · the same file in every environment
// internal/photos/service.go
type Service struct {
disk storage.Storage
}
func NewService(disk storage.Storage) *Service {
return &Service{disk: disk}
}
func (s *Service) Store(ctx context.Context, in UploadInput) (Photo, error) {
path := photoPath(in)
if err := s.disk.WithContext(ctx).Put(path, in.Body); err != nil {
return Photo{}, fmt.Errorf("store photo: %w", err)
}
return Photo{Path: path}, nil
}Your environment · the only thing that changes
Every primitive works this way. Cache, storage, queue, events, database, mail - each runs on in-process or local drivers in a standalone binary, then swaps to real infrastructure in production. No code changes.
The stack
The foundation teams rebuild in every service - already built, already coherent. One runtime, one configuration story, one operational surface.
Thin controllers, route groups, and middleware over the web abstraction. Health, readiness, and Swagger included.
First-class CLI entry points with injected dependencies, not shell scripts around your binary.
Named, durable background work with typed payloads, retries, timeouts, and worker processes.
Typed facts with local-first fan-out. In-process today, NATS or Kafka when you need it.
Declarative recurring work with stable names, overlap control, and operator visibility.
Generated connections, named resources, per-driver migrations, and a built-in db shell.
Named accessors with explicit TTLs, locks, counters, and rate limits behind one contract.
Named disks for files and blobs. Local in development, object storage in production.
Fluent message composition with pluggable delivery: SMTP, Resend, Postmark, SES, and more.
Server-authoritative sessions, HttpOnly cookies, refresh rotation, reset and verification flows.
Prometheus-compatible metrics with bounded labels, plus execution records for every runtime.
A first-party operator view over routes, inspects, schedules, queues, cache, and storage.
Generators
Make commands create the file and the wiring: providers, routes, schedules, subscriptions. No annotations, no reflection container, no hidden registration.
--remove deletes the file and undoes the wiring the generator manages. --dry-run shows you first.$ forj make:controller photos
$ forj make:job photos:thumbnail --queue media
$ forj make:schedule photos:digest --every 24h
$ forj make:subscriber photos:photo-uploaded
internal/photos/
├── controller.go # HTTP entry point
├── thumbnail_job.go # queue entry point
├── digest_schedule.go # scheduler entry point
├── photo_uploaded_subscriber.go # event entry point
└── service.go # your workflow codeOperations
One binary hosts everything locally, or splits into explicit processes when production needs to scale. Deployment is a file you copy. Your code never knows the difference.
Standalone
$ forj app # → ./bin/app run
one process: http + jobs + schedulerDistributed
$ forj api # → ./bin/app api
$ forj worker --queue media
$ forj schedulerforj route:list is the source of truth for the HTTP surface, not a scroll through startup logs.
/-/health and /-/ready ship generated, with token-gated structured diagnostics.
Prometheus-compatible series with bounded labels: route patterns, queue names, job names, schedule names.
Execution records for every request, job, schedule run, and command, browsable in a first-party operator UI.
Scale
Most products live their whole life as a single App - and that is the golden path. When a Project outgrows it, one command adds another runnable app in the same repo: shared code, separate wiring, separate binaries, separate scaling.
internal/. No RPC ceremony, no duplicated plumbing.billing without touching the rest.$ forj make:app billing
$ forj billing route:list
$ forj dev # orchestrates app + billing
photodrop/
├── cmd/app/ # default app
├── cmd/billing/ # named app
├── app/billing/ # its routes, commands, wiring
└── internal/ # shared behavior, one module
$ forj api # → ./bin/app api
$ forj billing worker # → ./bin/billing workerTested foundation
A driver should not only compile - it should prove its behavior against the backend it claims to support.
Driver suites run against Redis, Postgres, MySQL, NATS, Kafka, MinIO, SQS, and more through testcontainers and emulators. These numbers are generated from the repositories, not written by hand: see how they are counted →
Verified scenarios
Seven scenarios grow one small App from a single route to a fully observable system. Each ships only after it executes against the current templates - the tutorial cannot drift from the framework.
Fit
A framework should say who it serves and who it does not. Here is the honest version.
A rendered App is ordinary Go: explicit wiring, readable files, standard modules. Stop running forj tomorrow and your application still builds, tests, and deploys. The framework earns its place in your workflow, not in your lock-in.
I got tired of rebuilding the same foundation every time I started a Go service. I did not want magic, and I did not want a framework that fights the language. So GoForj is built from things that still feel like Go: explicit wiring, compiled binaries, small interfaces, readable control flow. It is the stack I always wanted.
For your next application
Runtime orchestration, explicit wiring, local-first drivers - and an optional Vue starter kit with auth, settings, and dashboard screens already shaped.
Quickstart →For your existing services
Queue, events, cache, storage, web, mail, scheduler, and more - standalone Go packages with their own APIs, drivers, and test suites.
Browse libraries →$ go install github.com/goforj/goforj/cmd/forj@latest
$ forj new