server middleware overview
Server-side integrations for Express, NestJS, Hono, and Next.js — extract identity, infer action and resource, call engine.can().
How it works
duck-iam ships server integrations for Express, NestJS, Hono, and Next.js. Every one follows the same pattern: extract the user identity, map the HTTP method to an action, infer the resource from the route, and call engine.can().
Every integration is a thin adapter over engine.can(). No runtime framework dependency — duck-iam defines its own minimal type interfaces, so no extra packages sneak in.
Pick a framework
| Framework | Doc | Subpath |
|---|---|---|
| Express | server/express | @gentleduck/iam/server/express |
| Hono | server/hono | @gentleduck/iam/server/hono |
| NestJS | server/nest | @gentleduck/iam/server/nest |
| Next.js App Router | server/next | @gentleduck/iam/server/next |
| Generic helpers | server/generic | @gentleduck/iam/server/generic |
All five share the same building blocks: identity extraction, action mapping, resource inference, scope resolution, and decision callbacks. The framework-specific docs cover each one's idioms.
HTTP method mapping
The default method-to-action map shared by every integration:
const METHOD_ACTION_MAP = {
GET: 'read',
HEAD: 'read',
OPTIONS: 'read',
POST: 'create',
PUT: 'update',
PATCH: 'update',
DELETE: 'delete',
}const METHOD_ACTION_MAP = {
GET: 'read',
HEAD: 'read',
OPTIONS: 'read',
POST: 'create',
PUT: 'update',
PATCH: 'update',
DELETE: 'delete',
}Override per-route or globally with the getAction option.
Common patterns
Combine global + per-route protection
Use global middleware for broad protection, then layer per-route guards for endpoints with different rules.
// Express example
app.use('/api', accessMiddleware(engine, { getUserId: (req) => req.user?.id }))
// Override for a specific route that needs a different scope
app.delete('/api/admin/users/:id',
guard(engine, 'manage', 'user', { scope: 'admin' }),
deleteUserHandler
)// Express example
app.use('/api', accessMiddleware(engine, { getUserId: (req) => req.user?.id }))
// Override for a specific route that needs a different scope
app.delete('/api/admin/users/:id',
guard(engine, 'manage', 'user', { scope: 'admin' }),
deleteUserHandler
)Custom action mapping
Override the default HTTP method mapping for non-standard action patterns.
const middleware = accessMiddleware(engine, {
getUserId: (req) => req.user?.id,
getAction: (req) => {
// POST /api/posts/:id/publish -> "publish" action
if (req.method === 'POST' && req.path.endsWith('/publish')) {
return 'publish'
}
return METHOD_ACTION_MAP[req.method] ?? 'read'
},
})const middleware = accessMiddleware(engine, {
getUserId: (req) => req.user?.id,
getAction: (req) => {
// POST /api/posts/:id/publish -> "publish" action
if (req.method === 'POST' && req.path.endsWith('/publish')) {
return 'publish'
}
return METHOD_ACTION_MAP[req.method] ?? 'read'
},
})Server-driven client permissions
- Generate a
PermissionMapon the server usingengine.permissions()orgetPermissions()(Next.js helper) - Pass the map to the client via props, response body, or server-rendered HTML
- Hydrate
AccessProvider(React), plugin (Vue), orAccessClient(vanilla) on the client - Client-side checks become instant lookups with no extra network requests
const permissions = await engine.permissions(userId, [
{ action: 'create', resource: 'post' },
{ action: 'delete', resource: 'post' },
{ action: 'manage', resource: 'team' },
])const permissions = await engine.permissions(userId, [
{ action: 'create', resource: 'post' },
{ action: 'delete', resource: 'post' },
{ action: 'manage', resource: 'team' },
])See client integrations for hydration on the consumer side.
Default failure responses
| Condition | Status | Override |
|---|---|---|
| Missing user identity | 401 | None — fail closed |
| Engine returns deny | 403 | onDenied |
| Engine throws | 500 | onError |
The engine itself never throws from authorize(). It catches internal errors, calls the onError engine hook, and returns a deny decision. The server integration try/catch is an extra safety net for adapter connection failures during subject resolution.