Skip to Content
DocsNext.js integrationRoute handlers

Route handlers

@quickben/next serves every collection over REST from one catch-all route file. It also has helpers for server components and server actions.

pnpm add @quickben/next
app/api/[...quickben]/route.ts
import { createRouteHandlers } from "@quickben/next"; import config from "../../../quickben.config"; export const { GET, POST, PATCH, DELETE } = createRouteHandlers(config);

No next.config wrapper is needed.

Routes

RouteLocal API callSuccess
GET /_metaCollections the user can read200
GET /:collectionqb.find200
GET /:collection/:idqb.findById200
POST /:collectionqb.create201
PATCH /:collection/:idqb.update200
DELETE /:collection/:idqb.delete (returns the deleted document)200
  • The user comes from config.auth.getUser(req) on every request. Access rules and hooks run as in the local API.
  • Bodies must be JSON objects sent with Content-Type: application/json. id, createdAt and updatedAt are removed from bodies.
  • Responses have Cache-Control: private, no-store.

Options

app/api/[...quickben]/route.ts
import { createRouteHandlers } from "@quickben/next"; import config from "../../../quickben.config"; export const { GET, POST, PATCH, DELETE } = createRouteHandlers(config, { basePath: "/api", maxLimit: 100, csrf: { trustedOrigins: ["https://app.example.com"] }, });
OptionDefaultPurpose
basePath/apiURL path of the catch-all route.
maxLimit100Largest limit of GET /:collection.
csrf{}Same-origin check on mutating methods. { trustedOrigins } or false.

Query string

ParameterFormatExample
whereJSON, same shape as the local APIwhere={"status":{"eq":"draft"}}
sortComma-separated keys, - for descendingsort=-publishedAt,title
limitInteger, at most maxLimitlimit=50
pageInteger, from 1page=2
cursornextCursor of the previous resultcursor=…
depthInteger 0–3depth=1
selectComma-separated field namesselect=title,status
  • GET /:collection/:id accepts depth and select.
  • Unknown parameters return 400 VALIDATION.
  • where is JSON so numbers, booleans and arrays keep their types. where[views][gte]=10 is not supported.
curl -G http://localhost:3000/api/posts \ --data-urlencode 'where={"status":{"eq":"published"}}' \ --data-urlencode 'sort=-createdAt' \ --data-urlencode 'select=title,status'

In client code, toSearchParams builds the query string:

import { toSearchParams } from "@quickben/next"; const params = toSearchParams({ where: { status: { eq: "draft" } }, sort: "-createdAt", limit: 20, }); const res = await fetch(`/api/posts?${params.toString()}`);

Errors

Every error has the same body:

{ "error": { "code": "VALIDATION", "message": "Validation failed. title: Required", "details": { "issues": [{ "path": "title", "message": "Required" }] } } }
CodeStatusWhendetails
VALIDATION400Invalid body or query string{ issues: [{ path, message }] }
UNAUTHORIZED401Access rule denies and there is no usernull
FORBIDDEN403Access rule denies a signed-in user; CSRF check failsnull
NOT_FOUND404Unknown document, collection or routenull
METHOD_NOT_ALLOWED405For example POST /posts/:id. Has an Allow header.null
CONFLICT409Unique field already used{ field }
UNSUPPORTED_MEDIA_TYPE415Body without Content-Type: application/jsonnull
CONFIG500Config error, for example devAuth() in productionnull
INTERNAL500Any other error. It is logged, and not returned.null

errorResponse(error) builds the same response in your own route handlers.

CSRF

POST, PATCH and DELETE check where the request comes from:

Request hasResult
An Origin headerAllowed when its host is the request host (Host or X-Forwarded-Host) or in trustedOrigins.
No Origin, Sec-Fetch-SiteRejected when the value is cross-site.
Neither header (curl, servers)Allowed.

A rejected request returns 403 FORBIDDEN. The JSON content type also forces a CORS preflight for cross-site fetch calls.

Setupcsrf option
App and API on one hostDefault ({})
Front end on another origin{ trustedOrigins: ["https://app.example.com"] }
Token auth that never uses cookiesfalse

GET /_meta

Returns the collections and modules the user can read: fields, labels, admin options and access: { create, update, delete }.

  • Collections whose read rule returns false are left out. A rule that returns a filter counts as allowed.
  • Functions (validate, function defaults) are not included.

Server helpers

ExportPurpose
getQuickben(config)Cached instance. The cache is on globalThis, so every bundle and dev reload shares it.
getRequestContext(config){ user, req } for local API calls in server components and server actions.
getUser(config)The current user, or null.
currentRequest()A Request built from headers() and cookies() of next/headers.
singleton(key, create)One value per process. Use it for database clients created in the config.
getMeta(qb, ctx)The GET /_meta body.

In Next.js apps, use getQuickben from @quickben/next, not from @quickben/core. The core version caches per module copy, and Next.js can load a module more than once.

Server component

app/posts/page.tsx
import { getQuickben, getRequestContext } from "@quickben/next"; import config from "../../quickben.config"; export default async function PostsPage() { const qb = await getQuickben(config); const posts = await qb.find("posts", { limit: 10 }, await getRequestContext(config)); return ( <ul> {posts.docs.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }

Server action

app/posts/actions.ts
"use server"; import { getQuickben, getRequestContext } from "@quickben/next"; import config from "../../quickben.config"; export async function createPost(formData: FormData) { const qb = await getQuickben(config); const title = String(formData.get("title")); await qb.create("posts", { title }, await getRequestContext(config)); }

currentRequest() reads cookies from cookies(). A cookie set earlier in the same server action, such as a sign-in cookie, is visible to the auth adapter.

Database client

db: singleton("db", () => drizzleAdapter({ dialect: "pglite", dataDir: "./.pglite" })),

Limits

Cache Components  (cacheComponents: true) is not supported yet. Pages that call the local API fail to prerender. Create apps with create-next-app --no-cache-components, or remove cacheComponents from next.config.ts.

Last updated on