| name | go-restgen |
| description | go-restgen REST API framework patterns — model definitions, route registration, auth, nesting, custom endpoints, SSE, file uploads, tenancy, and query configuration. |
go-restgen Framework Guide
You are working with go-restgen, a type-safe REST API framework for Go using generics. It auto-generates CRUD endpoints from model definitions. Built on Chi (router) and Bun (ORM).
Read patterns.md in this skill directory for complete handler signatures, all route options, and detailed examples.
Core Pattern
type Product struct {
bun.BaseModel `bun:"table:products"`
ID int `bun:"id,pk,autoincrement" json:"id"`
Name string `bun:"name,notnull" json:"name"`
Price float64 `bun:"price,notnull" json:"price"`
CreatedAt time.Time `bun:"created_at,notnull,skipupdate" json:"created_at,omitempty"`
UpdatedAt time.Time `bun:"updated_at,notnull" json:"updated_at,omitempty"`
}
func (p *Product) BeforeAppendModel(ctx context.Context, query bun.Query) error {
now := time.Now()
switch query.(type) {
case *bun.InsertQuery:
p.CreatedAt = now
p.UpdatedAt = now
case *bun.UpdateQuery:
p.UpdatedAt = now
}
return nil
}
db, _ := datastore.NewSQLite(":memory:")
datastore.Initialize(db)
defer datastore.Cleanup()
db.GetDB().NewCreateTable().Model((*Product)(nil)).IfNotExists().Exec(context.Background())
r := chi.NewRouter()
b := router.NewBuilder(r)
router.RegisterRoutes[Product](b, "/products",
router.AllPublic(),
router.WithFilters("Name", "Price"),
router.WithSorts("Name", "Price", "CreatedAt"),
router.WithPagination(20, 100),
)
This generates: GET/POST /products, GET/PUT/PATCH/DELETE /products/{id}.
Auth Patterns
router.AllPublic()
router.AllScoped("admin")
router.IsAuthenticated()
router.AllWithOwnershipUnless([]string{"UserID"}, "admin")
router.AuthConfig{
Methods: []string{router.MethodGet, router.MethodList},
Scopes: []string{router.ScopePublic},
},
router.AuthConfig{
Methods: []string{router.MethodPost, router.MethodPut, router.MethodPatch, router.MethodDelete},
Scopes: []string{"admin"},
},
Auth middleware sets AuthInfo on context:
authInfo := &router.AuthInfo{UserID: userID, TenantID: tenantID, Scopes: scopes}
ctx := context.WithValue(r.Context(), router.AuthInfoKey, authInfo)
Nesting
router.RegisterRoutes[Blog](b, "/blogs", router.AllPublic(), func(b *router.Builder) {
router.RegisterRoutes[Post](b, "/posts",
router.AllPublic(),
router.WithRelationName("Posts"),
)
})
Child model needs FK + belongs-to tag:
BlogID int `bun:"blog_id,notnull,skipupdate" json:"blog_id"`
Blog *Blog `bun:"rel:belongs-to,join:blog_id=id" json:"-"`
Child Relation Filters and Counts
Filter parent resources by child relation existence or count, and request per-item child counts. Relations must be registered with WithRelationName and authorized via AllowedIncludes.
GET /posts?filter[Comments][exists]=true
GET /posts?filter[Comments][exists]=false
GET /posts?filter[Comments][count_gt]=5
GET /posts?include_count=Comments
GET /posts?filter[Comments][exists]=true&include_count=Comments
Response with include_count:
{
"data": [...],
"counts": {"Comments": {"1": 5, "2": 3}}
}
Unauthorized relations are silently skipped (filter ignored, count excluded).
Custom Endpoints (Anything Funcs)
router.WithEndpoint("GET", "status", handlerFn, router.AuthConfig{...})
router.RegisterRootEndpoint(b, "GET", "/system/info", handlerFn, router.AllPublic())
router.WithSSE("events", sseFn, router.AuthConfig{...})
router.RegisterRootSSE(b, "/events/system", sseFn, router.AllPublic())
Key Conventions
- PK field: named
ID by default, override with router.WithAlternatePK("MyPK")
- FK fields: include
skipupdate to prevent modification
- Ownership fields: string type matching
AuthInfo.UserID
- Timestamps: use
BeforeAppendModel hook
- UUID PKs: use
uuid.UUID type with bun:"id,pk,type:uuid"
- Routes blocked by default: must explicitly configure auth
- Path IDs take precedence: JSON body IDs and FKs are ignored