Install
$ agentstack add skill-jkaninda-okapi-skills-route-definition ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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 — use okapi.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 routes
Router 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
}
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: jkaninda
- Source: jkaninda/okapi-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.