route_definition
Skill3 starsChanged 3 months agoMITView on GitHub
Okapi's RouteDefinition is a declarative alternative to imperative route registration (.Get(), .Post(), etc.). Instead of chaining method calls, you describe each route as a struct and register it with app.Register(). This keeps route metadata — method, path, handler, documentation, middleware — in one place, making routes easier to read, move between files, and generate programmatically. Imperative (traditional): Declarative (RouteDefinition): When Group is nil the route is registered on the root instance; when the group's Okapi reference is unset it is…
In this repository20 other files it gives its agents.
- authenticationSkill
- cliSkill
- contextSkill
- dynamic_routesSkill
- error_handlingSkill
- http_clientSkill
- middlewareSkill
- openapiSkill
- overviewSkill
- request_bindingSkill
- responseSkill
- routingSkill
- sse_streamSkill
- std_compatSkill
- templatingSkill
- testingSkill
- tls_httpsSkill
- validationSkill
- websocketSkill
- web_spaSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
2
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 67
When Group is nil the route is registered on the root instance.
- Distinctive: line 73
Use struct fields for common metadata — they're cleaner and more readable:
- Very specific: line 89
Use .WithTags() on groups so routes inherit tags automatically — no need to repeat Tags on every route.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Okapi Route Definition Skills
Okapi's RouteDefinition is a declarative alternative to imperative route registration (.Get(), .Post(), etc.). Instead of chaining method calls, you describe each route as a struct and register it with app.Register(). This keeps route metadata — method, path, handler, documentation, middleware — in one place, making routes easier to read, move between files, and generate programmatically.
RouteDefinition Struct
| Field | Type | Description |
Method | string | HTTP method (http.MethodGet, http.MethodPost, etc.) |
Path | string | Route path relative to its group (e.g. "/books/{id:int}") |
Handler | okapi.HandlerFunc | Handler function — use okapi.H() for generic input binding |
Group | *okapi.Group | Group this route belongs to (prefix, middleware, tags) |
OperationId | string | OpenAPI operation ID |
Summary | string | Short description for OpenAPI docs |
Description | string | Detailed description for OpenAPI docs |
Tags | []string | OpenAPI tags (overrides group tags if set) |
Request | any | Request body schema for OpenAPI docs |
Response | any | Success response schema (defaults to 200) |
Security | []map[string][]string | OpenAPI security requirements |
Options | []okapi.RouteOption | Additional route options (error responses, path params, etc.) |
Middlewares | []okapi.Middleware | Per-route middleware |
Declarative vs Imperative
Imperative (traditional):
group.Post("/books", okapi.H(handler.Create),
okapi.DocTags("Books"),
okapi.DocSummary("Create a book"),
okapi.Request(&CreateBookRequest{}),
okapi.DocResponse(201, &BookResponse{}),
okapi.DocResponse(409, &ErrorResponse{}),
)Declarative (RouteDefinition):
app.Register(okapi.RouteDefinition{
Method: http.MethodPost,
Path: "/books",
Handler: okapi.H(handler.Create),
Group: bookGroup,
Summary: "Create a book",
Request: &CreateBookRequest{},
Options: []okapi.RouteOption{
okapi.DocResponse(201, &BookResponse{}),
okapi.DocResponse(409, &ErrorResponse{}),
},
})Registration
// On the app — single or variadic
app.Register(routeDef)
app.Register(routeDefs...)
// Package-level equivalent (takes a slice)
okapi.RegisterRoutes(app, routeDefs)
// On a group — assigns the group to every route that doesn't set one
api := app.Group("/api")
api.Register(routeDefs...)When Group is nil the route is registered on the root instance; when the group's Okapi reference is unset it is filled in automatically. Supported methods are GET, POST, PUT, DELETE, PATCH, HEAD, and OPTIONS — an unsupported method panics.
Middleware comes from Middlewares on the definition, or from Use() on the app/group.
Using Struct Fields vs Options
Use struct fields for common metadata — they're cleaner and more readable:
| Use struct field | Use Options for |
Summary, Description | okapi.DocPathParam(...) — path parameters |
Tags | okapi.DocResponse(201, ...) — non-200 statuses |
Request | okapi.DocResponse(204, nil) — no content |
Response (200 only) | okapi.DocResponse(404, &ErrorResponse{}) — errors |
Security | okapi.DocHide() — hide from docs |
Middlewares | okapi.DocQueryParam(...) — query parameters |
OperationId | okapi.DocHeader(...) — header parameters |
okapi.DocErrorResponse(status, v)is deprecated — useokapi.DocResponse(status, v).
Group-Level Tags with .WithTags()
Use .WithTags() on groups so routes inherit tags automatically — no need to repeat Tags on every route. Only set Tags on a route when it needs to override the group default.
// All routes in this group inherit the "Books" tag
bookGroup := app.Group("/api/v1/books", authMiddleware).WithTags([]string{"Books"})
bookGroup.WithBearerAuth()
app.Register(
// Inherits "Books" tag from group
okapi.RouteDefinition{
Method: http.MethodGet,
Path: "",
Handler: bookService.List,
Group: bookGroup,
Summary: "List books",
Response: &BooksResponse{},
},
// Overrides group tag
okapi.RouteDefinition{
Method: http.MethodGet,
Path: "/categories",
Handler: categoryService.List,
Group: bookGroup,
Tags: []string{"Categories"},
Summary: "List categories",
},
)Organizing Routes in a Large Project
For large projects with many routes, use a Router struct and split route definitions across multiple files by domain. Each file contains methods that return []okapi.RouteDefinition.
File structure:
internal/routes/
├── routes.go # Router struct, InitRoutes, registerRoutes orchestrator
├── auth_routes.go # Auth & public API routes
├── user_routes.go # User-scoped routes
└── admin_routes.go # Admin routesRouter struct (in routes.go):
type Router struct {
app *okapi.Okapi
cfg *config.Config
v1 *okapi.Group
// Auth middleware
jwtAuth okapi.JWTAuth
jwtAdminAuth okapi.JWTAuth
// Handlers
userHandler *handlers.UserHandler
bookHandler *handlers.BookHandler
adminHandler *handlers.AdminHandler
}
func InitRoutes(app *okapi.Okapi, db *gorm.DB, cfg *config.Config) {
// Initialize repositories, services, handlers...
r := &Router{
app: app,
cfg: cfg,
v1: app.Group("/api/v1"),
jwtAuth: middlewares.JWTAuth(cfg),
jwtAdminAuth: middlewares.JWTAdminAuth(cfg),
userHandler: handlers.NewUserHandler(userRepo),
bookHandler: handlers.NewBookHandler(bookRepo),
adminHandler: handlers.NewAdminHandler(db),
}
r.registerRoutes()
}
func (r *Router) registerRoutes() {
r.app.Use(okapi.RequestID())
r.app.Register(r.authRoutes()...)
r.app.Register(r.userRoutes()...)
r.app.Register(r.adminRoutes()...)
// Special registrations that don't fit RouteDefinition
r.app.Static("/assets", "public/assets")
r.app.Web("/", "./web") // SPA index fallback — register after every API route
}Domain-specific route files (e.g. auth_routes.go):
func (r *Router) authRoutes() []okapi.RouteDefinition {
authGroup := r.v1.Group("/auth").WithTags([]string{"Auth"})
return []okapi.RouteDefinition{
{
Method: http.MethodPost,
Path: "/login",
Handler: okapi.H(r.userHandler.Login),
Group: authGroup,
Summary: "Login",
Request: &LoginRequest{},
Response: &AuthResponse{},
},
}
}Key principles:
- One
Routerstruct holds all handlers, middleware, and config - Each file returns
[]okapi.RouteDefinitionfrom methods onRouter - Groups are created inside each method with
.WithTags()to avoid tag repetition registerRoutes()orchestrates all registration viaapp.Register()- Conditional routes (dev mode, optional features) use
ifguards inregisterRoutes() - Special registrations (static files, NoRoute, global middleware) stay imperative
Common Patterns
CRUD resource — a typical set of routes for a resource:
func (r *Router) bookRoutes() []okapi.RouteDefinition {
g := r.v1.Group("/books", r.jwtAuth.Middleware).WithTags([]string{"Books"})
g.WithBearerAuth()
return []okapi.RouteDefinition{
{Method: http.MethodGet, Path: "", Handler: okapi.H(r.bookHandler.List), Group: g,
Summary: "List books", Request: &ListRequest{}, Response: &PageableResponse[Book]{}},
{Method: http.MethodGet, Path: "/{id:int}", Handler: okapi.H(r.bookHandler.Get), Group: g,
Summary: "Get book", Response: &Response[Book]{},
Options: []okapi.RouteOption{okapi.DocPathParam("id", "integer", "Book ID"), okapi.DocResponse(404, &ErrorResponse{})}},
{Method: http.MethodPost, Path: "", Handler: okapi.H(r.bookHandler.Create), Group: g,
Summary: "Create book", Request: &CreateBookRequest{},
Options: []okapi.RouteOption{okapi.DocResponse(201, &Response[Book]{}), okapi.DocResponse(409, &ErrorResponse{})}},
{Method: http.MethodPut, Path: "/{id:int}", Handler: okapi.H(r.bookHandler.Update), Group: g,
Summary: "Update book", Request: &UpdateBookRequest{}, Response: &Response[Book]{},
Options: []okapi.RouteOption{okapi.DocPathParam("id", "integer", "Book ID"), okapi.DocResponse(404, &ErrorResponse{})}},
{Method: http.MethodDelete, Path: "/{id:int}", Handler: okapi.H(r.bookHandler.Delete), Group: g,
Summary: "Delete book",
Options: []okapi.RouteOption{okapi.DocPathParam("id", "integer", "Book ID"), okapi.DocResponse(204, nil), okapi.DocResponse(404, &ErrorResponse{})}},
}
}Conditional routes — only registered when a feature is enabled:
func (r *Router) registerRoutes() {
r.app.Register(r.publicRoutes()...)
r.app.Register(r.userRoutes()...)
if r.cfg.DevMode {
r.app.Register(r.devRoutes()...)
}
}Optional handler — append to the slice when a dependency is available:
func (r *Router) adminRoutes() []okapi.RouteDefinition {
g := r.v1.Group("/admin", r.jwtAdminAuth.Middleware).WithTags([]string{"Admin"})
g.WithBearerAuth()
routes := []okapi.RouteDefinition{
// ... standard admin routes
}
if r.cronHandler != nil {
routes = append(routes, okapi.RouteDefinition{
Method: http.MethodGet,
Path: "/jobs",
Handler: r.cronHandler.List,
Group: g,
Summary: "List scheduled jobs",
Response: &Response[[]JobStatus]{},
})
}
return routes
}Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

