Skip to content

Philosophy

Kori's design is guided by a small set of principles. Understanding them helps explain why the API looks the way it does and what tradeoffs were made deliberately.

Explicit over magic

Go's net/http is explicit by design. Kori preserves that.

Every Kori handler has the same signature:

go
func(w http.ResponseWriter, r *http.Request) error

There is no context injection, no custom Context type, no request/response wrappers. The w and r you already know are still the only way to read the request and write the response.

This means any Go developer can read a Kori handler without learning new abstractions, and any net/http-compatible tool — middleware, test helpers, tracing libraries — continues to work without changes.

Built on standards

Kori does not reinvent what already exists. It connects existing, well-maintained pieces:

LayerProvided by
HTTP server and handler interfacenet/http (stdlib)
Routing and middlewareChi
Struct validationgo-playground/validator
OpenAPI 3.1 spec generationkori/openapi

Each dependency was chosen because it is stable, idiomatic, and already widely used in the Go ecosystem. Kori's job is to wire them together cleanly, not to replace them.

Small and focused

Kori deliberately avoids becoming a full-stack framework.

It is not:

  • an ORM or database layer
  • a dependency injection container
  • a configuration loader
  • a custom HTTP runtime
  • a monolithic framework that owns the application lifecycle

The surface area is intentionally small. A smaller toolkit is easier to understand, easier to upgrade, and easier to move away from if your requirements change.

Composable

Every feature in Kori can be adopted independently. There is no required initialization step and no global state that must be configured before the rest works.

You can use:

  • Only bindingBindJSON, BindQuery, BindPath on any *http.Request
  • Only validation — validation runs automatically inside every Bind* call
  • Only response helpersJSON, Text, NoContent on any http.ResponseWriter
  • Only error helpersNotFound, BadRequest, Conflict, and the HTTPError type work independently
  • Only OpenAPIkori/openapi is a separate module; it only adds what you import
  • Only SSENewSSEWriter works with any handler

You are not required to adopt a specific project structure or initialize a central application object. Kori functions are plain functions — call the ones you need.

Extensible through Options

Kori's extension point is the Option type:

go
type Option func(*RouteInfo)

An Option receives the method and pattern of the route being registered. This is how kori/openapi attaches OpenAPI metadata — as an Option passed at route registration time — without any separate registration step.

You can write your own Option for route logging, permission metadata, or any other cross-cutting concern:

go
func LogRoute(log *slog.Logger) kori.Option {
    return func(ri *kori.RouteInfo) {
        log.Info("route registered",
            "method", ri.Method,
            "pattern", ri.Pattern,
        )
    }
}

kori.GET(r, "/users", listUsers, LogRoute(log))

Options compose naturally — pass as many as needed, in any order.