https://github.com/jkaninda/okapi
Okapi – A lightweight, expressive, and minimalist Go web framework with built-in OpenAPI 3, Swagger UI, and powerful middleware support.
https://github.com/jkaninda/okapi
api-rest go go-api go-okapi go-openapi-ui go-server go-swagger golang golang-server okapi-api restful-api
Last synced: 3 months ago
JSON representation
Okapi – A lightweight, expressive, and minimalist Go web framework with built-in OpenAPI 3, Swagger UI, and powerful middleware support.
- Host: GitHub
- URL: https://github.com/jkaninda/okapi
- Owner: jkaninda
- License: mit
- Created: 2025-05-28T20:54:20.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-02-09T21:46:55.000Z (4 months ago)
- Last Synced: 2026-02-09T23:56:05.129Z (4 months ago)
- Topics: api-rest, go, go-api, go-okapi, go-openapi-ui, go-server, go-swagger, golang, golang-server, okapi-api, restful-api
- Language: Go
- Homepage: http://okapi.jkaninda.dev/
- Size: 1.61 MB
- Stars: 81
- Watchers: 1
- Forks: 14
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# Okapi
A modern, minimalist HTTP web framework for Go inspired by FastAPI's elegant design philosophy.
[](https://github.com/jkaninda/okapi/actions/workflows/tests.yml)
[](https://goreportcard.com/report/github.com/jkaninda/okapi)
[](https://pkg.go.dev/github.com/jkaninda/okapi)
[](https://codecov.io/gh/jkaninda/okapi)
[](https://github.com/jkaninda/okapi/releases)
Named after the okapi, a rare and graceful mammal native to the rainforests of northeastern Democratic Republic of the Congo—Okapi blends simplicity and strength in a unique, powerful package.
## 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** – Works seamlessly with Go's `net/http`
- **Dynamic Route Management** – Enable/disable routes at runtime without code changes
- **Production Ready** – CORS, TLS, graceful shutdown, and comprehensive middleware
## Installation
```bash
mkdir myapi && cd myapi
go mod init myapi
go get github.com/jkaninda/okapi@latest
```
## Quick Start
```go
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 with `go run main.go` and visit:
- **API**: http://localhost:8080
- **Docs**: http://localhost:8080/docs
---
## Request Binding & Validation
Okapi provides multiple approaches to bind and validate incoming requests, from simple binding to fully typed handler signatures.
### Validation Tags
Define validation rules directly on your structs:
```go
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:
```go
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:
```go
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:
```go
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:
```go
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:
```go
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:
```go
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
```go
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:
```go
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
```go
jwtAuth := okapi.JWTAuth{
SigningSecret: []byte("your-secret-key"),
ClaimsExpression: "Equals(`email_verified`, `true`)",
TokenLookup: "header:Authorization",
ContextKey: "user",
}
protected := o.Group("/api", jwtAuth.Middleware).WithBearerAuth()
protected.Get("/profile", getProfile)
```
### Basic Authentication
```go
basicAuth := okapi.BasicAuth{
Username: "admin",
Password: "secure-password",
}
admin := o.Group("/admin", basicAuth.Middleware)
admin.Get("/dashboard", getDashboard)
```
---
## Template Rendering
```go
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
//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
```go
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")
}
```
---
## CLI Integration
```go
import "github.com/jkaninda/okapi/okapicli"
func main() {
o := okapi.Default()
cli := okapicli.New(o, "My API").
String("config", "c", "config.yaml", "Config file").
Int("port", "p", 8000, "Server port").
Bool("debug", "d", false, "Debug mode")
cli.Parse()
o.WithPort(cli.GetInt("port"))
// ... register routes ...
cli.Run()
}
```
---
---
## OpenAPI Documentation
Okapi automatically generates interactive API documentation with multiple approaches to document your routes.
### Enabling Documentation
**With `okapi.Default()`** – Documentation is enabled by default at `/docs` and `/redoc`.
**With `okapi.New()`** – Documentation is disabled by default. Enable it conditionally:
```go
o := okapi.New()
if os.Getenv("ENABLE_DOCS") == "true" {
o.WithOpenAPIDocs()
}
```
### Documenting Routes
#### Composable Functions
Simple and readable for most routes:
```go
o.Get("/books", getBooksHandler,
okapi.DocSummary("List all available books"),
okapi.DocTags("Books"),
okapi.DocQueryParam("author", "string", "Filter by author name", false),
okapi.DocQueryParam("limit", "int", "Maximum results to return", false),
okapi.DocResponseHeader("X-Client-Id", "string", "Client ID"),
okapi.DocResponse([]Book{}),
okapi.DocResponse(400, ErrorResponse{}),
)
```
#### Fluent Builder
For complex or dynamic documentation needs:
```go
o.Post("/books", createBookHandler,
okapi.Doc().
Summary("Add a new book to the inventory").
Tags("Books").
BearerAuth().
ResponseHeader("X-Client-Id", "string", "Client ID").
RequestBody(BookRequest{}).
Response(201, Book{}).
Response(400, ErrorResponse{}).
Build(),
)
```
#### Struct-Based with Body Field
Define request/response metadata directly in structs:
```go
type BookRequest struct {
Body struct {
Name string `json:"name" minLength:"4" maxLength:"50" required:"true"`
Price int `json:"price" required:"true"`
} `json:"body"`
ID int `param:"id" query:"id"`
APIKey string `header:"X-API-Key" required:"true"`
}
o.Post("/books", createBookHandler,
okapi.Request(&BookRequest{}),
okapi.Response(&BookResponse{}),
)
```
#### Fluent Route Methods
Chain documentation directly on route definitions:
```go
o.Post("/books", handler).WithIO(&BookRequest{}, &BookResponse{}) // Both request & response
o.Post("/books", handler).WithInput(&BookRequest{}) // Request only
o.Get("/books", handler).WithOutput(&BooksResponse{}) // Response only
```
See the full guide at **[okapi.jkaninda.dev/features/openapi](https://okapi.jkaninda.dev/features/openapi.html)**
### Generated Documentation
| Swagger UI (`/docs`) | ReDoc (`/redoc`) |
|:--------------------------------------------------------------------------------:|:-------------------------------------------------------------------------:|
|  |  |
---
## Documentation
Full documentation available at **[okapi.jkaninda.dev](https://okapi.jkaninda.dev)**
Topics covered: Routing, Request Binding, Validation, Responses, Middleware, Authentication, OpenAPI, Testing, TLS, CORS, Graceful Shutdown, and more.
---
## Related Projects
Building microservices?
Check out **[Goma Gateway](https://github.com/jkaninda/goma-gateway)** a high-performance API Gateway with authentication, rate limiting, load balancing, and support for REST, GraphQL, gRPC, TCP, and UDP.
## Okapi vs Huma
Both **[Okapi](https://github.com/jkaninda/okapi)** and **[Huma](https://github.com/danielgtaylor/huma)** aim to improve developer experience in Go APIs with strong typing and OpenAPI integration. The key difference is **philosophy**: Okapi is a *batteries-included web framework*, while Huma is an *API layer designed to sit on top of existing routers*.
| Feature / Aspect | **Okapi** | **Huma** |
|------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------|
| **Positioning** | Full web framework | API framework built on top of existing routers |
| **Router** | Built-in high-performance router | Uses external routers (Chi, httprouter, Fiber, etc.) |
| **OpenAPI Generation** | Native, framework-level (Swagger UI & Redoc included) | Native, schema-first API design |
| **Request Binding** | Unified binder for JSON, XML, forms, query, headers, path params | Struct tags + resolver pattern for headers, query, path params |
| **Validation** | Tag-based (min, max, enum, required, default, pattern, etc.) | Included |
| **Response Modeling** | Output structs with `Body` pattern; headers & status via struct fields | Strongly typed response models with similar patterns |
| **Middleware** | Built-in + custom middleware, groups, per-route middleware | Router middleware + Huma-specific middleware and transformers |
| **Authentication** | Built-in JWT, Basic Auth, security schemes for OpenAPI | Security schemes via OpenAPI; middleware via router |
| **Dynamic Route Management** | Enable/disable routes & groups at runtime | Not a core feature |
| **Templating / HTML** | Built-in rendering (HTML templates, static files) | API-focused; not intended for HTML apps |
| **CLI Integration** | Built-in CLI support (flags, env config) | Included |
| **Testing Utilities** | Built-in test server and fluent HTTP assertions | Relies on standard Go testing tools |
| **Learning Curve** | Very approachable for Go web developers | Slightly steeper (requires OpenAPI-first mental model) |
| **Use Case Fit** | Full web apps, APIs, gateways, microservices | Pure API services, schema-first API design |
| **Philosophy** | "FastAPI-like DX for Go, batteries included" | "OpenAPI-first typed APIs on top of your router of choice" |
### Quick Comparison
**Okapi** — define a route with built-in validation and OpenAPI metadata:
```go
app:=okapi.Default()
app.Register(okapi.RouteDefinition{
Method: http.MethodPost,
Path: "/users",
Handler: createUser,
OperationId: "create-user",
Summary: "Create a new user",
Tags: []string{"users"},
Request: &UserRequest{},
Response: &User{},
})
```
**Huma** — similar concept, different style:
```go
huma.Register(api, huma.Operation{
OperationID: "create-user",
Method: http.MethodPost,
Path: "/users",
Summary: "Create a new user",
Tags: []string{"Users"},
}, createUser)
```
Both approaches generate OpenAPI documentation automatically.
---
### When to Choose Which?
#### Choose Okapi if you want:
- A **batteries-included web framework** with routing, middleware, auth, OpenAPI, templates, and CLI in one cohesive package
- **FastAPI-like developer experience** that feels idiomatic in Go
- **Dynamic route control** — enable or disable routes and groups at runtime
- To build APIs **and** serve HTML pages or static assets from the same application
#### Choose Huma if you want:
- A **schema-first, OpenAPI-driven API layer** where the spec drives your implementation
- To **keep using your existing router** (Chi, Fiber, Echo, etc.) without adopting a new framework
- **Strict typed request/response contracts** as your primary design model
- A **minimal, API-only stack** without broader web framework concerns
---
### Community & Maturity
- **Huma**: More established with a larger community and extensive production usage
- **Okapi**: Newer and rapidly evolving, with a smaller but growing community
Both are actively maintained. Choose based on your architectural preferences and project needs rather than stability concerns alone.
> **Note**: If you're already using Huma with Chi or another router and it's working well for you, there's no urgent reason to switch. Okapi is ideal for new projects or when you want a more integrated, batteries-included framework experience.
---
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
---
## Support
- **Documentation:** [okapi.jkaninda.dev](https://okapi.jkaninda.dev)
- **Issues:** [GitHub Issues](https://github.com/jkaninda/okapi/issues)
- **Discussions:** [GitHub Discussions](https://github.com/jkaninda/okapi/discussions)
- **LinkedIn:** [Jonas Kaninda](https://www.linkedin.com/in/jkaninda/)
---
## License
MIT License - see [LICENSE](LICENSE) for details.
---
**Made with ❤️ for the Go community**
⭐ **Star us on GitHub** — it motivates us to keep improving!
Copyright © 2025 Jonas Kaninda