Middleware
ShiftAPI uses standard func(http.Handler) http.Handler middleware — the same signature used by the Go ecosystem. Use WithMiddleware to apply middleware at any level.
Applying middleware
Section titled “Applying middleware”// API level — applies to all routesapi := shiftapi.New( shiftapi.WithMiddleware(cors, logging),)
// Group level — applies to all routes in the groupv1 := api.Group("/api/v1", shiftapi.WithMiddleware(auth),)
// Route level — applies to this route onlyshiftapi.Handle(v1, "GET /admin", getAdmin, shiftapi.WithMiddleware(adminOnly),)Resolution order
Section titled “Resolution order”Middleware resolves from outermost to innermost:
API → parent Group → child Group → Route → handler
In the example above, a request to GET /api/v1/admin passes through:
cors → logging → auth → adminOnly → handler
Within a single WithMiddleware(a, b, c) call, the first argument wraps outermost: a runs first, then b, then c.
Passing data to handlers
Section titled “Passing data to handlers”Use NewContextKey, SetContext, and FromContext to pass typed values from middleware to handlers — no untyped context.Value keys or type assertions needed:
type User struct { ID int Name string}
var userKey = shiftapi.NewContextKey[User]("user")
func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, err := authenticate(r) if err != nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, shiftapi.SetContext(r, userKey, user)) })}
shiftapi.Handle(authed, "GET /me", func(r *http.Request, _ struct{}) (*Profile, error) { user, ok := shiftapi.FromContext(r, userKey) if !ok { return nil, fmt.Errorf("missing user context") } return &Profile{Name: user.Name}, nil})Each ContextKey has pointer identity, so two keys for the same type never collide. The type parameter ensures SetContext and FromContext agree on the value type at compile time.
Example: CORS + auth
Section titled “Example: CORS + auth”func corsMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") next.ServeHTTP(w, r) })}
func authMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("Authorization") if token == "" { http.Error(w, "unauthorized", http.StatusUnauthorized) return } next.ServeHTTP(w, r) })}
api := shiftapi.New( shiftapi.WithMiddleware(corsMiddleware),)
authed := api.Group("/api", shiftapi.WithMiddleware(authMiddleware),)
shiftapi.Handle(authed, "GET /me", getMe)