zenstack-crud-server
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseZenStack V3 — Automatic CRUD Services (Query as a Service)
ZenStack V3 — 自动CRUD服务(查询即服务)
ZenStack turns your data model into secure CRUD web APIs with no hand-written controllers. Two
pieces combine:
- API handler — framework-agnostic; defines the API style (RPC or RESTful) and translates HTTP
requests into ORM queries. From .
@zenstackhq/server/api - Server adapter — framework-specific glue that installs a handler into Express/Next.js/etc. and
supplies a per-request, policy-enforced ORM client. From .
@zenstackhq/server/<framework>
HTTP request → server adapter → API handler (RPC | REST) → ZenStack ORM (policies applied) → DBThis builds on the other skills: define the schema with , secure it with
, and create the base client with . Install the server
package: .
zenstack-schema-modelingzenstack-access-controlzenstack-queryingnpm install @zenstackhq/serverZenStack 可将你的数据模型转换为安全的CRUD Web API,无需编写控制器代码。它由两部分组成:
- API处理器 — 与框架无关;定义API风格(RPC或RESTful)并将HTTP请求转换为ORM查询。来自。
@zenstackhq/server/api - 服务器适配器 — 框架专属的适配层,将处理器安装到Express/Next.js等框架中,并提供每个请求的、经过策略校验的ORM客户端。来自。
@zenstackhq/server/<framework>
HTTP请求 → 服务器适配器 → API处理器(RPC | REST) → ZenStack ORM(应用策略) → 数据库此功能基于其他技能构建:使用定义架构、使用保障安全、使用创建基础客户端。安装服务器包:。
zenstack-schema-modelingzenstack-access-controlzenstack-queryingnpm install @zenstackhq/serverAPI handlers
API处理器
RPC — mirrors the ORM API 1:1. Routes look like and
(body ); responses are wrapped in .
GET /post/findMany?q=<urlencoded-json>POST /post/create{ data: ... }{ data: ... }ts
import { RPCApiHandler } from '@zenstackhq/server/api';
import { schema } from '~/zenstack/schema';
const apiHandler = new RPCApiHandler({ schema });RPC endpoints: every ORM op (, , , , ,
, , , , , , ,
, , ), plus for custom procedures and
. Status codes: create, other success, malformed,
policy violation, not found, validation error, unexpected.
findManyfindUniquefindFirstcountaggregategroupBycreatecreateManycreateManyAndReturnupsertupdateupdateManyupdateManyAndReturndeletedeleteMany$procs/<name>$transaction/sequential201200400403404422500RESTful — JSON:API v1.1 compliant. Routes like , , ,
, , plus relationship routes.
GET /postGET /post/:idPOST /postPUT|PATCH /post/:idDELETE /post/:idts
import { 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,bBoth handlers also accept for slicing/omitting what the API exposes:
queryOptionsts
new RPCApiHandler({
schema,
queryOptions: {
slicing: {
includedModels: ['User', 'Post'],
models: { post: { excludedOperations: ['delete'] } },
},
omit: { user: { password: true } },
},
});RPC — 与ORM API 1:1镜像。路由格式如和(请求体);响应包裹在中。
GET /post/findMany?q=<urlencoded-json>POST /post/create{ data: ... }{ data: ... }ts
import { RPCApiHandler } from '@zenstackhq/server/api';
import { schema } from '~/zenstack/schema';
const apiHandler = new RPCApiHandler({ schema });RPC端点:支持所有ORM操作(, , , , ,
, , , , , , ,
, , ),此外还有自定义过程的和事务接口。状态码:创建成功返回,其他成功返回,请求格式错误返回,策略校验失败返回,资源不存在返回,验证错误返回,意外错误返回。
findManyfindUniquefindFirstcountaggregategroupBycreatecreateManycreateManyAndReturnupsertupdateupdateManyupdateManyAndReturndeletedeleteMany$procs/<name>$transaction/sequential201200400403404422500RESTful — 符合JSON:API v1.1规范。路由格式如, , ,
, ,以及关联关系路由。
GET /postGET /post/:idPOST /postPUT|PATCH /post/:idDELETE /post/:idts
import { RestApiHandler } from '@zenstackhq/server/api'; // 注意:是RestApiHandler,不是RESTful
const apiHandler = new RestApiHandler({
schema,
endpoint: 'http://localhost:3000/api', // 必填 — 用于构建资源链接
});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,b两种处理器都支持配置,用于筛选/隐藏API暴露的内容:
queryOptionsts
new RPCApiHandler({
schema,
queryOptions: {
slicing: {
includedModels: ['User', 'Post'],
models: { post: { excludedOperations: ['delete'] } },
},
omit: { user: { password: true } },
},
});Server adapters — getClient
is where access control lives
getClient服务器适配器 — getClient
是访问控制的核心
getClientEvery adapter takes and a callback. Return a
policy-enforced client bound to the current user via (see ),
so each request is isolated:
apiHandlergetClient(request) => ClientContract$setAuthzenstack-access-controlts
getClient: (req) => authDb.$setAuth(getSessionUser(req)),authDbdb.$use(new PolicyPlugin())authDb.$setAuth(undefined)db每个适配器都接收和回调函数。需返回一个通过绑定当前用户的策略校验客户端(详见),确保每个请求相互隔离:
apiHandlergetClient(request) => ClientContract$setAuthzenstack-access-controlts
getClient: (req) => authDb.$setAuth(getSessionUser(req)),authDbdb.$use(new PolicyPlugin())authDb.$setAuth(undefined)dbExpress
Express
ts
import { ZenStackMiddleware } from '@zenstackhq/server/express';
app.use(express.json());
app.use(
'/api/model',
ZenStackMiddleware({
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
}),
);(Optional writes to and calls instead.)
sendResponse: falseres.localsnext()ts
import { ZenStackMiddleware } from '@zenstackhq/server/express';
app.use(express.json());
app.use(
'/api/model',
ZenStackMiddleware({
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
}),
);(可选配置,会将结果写入并调用,而非直接返回响应。)
sendResponse: falseres.localsnext()Fastify
Fastify
ts
import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
server.register(ZenStackFastifyPlugin, {
prefix: '/api/model', // required
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
});ts
import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';
server.register(ZenStackFastifyPlugin, {
prefix: '/api/model', // 必填
apiHandler,
getClient: (req) => authDb.$setAuth(getSessionUser(req)),
});Next.js — App Router
Next.js — App Router
ts
// 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,
};ts
// 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,
};Next.js — Pages Router
Next.js — Pages Router
ts
// src/pages/api/model/[...path].ts
import { NextRequestHandler } from '@zenstackhq/server/next';
export default NextRequestHandler({
apiHandler,
getClient: (req, res) => authDb.$setAuth(getSessionUser(req, res)),
});ts
// src/pages/api/model/[...path].ts
import { NextRequestHandler } from '@zenstackhq/server/next';
export default NextRequestHandler({
apiHandler,
getClient: (req, res) => authDb.$setAuth(getSessionUser(req, res)),
});Nuxt
Nuxt
ts
// server/api/model/[...].ts
import { createEventHandler } from '@zenstackhq/server/nuxt';
export default createEventHandler({
apiHandler,
getClient: (event) => authDb.$setAuth(getSessionUser(event)),
});ts
// server/api/model/[...].ts
import { createEventHandler } from '@zenstackhq/server/nuxt';
export default createEventHandler({
apiHandler,
getClient: (event) => authDb.$setAuth(getSessionUser(event)),
});SvelteKit (API route — preferred; wildcard param must be named path
)
pathSvelteKit(API路由 — 推荐;通配符参数必须命名为path
)
pathts
// 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;(Legacy in with a option is deprecated.)
SvelteKitHandlerhooks.server.tsprefixts
// 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;(中的旧版及配置已被废弃。)
hooks.server.tsSvelteKitHandlerprefixHono
Hono
ts
import { createHonoHandler } from '@zenstackhq/server/hono';
app.use(
'/api/model/*',
createHonoHandler({
apiHandler,
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
);ts
import { createHonoHandler } from '@zenstackhq/server/hono';
app.use(
'/api/model/*',
createHonoHandler({
apiHandler,
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
);Elysia
Elysia
ts
import { createElysiaHandler } from '@zenstackhq/server/elysia';
app.group('/crud', (app) =>
app.use(
createElysiaHandler({
apiHandler,
basePath: '/api/model',
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
),
);ts
import { createElysiaHandler } from '@zenstackhq/server/elysia';
app.group('/crud', (app) =>
app.use(
createElysiaHandler({
apiHandler,
basePath: '/api/model',
getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
}),
),
);TanStack Start
TanStack Start
ts
// 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,
},
},
});ts
// 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,
},
},
});Data type serialization
数据类型序列化
Handlers use superjson so non-JSON types survive the wire: → ISO string, →
base64, / → string. The generated client SDKs handle this automatically. If you
hand-craft requests, include the superjson under a key
(); responses carry the same.
DateTimeBytesBigIntDecimalmetaserialization{ "data": ..., "meta": { "serialization": <meta> } }处理器使用superjson实现非JSON类型的传输:转换为ISO字符串,转换为base64,/转换为字符串。生成的客户端SDK会自动处理该逻辑。如果手动构造请求,需在键下包含superjson的信息(格式为);响应也会携带相同的信息。
DateTimeBytesBigIntDecimalserializationmeta{ "data": ..., "meta": { "serialization": <meta> } }Consuming the API
API调用
Fetch client (v3.7.0+, RPC APIs only)
Fetch客户端(v3.7.0+,仅支持RPC API)
ts
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' } });Add auth via a custom :
fetchts
import 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 });Custom procedures: / .
Sequential tx: .
client.$procs.getStats.query()client.$procs.send.mutate({ args })client.$transaction([{ model, op, args }, ...])ts
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' } });通过自定义添加认证:
fetchts
import 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 }, ...])TanStack Query hooks (RPC APIs only; React 18+/Vue 3+/Svelte 5.25+)
TanStack Query钩子(仅支持RPC API;React 18+/Vue 3+/Svelte 5.25+)
Install and the matching . Provide settings near the
root (React shown; Vue uses , Svelte ):
@zenstackhq/tanstack-query@tanstack/*-queryprovideQuerySettingsContextsetQuerySettingsContexttsx
import { 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>;Hooks mirror the ORM client via :
useClientQueries(schema)ts
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' } });- Auto invalidation: mutations invalidate affected queries automatically (respects nested
reads/writes and cascades). Opt out per mutation with .
{ invalidateQueries: false } - Optimistic updates: ; customize with
client.post.useCreate({ optimisticUpdate: true }).optimisticUpdateProvider - Also: ,
useInfiniteFindMany,$procs.<name>.useQuery()/useMutation(), and$transaction.useSequential()/DbNull/JsonNullexports for JSON nulls.AnyNull
A community Pinia Colada integration exists as (Vue 3).
zenstack-pinia-colada安装及对应的包。在根组件附近提供配置(以下为React示例;Vue使用,Svelte使用):
@zenstackhq/tanstack-query@tanstack/*-queryprovideQuerySettingsContextsetQuerySettingsContexttsx
import { 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>;通过获取与ORM客户端镜像的钩子:
useClientQueries(schema)ts
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 })自定义逻辑。optimisticUpdateProvider - 其他功能:、
useInfiniteFindMany、$procs.<name>.useQuery()/useMutation(),以及用于JSON空值的$transaction.useSequential()/DbNull/JsonNull导出。AnyNull
社区提供了Pinia Colada集成包(适用于Vue 3)。
zenstack-pinia-coladaOpenAPI spec
OpenAPI规范
Both handlers can emit an OpenAPI spec via (RPC v3.6.0+, REST v3.5.0+):
generateSpec()ts
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);
});Options: (default ), (default ), ,
, (default false). / on the handler
also shape what appears in the spec.
title'ZenStack Generated API'version'1.0.0'descriptionsummaryrespectAccessPoliciesqueryOptions.slicingomit两种处理器都可通过生成OpenAPI规范(RPC从v3.6.0+支持,REST从v3.5.0+支持):
generateSpec()ts
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, // 为受策略保护的模型生成403响应定义
});
res.json(spec);
});配置项:(默认)、(默认)、、、(默认false)。处理器的/配置也会影响规范内容。
title'ZenStack Generated API'version'1.0.0'descriptionsummaryrespectAccessPoliciesqueryOptions.slicingomitReference docs
参考文档
Full ZenStack documentation for this topic is bundled under :
references/- service-overview.md — automatic CRUD service overview
- server-adapter.md — server adapter concepts
- api-handler-overview.md — API handlers overview
- api-handler-rpc.md — RPC API handler
- api-handler-rest.md — RESTful API handler
- client-sdk-overview.md — client SDK overview
- fetch-client.md — fetch client
- tanstack-query.md — TanStack Query hooks
- openapi-overview.md — OpenAPI overview
- openapi-rpc.md — OpenAPI for RPC
- openapi-restful.md — OpenAPI for REST
- api-reference.md — server API reference
- Server adapters: express, fastify, next, nuxt, sveltekit, hono, elysia, tanstack-start
关于此主题的完整ZenStack文档打包在目录下:
references/- service-overview.md — 自动CRUD服务概述
- server-adapter.md — 服务器适配器概念
- api-handler-overview.md — API处理器概述
- api-handler-rpc.md — RPC API处理器
- api-handler-rest.md — RESTful API处理器
- client-sdk-overview.md — 客户端SDK概述
- fetch-client.md — Fetch客户端
- tanstack-query.md — TanStack Query钩子
- openapi-overview.md — OpenAPI概述
- openapi-rpc.md — RPC的OpenAPI规范
- openapi-restful.md — REST的OpenAPI规范
- api-reference.md — 服务器API参考
- 服务器适配器:express、fastify、next、nuxt、sveltekit、hono、elysia、tanstack-start