agentleFS
jkanindajkaninda/okapi-skills/std_compat/SKILL.md

std_compat

Skill3 starsChanged 3 months agoMITView on GitHub

Okapi implements http.Handler and accepts standard handlers and middleware, so a net/http codebase can be migrated incrementally. Available on both *Okapi and *Group: Standard handlers still get the full middleware chain, routing, CORS registration, and OpenAPI entry. Both accessors work: Okapi's router captures parameters into the request context rather than through http.ServeMux, so it copies them onto the request with SetPathValue before invoking a standard handler (HandleStd and HandleHTTP, on the app and on groups alike). That bridge costs one…

The file

Files
SKILL.md

Okapi ↔ net/http Compatibility

Okapi implements http.Handler and accepts standard handlers and middleware, so a net/http codebase can be migrated incrementally.

Okapi as an http.Handler

o := okapi.Default()
o.Get("/hello", handler)

http.ListenAndServe(":8080", o)                // Okapi mounted in net/http
// or hand Okapi a pre-built server
o.StartServer(&http.Server{Addr: ":8080", ReadTimeout: 5 * time.Second})

Registering Standard Handlers

Available on both *Okapi and *Group:

o.HandleStd(method, path string, h func(http.ResponseWriter, *http.Request), opts ...RouteOption)
o.HandleHTTP(method, path string, h http.Handler, opts ...RouteOption)
o.HandleStd("GET", "/greet", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello from Okapi!"))
})

type MyHandler struct{}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { /* ... */ }

o.HandleHTTP("GET", "/custom", &MyHandler{})

// Whole subtree handed to a file server (catch-all segment)
o.HandleHTTP("GET", "/assets/{any...}",
    http.StripPrefix("/assets/", http.FileServer(http.Dir("./public"))))

Standard handlers still get the full middleware chain, routing, CORS registration, and OpenAPI entry.

Path Parameters in a Standard Handler

Both accessors work:

o.HandleStd("GET", "/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")        // works: Okapi copies captured params onto the request
    _ = id
})

import "github.com/jkaninda/njia"

o.HandleStd("GET", "/users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := njia.Param(r, "id")      // reads the router context directly — no map allocation
    _ = id
})

Okapi's router captures parameters into the request context rather than through http.ServeMux, so it copies them onto the request with SetPathValue before invoking a standard handler (HandleStd and HandleHTTP, on the app and on groups alike). That bridge costs one map allocation per request and is paid only by routes that use a standard handler and declare parameters. njia.Param is the allocation-free accessor for new code; r.PathValue exists so an unmodified net/http handler keeps working.

Native Okapi handlers use c.Param("id") / c.PathParam("id").

Standard Middleware Bridge

UseMiddleware accepts the classic func(http.Handler) http.Handler signature on both *Okapi and *Group:

o.UseMiddleware(func(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-Powered-By", "Okapi")
        next.ServeHTTP(w, r)
    })
})

// Third-party middleware plugs in unchanged
import "github.com/gorilla/handlers"

o.UseMiddleware(handlers.CompressHandler)
api := o.Group("/api")
api.UseMiddleware(otelhttp.NewMiddleware("api"))

Handler Comparison

Aspecthttp.HandlerFuncokapi.HandlerFunc
Signaturefunc(http.ResponseWriter, *http.Request)func(*okapi.Context) error
Response writingWrite to w directlyReturn c.OK(...), c.JSON(...), …
Error handlingInline; Okapi cannot capture itReturn the error; Okapi's error handler formats it
Status codesw.WriteHeader(code)Helpers (c.OK, c.Created, c.JSON)
Content typeSet manuallySet by the helper
Path paramsr.PathValue("id") / njia.Param(r, "id")c.Param("id")
Binding & validationParse manuallyc.Bind(&v) with struct-tag validation

Standard handlers get routing and middleware but not *okapi.Context — so binding, validation, and the error-returning signature are unavailable inside them.

Standard Handlers and OpenAPI

Standard handlers are ordinary routes, so they appear in the generated document and accept the same doc options:

o.HandleStd("GET", "/legacy", legacyHandler,
    okapi.DocSummary("A standard handler"),
    okapi.DocTag("legacy"))

Okapi cannot infer request/response schemas for them — declare schemas explicitly with okapi.DocRequestBody(...) / okapi.DocResponse(...), or use okapi.DocHide() to keep the route out of the spec.

Gradual Migration

o := okapi.Default()

// Phase 1: mount existing handlers untouched
o.HandleStd("GET", "/legacy/users", legacyListUsers)

// Phase 2: new endpoints use native handlers
o.Get("/api/v1/users", okapi.HandleO(func(c *okapi.Context) (*UsersOutput, error) {
    return &UsersOutput{Body: users}, nil
}))

// Phase 3: convert a legacy handler
// func legacyListUsers(w http.ResponseWriter, r *http.Request)
// becomes
// func listUsers(c *okapi.Context) error { return c.OK(users) }

Mixed routing is fully supported — standard and native routes coexist in one router with one middleware chain.

Accessing the Underlying Objects

o.Get("/raw", func(c *okapi.Context) error {
    r := c.Request()          // *http.Request
    w := c.Response()         // okapi.ResponseWriter (extends http.ResponseWriter)
    raw := c.ResponseWriter()  // the underlying http.ResponseWriter
    ctx := c.Context()        // request context.Context
    w.Header().Set("X-Custom", "value")
    _, _ = r, raw
    _ = ctx
    return nil
})

Router Note

Since v0.10.0 the router is github.com/jkaninda/njia (Okapi's own router; it replaced the archived gorilla/mux). Routing, path variables, strict-slash redirects, and NotFound/MethodNotAllowed behave as before. The deprecated okapi.WithMuxRouter option now takes a *njia.Router and is a no-op in spirit — Okapi manages its own router and the option will be removed.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.