@airdraft/next
v0.1.20
Published
Airdraft Next.js App Router integration — route handlers, OpenAPI, revalidation
Readme
@airdraft/next
Next.js adapter for Airdraft. Provides the API route handler, server-side helpers, middleware, and revalidation utilities.
Installation
npm install @airdraft/nextUsage
API Route Handler
Create app/api/cms/[...path]/route.ts:
import { createCmsHandler } from '@airdraft/next'
import { airdraft } from '../../../../airdraft.config'
export const { GET, POST, PATCH, PUT, DELETE } = createCmsHandler(airdraft)createCmsHandler automatically wraps all routes with audit logging when a plugin with onAuditEvent is registered.
Server Client
import { createCmsClient } from '@airdraft/next'
import { airdraft } from '../airdraft.config'
const cms = createCmsClient(airdraft)
const { entries } = await cms.listEntries('posts')Middleware
Protect your CMS UI routes in middleware.ts (not the API routes — those are protected by @airdraft/plugin-auth):
import { withCmsProxy } from '@airdraft/next'
export default withCmsProxy({ basePath: '/api/cms' })
export const config = { matcher: ['/cms/:path*'] }Revalidation
import { revalidateEntry } from '@airdraft/next'
// Call after publishing an entry to trigger ISR
await revalidateEntry({ collection: 'posts', slug: 'hello-world' })Exports
| Export | Description |
|---|---|
| createCmsHandler(config) | Returns { GET, POST, PATCH, PUT, DELETE } Next.js route handlers. |
| createCmsClient(config) | Returns a direct server-side CMS client (no HTTP). Use in Server Components and generateStaticParams. |
| withCmsProxy(options) | Next.js middleware that proxies CMS requests to the configured basePath. |
| revalidateEntry(options) | Triggers Next.js ISR revalidation for an entry path. |
| createTokenProvider() | Token provider for client-side auth flows. |
Server Client
import { createCmsClient } from '@airdraft/next'
import { airdraft } from '../airdraft.config'
const cms = createCmsClient(airdraft)
// List with pagination
const { entries, total, page, pages, hasNext } = await cms.listEntries('posts', {
status: 'published',
limit: 10,
page: 2,
})
// Single entry with sibling navigation
const post = await cms.getEntry('posts', 'hello-world', { siblings: true })
// post.wordCount, post.readTime, post.prev, post.nextHTTP API
System routes
| Route | Description |
|---|---|
| GET /health | Health check. Returns { data: { status: 'ok', timestamp: '...' } }. |
| GET /schema | Full CmsSchema describing all collections and fields. |
| GET /openapi.json | OpenAPI 3.0.0 spec auto-generated from the runtime collection schemas. |
| GET /docs | Scalar API reference UI, sourced from /openapi.json. |
Query parameters:
| Param | Description |
|---|---|
| limit | Max entries per page (capped at 100). |
| page | 1-based page number. Converted to offset = (page-1)*limit. Takes precedence over offset. |
| offset | Zero-based offset (alternative to page). |
| sort[field] | Field name to sort by (e.g. sort[field]=createdAt). |
| sort[order] | Sort direction: asc or desc. |
| status | published | draft | all (default). |
| expand | Comma-separated relation field names to expand inline. |
| filter[field] | Equality filter on any field. |
Response shape:
{
"data": [
{ "slug": "...", "title": "...", "wordCount": 1432, "readTime": "7 min read", "_sha": "..." }
],
"meta": {
"total": 42,
"page": 1,
"pages": 5,
"hasNext": true,
"hasPrev": false,
"offset": 0,
"limit": 10
}
}GET /{collection}/{slug}
Query parameters:
| Param | Description |
|---|---|
| expand | Comma-separated relation field names to expand inline. |
| siblings | Set to true or 1 to include prev/next sibling stubs in meta. |
| siblings[sort] | Field name to sort the sibling pool by. |
| siblings[order] | asc | desc (default desc). |
| siblings[status] | Filter sibling pool by publish status. |
| siblings[fields] | Comma-separated fields to include in sibling stubs. |
| siblings[filter][field] | Equality filter on the sibling pool. |
Response shape:
{
"data": { "slug": "...", "title": "...", "body": "..." },
"meta": {
"sha": "...",
"wordCount": 1432,
"readTime": "7 min read",
"prev": { "slug": "prev-post", "data": { "title": "Prev Post" }, "wordCount": 980, "readTime": "5 min read" },
"next": null
}
}Audit Logging
When any plugin registers an onAuditEvent hook, createHandler wraps every route with auditWrap. Each request produces an AuditEvent that includes:
action,method,path,statusCode,durationMscollection,slug(when applicable)actor— the raw API key / bearer tokenerror.message,error.code,error.details— per-field validation failures are included indetailsforVALIDATION_ERRORresponses
Response Transform
After each successful response, createHandler runs all plugin transformResponse hooks in registration order. This is used by @airdraft/plugin-auth to transparently append refreshed Set-Cookie headers when a server-side silent token rotation occurs (expired access token + valid refresh cookie). Auth errors (401/403) bypass the transform pipeline.
Changelog
Breaking change (v0.1.14): Sort query parameters changed from flat
?sort=field&order=ascto bracket notation?sort[field]=field&sort[order]=ascto be consistent with thefilter[field]convention. Update any API consumers before upgrading.
See CHANGELOG.md.
