Loading...
Loading...
Expose ZenStack V3 data as automatic CRUD web APIs ("Query as a Service") and consume them. Use when adding REST/RPC endpoints with a server adapter (Express, Fastify, Next.js, Nuxt, SvelteKit, Hono, Elysia, TanStack Start), wiring per-request access control via getClient/$setAuth, generating an OpenAPI spec, or calling the API from a client (fetch-client, TanStack Query hooks).
npx skill4agent add zenstackhq/skills zenstack-crud-server@zenstackhq/server/api@zenstackhq/server/<framework>HTTP request → server adapter → API handler (RPC | REST) → ZenStack ORM (policies applied) → DBzenstack-schema-modelingzenstack-access-controlzenstack-queryingnpm install @zenstackhq/serverGET /post/findMany?q=<urlencoded-json>POST /post/create{ data: ... }{ data: ... }import { RPCApiHandler } from '@zenstackhq/server/api';
import { schema } from '~/zenstack/schema';
const apiHandler = new RPCApiHandler({ schema });findManyfindUniquefindFirstcountaggregategroupBycreatecreateManycreateManyAndReturnupsertupdateupdateManyupdateManyAndReturndeletedeleteMany$procs/<name>$transaction/sequential201200400403404422500GET /postGET /post/:idPOST /postPUT|PATCH /post/:idDELETE /post/:idimport { RestApiHandler } from '@zenstackhq/server/api'; // note: RestApiHandler, not RESTful
const apiHandler = new RestApiHandler({
schema,
endpoint: 'http://localhost:3000/api', // required — used to build resource links
});RestApiHandlerendpointpageSizeInfinitymodelNameMapping{ User: 'users' }externalIdMapping{ Tag: 'name' }nestedRoutesfilter[field]=filter[field$op]=$lt$gt$contains$startsWithsort=field,-otherpage[offset]=page[limit]=include=rel,rel.nestedfields[type]=a,bqueryOptionsnew RPCApiHandler({
schema,
queryOptions: {
slicing: {
includedModels: ['User', 'Post'],
models: { post: { excludedOperations: ['delete'] } },
},
omit: { user: { password: true } },
},
});getClientapiHandlergetClient(request) => ClientContract$setAuthzenstack-access-controlgetClient: (req) => authDb.$setAuth(getSessionUser(req)),authDbdb.$use(new PolicyPlugin())authDb.$setAuth(undefined)dbimport { ZenStackMiddleware } from '@zenstackhq/server/express';
app.use(express.json());
app.use(
'/api/model',
ZenStackMiddleware({
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
}),
);sendResponse: falseres.localsnext()import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
server.register(ZenStackFastifyPlugin, {
prefix: '/api/model', // required
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
});// src/app/api/model/[...path]/route.ts
import { NextRequestHandler } from '@zenstackhq/server/next';
const handler = NextRequestHandler({
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
useAppDir: true,
});
export {
handler as GET,
handler as POST,
handler as PUT,
handler as PATCH,
handler as DELETE,
};// src/pages/api/model/[...path].ts
import { NextRequestHandler } from '@zenstackhq/server/next';
export default NextRequestHandler({
apiHandler,
getClient: (req, res) => authDb.$setAuth(getSessionUser(req, res)),
});// server/api/model/[...].ts
import { createEventHandler } from '@zenstackhq/server/nuxt';
export default createEventHandler({
apiHandler,
getClient: (event) => authDb.$setAuth(getSessionUser(event)),
});path// src/routes/api/model/[...path]/+server.ts
import { SvelteKitRouteHandler } from '@zenstackhq/server/sveltekit';
const handler = SvelteKitRouteHandler({
apiHandler,
getClient: (event) => authDb.$setAuth(getSessionUser(event)),
});
export const GET = handler,
POST = handler,
PUT = handler,
PATCH = handler,
DELETE = handler;SvelteKitHandlerhooks.server.tsprefiximport { createHonoHandler } from '@zenstackhq/server/hono';
app.use(
'/api/model/*',
createHonoHandler({
apiHandler,
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
);import { createElysiaHandler } from '@zenstackhq/server/elysia';
app.group('/crud', (app) =>
app.use(
createElysiaHandler({
apiHandler,
basePath: '/api/model',
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
),
);// app/routes/api/$.ts
import { TanStackStartHandler } from '@zenstackhq/server/tanstack-start';
const handler = TanStackStartHandler({
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
});
export const Route = createFileRoute('/api/$')({
server: {
handlers: {
GET: handler,
POST: handler,
PUT: handler,
PATCH: handler,
DELETE: handler,
},
},
});DateTimeBytesBigIntDecimalmetaserialization{ "data": ..., "meta": { "serialization": <meta> } }import { createClient } from '@zenstackhq/fetch-client';
import { schema } from '~/zenstack/schema';
const client = createClient(schema, {
endpoint: 'https://example.com/api/model',
});
const users = await client.user.findMany({ include: { posts: true } });
await client.post.create({ data: { title: 'Hello' } });fetchimport type { FetchFn } from '@zenstackhq/fetch-client';
const fetchFn: FetchFn = (url, init) =>
fetch(url, {
...init,
headers: { ...init?.headers, authorization: `Bearer ${getToken()}` },
});
createClient(schema, { endpoint, fetch: fetchFn });client.$procs.getStats.query()client.$procs.send.mutate({ args })client.$transaction([{ model, op, args }, ...])@zenstackhq/tanstack-query@tanstack/*-queryprovideQuerySettingsContextsetQuerySettingsContextimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { QuerySettingsProvider } from '@zenstackhq/tanstack-query/react';
<QueryClientProvider client={new QueryClient()}>
<QuerySettingsProvider value={{ endpoint: '/api/model' }}>
{children}
</QuerySettingsProvider>
</QueryClientProvider>;useClientQueries(schema)import { useClientQueries } from '@zenstackhq/tanstack-query/react';
const client = useClientQueries(schema);
const { data } = client.user.useFindMany({ include: { posts: true } });
const create = client.post.useCreate();
create.mutate({ data: { title: 'New post' } });{ invalidateQueries: false }client.post.useCreate({ optimisticUpdate: true })optimisticUpdateProvideruseInfiniteFindMany$procs.<name>.useQuery()/useMutation()$transaction.useSequential()DbNullJsonNullAnyNullzenstack-pinia-coladagenerateSpec()import type { OpenApiSpecOptions } from '@zenstackhq/server/api';
app.get('/api/openapi.json', async (_req, res) => {
const spec = await apiHandler.generateSpec({
title: 'My Blog API',
version: '2.0.0',
respectAccessPolicies: true, // emit 403 responses for policy-protected models
});
res.json(spec);
});title'ZenStack Generated API'version'1.0.0'descriptionsummaryrespectAccessPoliciesqueryOptions.slicingomitreferences/