Okapi
Okapi – A lightweight, expressive, and minimalist Go web framework with built-in OpenAPI 3, Swagger UI, ReDoc, and powerful middleware support.
Install / Use
/learn @jkaninda/OkapiREADME
Okapi
A modern, minimalist HTTP web framework for Go inspired by the elegant developer experience of FastAPI.
Okapi focuses on clarity, strong typing, automatic validation, and built-in OpenAPI documentation, making it easy to build production-ready APIs without boilerplate.
<p align="center"> <img src="https://raw.githubusercontent.com/jkaninda/okapi/main/logo.png" width="150" alt="Okapi logo"> </p>Named after the okapi, a rare and graceful mammal native to the rainforests of northeastern Democratic Republic of the Congo — Okapi blends simplicity, elegance, and strength into a powerful framework for building Go web services.
Why Okapi?
Go developers often combine multiple libraries for routing, validation, documentation, authentication, and testing.
Okapi brings all these capabilities together in one cohesive framework while remaining fully compatible with Go’s standard net/http.
Key goals:
- Developer experience similar to FastAPI
- Minimal boilerplate
- Strong typing
- Built-in OpenAPI documentation
- Production-ready features out of the box
Features
- Intuitive API Design – Clean, declarative syntax for routes and middleware.
- Automatic Request Binding – Parse JSON, XML, forms, query params, headers, and path variables into structs
- Built-in Validation – Struct tag-based validation with comprehensive error messages.
- Auto-Generated OpenAPI Docs – Swagger UI and ReDoc automatically synced with your code.
- Runtime Documentation Control – Enable/disable OpenAPI docs at runtime without redeployment
- Authentication Ready – Native JWT, Basic Auth, and extensible middleware support.
- Standard Library Compatible – Fully compatible with Go’s
net/http. - Dynamic Route Management – Enable/disable routes at runtime without code changes
- Production Ready – TLS support, CORS, graceful shutdown, middleware system, and more.
Installation
mkdir myapi && cd myapi
go mod init myapi
go get github.com/jkaninda/okapi@latest
Quick Start
package main
import "github.com/jkaninda/okapi"
func main() {
o := okapi.Default()
o.Get("/", func(c *okapi.Context) error {
return c.OK(okapi.M{
"message": "Hello from Okapi!",
})
})
err := o.Start()
if err != nil {
panic(err)
}
}
Run the application:
go run main.go
Visit:
- API → http://localhost:8080
- Swagger Docs → http://localhost:8080/docs
- ReDoc → http://localhost:8080/redoc
Request Binding & Validation
Okapi supports multiple binding styles, from simple handlers to fully typed input/output patterns.
Validation Tags
Define validation rules directly on your structs:
type Book struct {
Name string `json:"name" minLength:"4" maxLength:"50" required:"true" pattern:"^[A-Za-z]+$"`
Price int `json:"price" required:"true" min:"5" max:"100"`
Year int `json:"year" deprecated:"true"`
Status string `json:"status" enum:"available,out_of_stock,discontinued" default:"available"`
}
Method 1: Binding with c.Bind()
The simplest approach to bind and validate within your handler:
o.Post("/books", func(c *okapi.Context) error {
var book Book
if err := c.Bind(&book); err != nil {
return c.ErrorBadRequest(err)
}
return c.Created(book)
})
Method 2: Typed Input with okapi.Handle()
Automatic input binding with a typed handler signature:
o.Post("/books", okapi.Handle(func(c *okapi.Context, book *Book) error {
book.ID = generateID()
return c.Created(book)
}),
okapi.DocRequestBody(&Book{}),
okapi.DocResponse(&Book{}),
)
Method 3: Shorthand with okapi.H()
A concise version for simple input validation:
type BookDetailInput struct {
ID int `path:"id"`
}
o.Get("/books/{id:int}", okapi.H(func(c *okapi.Context, input *BookDetailInput) error {
book := findBookByID(input.ID)
if book == nil {
return c.AbortNotFound("Book not found")
}
return c.OK(book)
}))
Method 4: Input & Output with okapi.HandleIO()
Define both input and output structs separately for complex operations:
type BookEditInput struct {
ID int `path:"id" required:"true"`
Body Book `json:"body"`
}
type BookOutput struct {
Status int
Body Book
}
o.Put("/books/{id:int}", okapi.HandleIO(func(c *okapi.Context, input *BookEditInput) (*BookOutput, error) {
book := updateBook(input.ID, input.Body)
if book == nil {
return nil, c.AbortNotFound("Book not found")
}
return &BookOutput{Body: *book}, nil
})).WithIO(&BookEditInput{}, &BookOutput{})
Method 5: Output Only with okapi.HandleO()
When you only need a structured output without specific input validation:
type BooksResponse struct {
Body []Book `json:"books"`
}
o.Get("/books", okapi.HandleO(func(c *okapi.Context) (*BooksResponse, error) {
return &BooksResponse{Body: getAllBooks()}, nil
})).WithOutput(&BooksResponse{})
Advanced Request/Response Patterns
Separate payload from metadata using the Body field pattern:
type BookRequest struct {
Body Book `json:"body"` // Request payload
ID int `param:"id" query:"id"` // Path or query parameter
APIKey string `header:"X-API-Key" required:"true"` // Header
}
type BookResponse struct {
Status int // HTTP status code
Body Book // Response payload
RequestID string `header:"X-Request-ID"` // Response header
}
o.Post("/books", func(c *okapi.Context) error {
var req BookRequest
if err := c.Bind(&req); err != nil {
return c.ErrorBadRequest(err)
}
res := &BookResponse{
Status: 201,
RequestID: uuid.New().String(),
Body: req.Body,
}
return c.Respond(res) // Automatically sets status, headers, and body
},
okapi.Request(&BookRequest{}),
okapi.Response(BookResponse{}),
)
Route Groups & Middleware
api := o.Group("/api")
// Versioned API groups
v1 := api.Group("/v1", authMiddleware).Deprecated()
v2 := api.Group("/v2")
v1.Get("/books", getBooks)
v2.Get("/books", v2GetBooks)
// Disable routes at runtime
v2.Get("/experimental", experimentalHandler).Disable()
// Apply middleware to individual routes
v2.Get("/books/{id}", getBookByID).Use(cacheMiddleware)
// Protected admin routes
admin := api.Group("/admin", adminMiddleware)
admin.Get("/dashboard", getDashboard)
Declarative Route Definition
Ideal for controller or service-based architectures:
type BookService struct{}
func (s *BookService) Routes() []okapi.RouteDefinition {
apiGroup := &okapi.Group{Prefix: "/api"}
return []okapi.RouteDefinition{
{
Method: http.MethodGet,
Path: "/books",
Handler: s.List,
Group: apiGroup,
Summary: "List all books",
Response: &BooksResponse{},
},
{
Method: http.MethodPost,
Path: "/books",
Handler: s.Create,
Group: apiGroup,
Middlewares: []okapi.Middleware{authMiddleware},
Security: bearerAuthSecurity,
Options: []okapi.RouteOption{
okapi.DocSummary("Create a book"),
okapi.DocRequestBody(&Book{}),
okapi.DocResponse(&Book{}),
},
},
}
}
// Register routes
app := okapi.Default()
bookService := &BookService{}
app.Register(bookService.Routes()...)
Authentication
JWT Authentication
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("your-secret-key"),
Issuer: "okapi",
Audience: "okapi.jkaninda.dev",
ClaimsExpression: "Equals(`email_verified`, `true`)",
TokenLookup: "header:Authorization",
ContextKey: "user",
}
protected := o.Group("/api", jwtAuth.Middleware).WithBearerAuth()
protected.Get("/profile", getProfile)
Basic Authentication
basicAuth := okapi.BasicAuth{
Username: "admin",
Password: "secure-password",
}
admin := o.Group("/admin", basicAuth.Middleware)
admin.Get("/dashboard", getDashboard)
Template Rendering
func main() {
tmpl, _ := okapi.NewTemplateFromDirectory("views", ".html")
o := okapi.Default().WithRenderer(tmpl)
o.Get("/", func(c *okapi.Context) error {
return c.Render(http.StatusOK, "home", okapi.M{
"title": "Welcome",
"message": "Hello, World!",
})
})
o.Start()
}
Embedded Templates
//go:embed views/*
var Views embed.FS
func main() {
app := okapi.New()
app.WithRendererFromFS(Views, "views/*.html")
app.StaticFS("/assets", http.FS(must(fs.Sub(Views, "views/assets"))))
app.Start()
}
Testing
import "github.com/jkaninda/okapi/okapitest"
func TestGetBooks(t *testing.T) {
server := okapi.NewTestServer(t)
server.Get("/books", GetBooksHandler)
okapitest.GET(t, server.BaseURL+"/books").
ExpectStatusOK().
Exp
Related Skills
xurl
344.1kA CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.
openhue
344.1kControl Philips Hue lights and scenes via the OpenHue CLI.
sag
344.1kElevenLabs text-to-speech with mac-style say UX.
weather
344.1kGet current weather and forecasts via wttr.in or Open-Meteo
