Options
ShiftAPI uses a layered option system. Options configure the API, groups, and individual routes.
Option types
Section titled “Option types”Each option type is named for the method it can be passed to.
| Type | Where it works | Examples |
|---|---|---|
Option | New, Group, and every route method | WithError, WithMiddleware, WithResponseHeader |
APIOption | New | WithInfo, WithLogger, WithBadRequestError, WithInternalServerError |
GroupOption | Group | (all Option values) |
RouteOption | Handle, HandleRaw, HandleSSE, HandleWS | WithRouteInfo |
HandleOption | Handle, HandleRaw | WithStatus, WithContentType |
SSEOption | HandleSSE | SSESends |
WSOption | HandleWS | WithWSAcceptOptions |
Option is the widest: it satisfies every other interface, so WithError and WithMiddleware work everywhere. RouteOption sits one level down, covering all four route methods but not New or Group. The narrow types are restricted at compile time, so passing SSESends to Handle fails to build.
Composing options
Section titled “Composing options”Use ComposeOptions to bundle multiple Option values into a single reusable option that works at any level:
func WithAuth() shiftapi.Option { return shiftapi.ComposeOptions( shiftapi.WithMiddleware(authMiddleware), shiftapi.WithError[*AuthError](http.StatusUnauthorized), )}
// Works at any levelapi := shiftapi.New(WithAuth())v1 := api.Group("/api/v1", WithAuth())v1.Handle("GET /secret", getSecret, WithAuth())Level-specific composition
Section titled “Level-specific composition”To mix shared options with level-specific options, use the corresponding compose function:
| Function | Accepts | Returns |
|---|---|---|
ComposeOptions | ...Option | Option |
ComposeAPIOptions | ...APIOption | APIOption |
ComposeGroupOptions | ...GroupOption | GroupOption |
ComposeHandleOptions | ...HandleOption | HandleOption |
Since Option implements all level interfaces, you can pass it to any of the level-specific compose functions alongside level-specific options:
createOpts := shiftapi.ComposeHandleOptions( shiftapi.WithStatus(http.StatusCreated), // HandleOption shiftapi.WithError[*ConflictError](http.StatusConflict), // Option (also a HandleOption))
api.Handle("POST /users", createUser, createOpts)Route groups
Section titled “Route groups”Use Group to register routes under a common path prefix with shared options:
v1 := api.Group("/api/v1", shiftapi.WithError[*RateLimitError](http.StatusTooManyRequests), shiftapi.WithMiddleware(auth),)
v1.Handle("GET /users", listUsers) // GET /api/v1/usersv1.Handle("POST /users", createUser) // POST /api/v1/usersGroups can be nested. Error types and middleware are inherited by child groups:
admin := v1.Group("/admin", shiftapi.WithError[*ForbiddenError](http.StatusForbidden), shiftapi.WithMiddleware(adminOnly),)admin.Handle("GET /stats", getStats) // GET /api/v1/admin/stats// inherits RateLimitError (429) + auth middleware from v1// adds ForbiddenError (403) + adminOnly middlewareOptions merge across all three levels: New() (API-wide) → Group() (group-level) → route registration (route-level).
Splitting routes across files
Section titled “Splitting routes across files”A group is also the unit of route composition. A function that registers routes
should take a *shiftapi.Group, leaving the prefix and shared options to the
caller:
func UserRoutes(g *shiftapi.Group) { g.Handle("GET /users", listUsers) g.Handle("POST /users", createUser)}
UserRoutes(api.Group("/api/v1")) // GET /api/v1/users, POST /api/v1/usersPass api.Group("") to mount at the root. Take *shiftapi.Group rather than
*shiftapi.API in these functions. Go does not allow generic methods on
interfaces, so no single interface covers both types, and a group works
everywhere an API does.