Full-stack, idiomatic Go,
without the boilerplate.
Fabrik is a full-stack framework that generates the wiring you'd normally hand-write - routing, dependency injection, a runnable server - as idiomatic Go. Framework speed, without the magic.
Prefer the Go toolchain? Install from source with go install →
How Fabrik works
Annotate your Go code with a few //fabrik:* directives. fabrik wire
scans them and generates main.gen.go: all the wiring your app needs,
as idiomatic Go code, reviewable, without the implicit magic.
package web
//fabrik:http GET /hello/{name}
func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.PathValue("name"))
}
// Code generated by fabrik. DO NOT EDIT.
r := router.New()
r.Method("GET", "/hello/{name}", web.Hello)
server := httpserver.New(r, nil)
package shared
//fabrik:cli:command
func Serve(ctx cli.Context, server *httpserver.Server) error {
return server.Run(ctx)
}
//fabrik:cli:command
func Migrate(ctx cli.Context, db *sql.DB, src migrations.Sources) error {
return src.Migrate(ctx, db, migrations.DialectSQLite)
}
// Code generated by fabrik. DO NOT EDIT.
{
Name: "serve",
Run: func(ctx cli.Context) error {
server, cleanup, err := buildServe(ctx)
if err != nil {
return err
}
defer cleanup()
return shared.Serve(ctx, server)
},
},
{
Name: "migrate",
Run: func(ctx cli.Context) error {
db, sources, cleanup, err := buildMigrate(ctx)
if err != nil {
return err
}
defer cleanup()
return shared.Migrate(ctx, db, sources)
},
},
package web
type Visit struct {
Path string `json:"path"`
}
//fabrik:job
func RecordVisit(ctx context.Context, q *query.DB, v Visit) error {
_, err := q.ExecContext(ctx,
`insert into visits (path)
values (?)`, v.Path)
return err
}
//fabrik:cron name=purge-sessions schedule="0 * * * *"
func PurgeSessions(ctx context.Context, q *query.DB) error {
_, err := q.ExecContext(ctx,
`delete from sessions
where expires_at < ?`, time.Now())
return err
}
// Code generated by fabrik. DO NOT EDIT.
jobsManager, err := jobs.New(jobsStore, jobsConfig)
if err != nil {
return err
}
jobs.Register[web.Visit](jobsManager, "web.Visit")
jobs.On[web.Visit](jobsManager, "RecordVisit",
func(c jobs.Context, m web.Visit) error {
return web.RecordVisit(c, queriesDB, m)
})
jobs.RegisterCron(jobsManager, "purge-sessions", "0 * * * *",
func(c jobs.Context) error {
return web.PurgeSessions(c, queriesDB)
})
worker := jobs.NewRunner(jobsManager, runtimeConfig)
package shared
//fabrik:config http
type HTTPConfig struct {
Addr string `yaml:"addr" env:"DEMO_HTTP_ADDR" default:":8080"`
}
//fabrik:config database
type DatabaseConfig struct {
Path string `yaml:"path" env:"DEMO_DATABASE_PATH" default:"demo.db"`
}
// Code generated by fabrik. DO NOT EDIT.
httpConfig, err := config.Load[shared.HTTPConfig](
config.FileOptional("config.yaml"),
config.FileOptional("config.local.yaml"),
config.Section("http"),
)
if err != nil {
return err
}
databaseConfig, err := config.Load[shared.DatabaseConfig](
config.FileOptional("config.yaml"),
config.FileOptional("config.local.yaml"),
config.Section("database"),
)
if err != nil {
return err
}
package mail
//fabrik:config mailer
type MailerConfig struct {
Kind string `yaml:"kind" default:"log"`
Addr string `yaml:"addr" env:"SMTP_ADDR"`
}
//fabrik:provider:select mailer.kind
type Mailer interface {
Send(ctx context.Context, to, subject, body string) error
}
type SMTPMailer struct{ addr string }
func (m *SMTPMailer) Send(ctx context.Context, to, subject, body string) error {
msg := []byte("Subject: " + subject + "\r\n\r\n" + body)
return smtp.SendMail(m.addr, nil, "noreply@app.dev", []string{to}, msg)
}
//fabrik:provider case=smtp
func NewSMTPMailer(cfg *MailerConfig) *SMTPMailer {
return &SMTPMailer{addr: cfg.Addr}
}
type LogMailer struct{}
func (*LogMailer) Send(ctx context.Context, to, subject, body string) error {
slog.InfoContext(ctx, "mail (dev)", "to", to, "subject", subject)
return nil
}
//fabrik:provider case=log
func NewLogMailer() *LogMailer { return &LogMailer{} }
// Code generated by fabrik. DO NOT EDIT.
// mail.Mailer, selected by mailer.kind
var mailer mail.Mailer
switch mailerConfig.Kind {
case "log":
mailer = mail.NewLogMailer()
case "smtp":
mailer = mail.NewSMTPMailer(mailerConfig)
default:
return fmt.Errorf("no mail.Mailer implementation for %q", mailerConfig.Kind)
}
Get started in a minute
-
Install the CLI
curl -fsSL https://gofabrik.dev/install.sh | shPrefer the Go toolchain? Install from source with
go install→ -
Scaffold and run
fabrik new hello cd hello fabrik run -
Say hello
curl 'localhost:8080/?name=fabrik' # Hello, fabrik!