Skip to content

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.

// API level — applies to all routes
api := shiftapi.New(
shiftapi.WithMiddleware(cors, logging),
)
// Group level — applies to all routes in the group
v1 := api.Group("/api/v1",
shiftapi.WithMiddleware(auth),
)
// Route level — applies to this route only
shiftapi.Handle(v1, "GET /admin", getAdmin,
shiftapi.WithMiddleware(adminOnly),
)

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:

corsloggingauthadminOnly → handler

Within a single WithMiddleware(a, b, c) call, the first argument wraps outermost: a runs first, then b, then c.

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.

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)