Introducing go-restgen
Introducing go-restgen, a lightweight, type-safe REST API framework for Go.
Image: Generated with ChatGPT (DALL-E 3) | Go gopher by Renée French, CC BY 4.0 | See prompt
Who Loves Boilerplate?
Every backend developer knows the feeling. You’ve got a new Go project, you’ve got your data models, and you know exactly what needs to happen. But before you can write a single line of actual business logic, you’re staring down the barrel of hours of infrastructure work.
Define your structs. Write the SQL to create the tables. Wire up your router. Write a handler for GET /resource. Write one for GET /resource/{id}. Write one for POST, PUT, DELETE. Don’t forget pagination. Don’t forget error handling. Add auth middleware. Figure out ownership — who can see whose data? What about nested resources, where a comment belongs to a post which belongs to a user? Do you validate the parent chain on every request, or just hope the client is well-behaved?
Now multiply that by every resource in your API.
It’s not hard work. It’s just tedious work — the kind that burns time you should be spending on the problems that actually matter.
Introducing go-restgen
go-restgen is a lightweight, type-safe REST API framework for Go that uses generics to automatically generate CRUD endpoints. You define your model, write your table migration, wire up your auth middleware, and register your routes. The framework handles the HTTP layer.
Here’s what the setup looks like for a simple authenticated API:
func main() {
// Connect and initialise the datastore
db, err := datastore.NewPostgres("postgres://user:pass@localhost:5432/myapp")
if err != nil {
log.Fatal(err)
}
datastore.Initialize(db)
defer datastore.Cleanup()
// Create your tables (you write the SQL model — no autogen here)
_, err = db.GetDB().NewCreateTable().Model((*Article)(nil)).IfNotExists().Exec(context.Background())
if err != nil {
log.Fatal(err)
}
r := chi.NewRouter()
// Wire your own auth middleware — JWT, OAuth, sessions, whatever you use
r.Use(authMiddleware)
// Register routes — this generates the full CRUD API
b := router.NewBuilder(r)
router.RegisterRoutes[Article](b, "/articles",
router.AuthConfig{
Methods: []string{router.MethodGet},
Scopes: []string{router.ScopePublic}, // Public reads
},
router.AuthConfig{
Methods: []string{router.MethodPost, router.MethodPut, router.MethodDelete},
Ownership: &router.OwnershipConfig{
Fields: []string{"AuthorID"}, // Owners can write their own
BypassScopes: []string{"admin"}, // Admins can write any
},
},
)
http.ListenAndServe(":8080", r)
}
That registers GET /articles, GET /articles/{id}, POST /articles, PUT /articles/{id}, and DELETE /articles/{id} — with public reads, ownership-scoped writes, admin bypass, and full request parsing, error handling, and JSON response formatting included.
A bit about it
go-restgen is built on two excellent, battle-tested libraries: the Chi router and the Bun ORM. It doesn’t try to replace them — it adds a generation and structure layer on top of tools that already work well.
The framework follows a clean three-layer architecture: HTTP handlers at the top, a service layer for business logic in the middle, and a datastore layer for database operations at the bottom.
It supports PostgreSQL and SQLite out of the box. SQLite in-memory mode makes unit testing fast and dependency-free. UUID primary keys are supported alongside integer auto-increment. The framework is MIT licensed and the source is on GitHub.
Features
Zero boilerplate CRUD — Register a resource type with RegisterRoutes[T] and get a complete, functioning REST API. No handler code to write, no repetitive wiring.
Type-safe by design — Go generics provide compile-time type checking throughout. No interface{} casting, no runtime surprises, no reflection magic you have to debug at 11pm.
Granular authentication and authorization — The framework doesn’t implement auth itself; you bring your own middleware. go-restgen integrates that auth context into fine-grained controls: route-level scopes, per-operation ownership enforcement, admin bypass rules, and configurable access per HTTP method. Routes are blocked by default — secure by design, not by accident.
Nested resource support — Define parent-child relationships and the framework automatically validates the full parent chain on every request. A DELETE /users/{userId}/posts/{postId}/comments/{commentId} doesn’t just check if the comment exists — it verifies the comment belongs to that post, and the post belongs to that user, at the database level with JOINs, every time.
Custom handlers — When generated behaviour isn’t enough, override individual operations with your own logic while keeping everything else. Common patterns like /me endpoints, auto-setting ownership on create, filtering GetAll by the authenticated user, and preventing deletion of immutable records are first-class use cases.
Action endpoints — Beyond standard CRUD, define custom actions on resources: POST /orders/{id}/cancel, POST /tasks/{id}/complete. Each action has its own auth configuration, and the framework pre-fetches and validates the resource before your handler runs.
Batch operations — Bulk create, update, and delete via /resource/batch endpoints, with transactional all-or-nothing semantics.
File uploads — Two modes: proxy mode (files stream through your server) and signed URL mode (direct client-to-storage uploads). Both integrate with the standard auth and ownership model.
Audit logging — Attach an auditor to any resource and get transactionally consistent audit records for every create, update, and delete operation.
OpenTelemetry metrics — Built-in metrics middleware via the go-restgen/metrics package. Request duration histograms and count counters with resource, method, and status dimensions. If you’re not using OTEL, the middleware is a no-op with negligible overhead.
Get started
go get github.com/sjgoldie/go-restgen
The examples directory has complete, runnable examples covering simple CRUD, nested routes, UUID primary keys, auth patterns, validation, audit logging, file uploads, action endpoints, and batch operations. Each example includes Bruno API tests.
The project is at v0.5.4 and not yet at v1 — the API may still shift as real-world usage surfaces rough edges. Feedback, issues, and pull requests are all welcome.
If you build something with it, or find something that doesn’t work the way it should, I’d genuinely like to hear about it.
github.com/sjgoldie/go-restgen
Image prompt: Based on the attached reference image of the Go gopher, illustrate the Go programming language gopher mascot standing at a factory conveyor belt. On the left side of the belt: a single blueprint/schematic labeled “Model”. The gopher is operating a large lever or press in the middle. Coming off the right side of the belt: four identical red route signs, each clearly labeled with one label only — “GET”, “POST”, “PUT”, “DELETE”. Cel-shaded art style, hand-drawn line art, graphic novel illustration, charcoal and red colour palette, white background, stylized illustration.
Go gopher by Renée French, CC BY 4.0