Okapi is a modern, minimalist HTTP web framework for Go inspired by FastAPI's elegant design philosophy. Build fast, scalable, and well-documented APIs with minimal boilerplate while maintaining full control over your application.
Named after the okapi (/oʊˈkɑːpiː/), a rare and graceful mammal native to the rainforests of northeastern Democratic Republic of the Congo—just like its namesake, Okapi blends simplicity and strength in a unique, powerful package.
- Intuitive API Design – Clean, declarative syntax for routes and middleware
- Automatic Request Binding – Seamlessly parse JSON, XML, forms, query params, headers, and path variables into structs
- Built-in Security – Native JWT, Basic Auth, and extensible custom middleware support
- Standard Library Compatible – Works seamlessly with Go's
net/httpand existing codebases - High-Performance Routing – Optimized HTTP router with minimal overhead
- Auto-Generated OpenAPI Docs – OpenAPI 3.0 & Swagger UI automatically synced with your code
- ️ Dynamic Route Management – Enable/disable routes or groups at runtime without code changes
- Production Ready – CORS, templating, static files, TLS, graceful shutdown, and comprehensive middleware
Perfect for: REST APIs, microservices, rapid prototyping, and learning modern Go web development.
- Easy to Learn - Familiar Go idioms, productive in minutes
- Lightweight - Full control with minimal abstraction overhead
- Production Battle-Tested - Fast, reliable, and efficient under real-world load
- Standard Library First - Zero friction with existing Go code
- Self-Documenting - OpenAPI specs always in sync with implementation
- Dynamic Control - Toggle routes and groups at runtime—no code changes needed
Create a New Project
mkdir myapi && cd myapi
go mod init myapi
go get github.com/jkaninda/okapi@latestCreate a simple API in just a few lines of code:
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!",
"license": "MIT",
})
})
if err := o.Start(); err != nil {
panic(err)
}
}Run your app:
go run main.goAccess your API:
- Application: http://localhost:8080
- API Documentation: http://localhost:8080/docs
// Path parameters with type constraints
o.Get("/books/{id:int}", func(c *okapi.Context) error {
id := c.Param("id")
return c.JSON(200, okapi.M{"book_id": id})
})
// Struct binding with automatic validation
type Book struct {
Name string `json:"name" minLength:"5" maxLength:"50" required:"true"`
Price int `json:"price" min:"1" max:"100" required:"true"`
}
// Method 1: Using WithIO for cleaner syntax
o.Put("/books", func(c *okapi.Context) error {
book := &Book{}
if err := c.Bind(book); err != nil {
return c.ErrorBadRequest(err)
}
return c.OK(book)
}).WithIO(&Book{}, &Book{})
// Method 2: Using RouteOptions for more control
o.Post("/books", func(c *okapi.Context) error {
book := &Book{}
if err := c.Bind(book); err != nil {
return c.ErrorBadRequest(err)
}
return c.Created(book)
},
okapi.DocSummary("Create a new book"),
okapi.DocRequestBody(Book{}),
okapi.DocResponse(Book{}),
)Separate your payload from metadata using the Body field pattern for cleaner, more maintainable code:
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"`
}
type BookRequest struct {
Body Book `json:"body"`
ID int `param:"id" query:"id"`
APIKey string `header:"X-API-Key" required:"true"`
}
type BookResponse struct {
Status int // HTTP status code
Body Book // Response payload
RequestID string `header:"X-Request-ID"` // Custom response header
}
func main() {
o := okapi.Default()
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 based on struct tags
// Alternative: return c.Return(res) to use the Status field as the HTTP status code
},
okapi.DocSummary("Create a new book"),
okapi.Request(&BookRequest{}),
okapi.Response(BookResponse{}),
)
// or using WithIO for cleaner syntax
// o.Post("/books", createBookHandler).WithIO(&BookRequest{}, &BookResponse{})
if err := o.Start(); err != nil {
panic(err)
}
}api := o.Group("/api")
// Version management with deprecation markers
v1 := api.Group("/v1", authMiddleware).Deprecated()
v2 := api.Group("/v2")
v3 := api.Group("/v3")
v1.Get("/books", getBooks)
v2.Get("/books", v2GetBooks)
// Dynamically disable specific routes
v3.Get("/books", v2GetBooks).Disable()
// Apply middleware to individual routes
v2.Get("/books/:id", v2GetBookByID).Use(customMiddleware)
// Protected admin routes
admin := api.Group("/admin", adminMiddleware)
admin.Get("/dashboard", getDashboard)For better organization, define routes using the RouteDefinition struct—ideal for controller-based architectures:
type BookService struct{}
func (s *BookService) List(c *okapi.Context) error {
return c.OK(okapi.M{"success": true, "message": "Books retrieved"})
}
func (s *BookService) Create(c *okapi.Context) error {
return c.Created(okapi.M{"success": true, "message": "Book created"})
}
func (s *BookService) bookRoutes() []okapi.RouteDefinition {
apiGroup := &okapi.Group{Prefix: "/api"}
return []okapi.RouteDefinition{
{
Method: http.MethodPut,
Path: "/books",
Handler: s.Update,
Group: apiGroup,
OperationId: "updateBook", // OpenAPI operationId
Summary: "Update Book", // OpenAPI summary
Description: "Update an existing book in the inventory", // OpenAPI description
Request: &BookRequest{}, // OpenAPI request body (if applicable)
Response: &BooksResponse{}, // OpenAPI success response (if applicable)
},
{
Method: http.MethodPost,
Path: "/books",
Handler: s.Create,
Group: apiGroup,
Middlewares: []okapi.Middleware{customMiddleware},
Security: bearerAuthSecurity,
// Using RouteOptions for more control over OpenAPI metadata
Options: []okapi.RouteOption{
okapi.DocSummary("Create Book"),
okapi.DocDescription("Add a new book to the inventory"),
okapi.DocRequestBody(&Book{}),
okapi.DocResponse(&Book{}),
okapi.DocResponse(http.StatusUnauthorized, AuthError{}),
},
},
}
}Register routes:
app := okapi.Default()
bookService := &BookService{}
// Method 1: Direct registration
app.Register(bookService.bookRoutes()...)
// Method 2: Using helper function
okapi.RegisterRoutes(app, bookService.Routes())See the complete example in examples/route-definition.
// JWT Authentication
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("your-secret-key"),
TokenLookup: "header:Authorization",
}
protected := o.Group("/api", jwtAuth.Middleware).WithBearerAuth()
protected.Get("/profile", getProfile)
protected.Post("/logout", logout)
// Basic Authentication
basicAuth := okapi.BasicAuth{
Username: "admin",
Password: "secure-password",
}
admin := o.Group("/admin", basicAuth.Middleware)
admin.Get("/dashboard", getDashboard)Built-in testing utilities for comprehensive test coverage:
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().
ExpectBodyContains("Go Programming").
ExpectHeader("Content-Type", "application/json")
}Build production-ready command-line applications:
import "github.com/jkaninda/okapi/okapicli"
func main() {
o := okapi.Default()
cli := okapicli.New(o, "Okapi CLI Application").
String("config", "c", "config.yaml", "Configuration file path").
Int("port", "p", 8000, "HTTP server port").
Bool("debug", "d", false, "Enable debug mode")
if err := cli.Parse(); err != nil {
panic(err)
}
// Apply CLI options
o.WithPort(cli.GetInt("port"))
if cli.GetBool("debug") {
o.WithDebug()
}
o.Get("/", func(ctx *okapi.Context) error {
return ctx.OK(okapi.M{"message": "Hello, Okapi!"})
})
if err := cli.Run(); err != nil {
panic(err)
}
}Complete documentation: okapi.jkaninda.dev
- Routing – Path patterns, groups, dynamic management
- Request Binding – JSON, XML, forms, validation
- Responses – JSON, XML, templates, file serving
- Middleware – Built-in and custom middleware
- Authentication – JWT, Basic Auth, OAuth2
- OpenAPI/Swagger – Auto-generated documentation
- Testing – Comprehensive testing utilities
- Advanced Features – TLS, CORS, graceful shutdown, CLI integration
Building microservices? Check out Goma Gateway – a high-performance API Gateway featuring:
- Authentication & authorization
- HTTP caching & rate limiting
- Load balancing
- Support for REST, GraphQL, gRPC, TCP, and UDP
We welcome contributions! Here's how to get started:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please read our Contributing Guide for detailed guidelines.
- Documentation: okapi.jkaninda.dev
- Bug Reports: GitHub Issues
- Discussions: GitHub Discussions
- LinkedIn: Jonas Kaninda
MIT License - see LICENSE for details.
Made with ❤️ for the Go community
⭐ Star us on GitHub — it motivates us to keep improving!
Copyright © 2025 Jonas Kaninda
