AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Zenstack Crud Server

skill-zenstackhq-skills-zenstack-crud-server · by zenstackhq

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).

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add skill-zenstackhq-skills-zenstack-crud-server

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-zenstackhq-skills-zenstack-crud-server)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Zenstack Crud Server? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

ZenStack V3 — Automatic CRUD Services (Query as a Service)

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/.

HTTP request → server adapter → API handler (RPC | REST) → ZenStack ORM (policies applied) → DB

This builds on the other skills: define the schema with zenstack-schema-modeling, secure it with zenstack-access-control, and create the base client with zenstack-querying. Install the server package: npm install @zenstackhq/server.

API handlers

RPC — mirrors the ORM API 1:1. Routes look like GET /post/findMany?q= and POST /post/create (body { data: ... }); responses are wrapped in { data: ... }.

import { RPCApiHandler } from '@zenstackhq/server/api';
import { schema } from '~/zenstack/schema';

const apiHandler = new RPCApiHandler({ schema });

RPC endpoints: every ORM op (findMany, findUnique, findFirst, count, aggregate, groupBy, create, createMany, createManyAndReturn, upsert, update, updateMany, updateManyAndReturn, delete, deleteMany), plus $procs/ for custom procedures and $transaction/sequential. Status codes: 201 create, 200 other success, 400 malformed, 403 policy violation, 404 not found, 422 validation error, 500 unexpected.

RESTful — JSON:API v1.1 compliant. Routes like GET /post, GET /post/:id, POST /post, PUT|PATCH /post/:id, DELETE /post/:id, plus relationship routes.

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
});

RestApiHandler options: endpoint (required), pageSize (default 100; Infinity disables paging), modelNameMapping ({ User: 'users' }), externalIdMapping ({ Tag: 'name' }), nestedRoutes (bool). REST query params: filter[field]=, filter[field$op]= ($lt,$gt, $contains,$startsWith,…), sort=field,-other, page[offset]=/page[limit]=, include=rel,rel.nested, fields[type]=a,b.

Both handlers also accept queryOptions for slicing/omitting what the API exposes:

new RPCApiHandler({
    schema,
    queryOptions: {
        slicing: {
            includedModels: ['User', 'Post'],
            models: { post: { excludedOperations: ['delete'] } },
        },
        omit: { user: { password: true } },
    },
});

Server adapters — getClient is where access control lives

Every adapter takes apiHandler and a getClient(request) => ClientContract callback. Return a policy-enforced client bound to the current user via $setAuth (see zenstack-access-control), so each request is isolated:

getClient: (req) => authDb.$setAuth(getSessionUser(req)),

authDb is db.$use(new PolicyPlugin()). Return authDb.$setAuth(undefined) for anonymous, or the raw db to bypass policies (rarely what you want for a public API).

Express

import { ZenStackMiddleware } from '@zenstackhq/server/express';

app.use(express.json());
app.use(
    '/api/model',
    ZenStackMiddleware({
        apiHandler,
        getClient: (req) => authDb.$setAuth(getSessionUser(req)),
    }),
);

(Optional sendResponse: false writes to res.locals and calls next() instead.)

Fastify

import { ZenStackFastifyPlugin } from '@zenstackhq/server/fastify';

server.register(ZenStackFastifyPlugin, {
    prefix: '/api/model', // required
    apiHandler,
    getClient: (req) => authDb.$setAuth(getSessionUser(req)),
});

Next.js — App Router

// 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

// 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

// 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)

// 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 SvelteKitHandler in hooks.server.ts with a prefix option is deprecated.)

Hono

import { createHonoHandler } from '@zenstackhq/server/hono';

app.use(
    '/api/model/*',
    createHonoHandler({
        apiHandler,
        getClient: (ctx) => authDb.$setAuth(getSessionUser(ctx)),
    }),
);

Elysia

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

// 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: DateTime → ISO string, Bytes → base64, BigInt/Decimal → string. The generated client SDKs handle this automatically. If you hand-craft requests, include the superjson meta under a serialization key ({ "data": ..., "meta": { "serialization": } }); responses carry the same.

Consuming the API

Fetch client (v3.7.0+, RPC APIs only)

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 fetch:

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: client.$procs.getStats.query() / client.$procs.send.mutate({ args }). Sequential tx: client.$transaction([{ model, op, args }, ...]).

TanStack Query hooks (RPC APIs only; React 18+/Vue 3+/Svelte 5.25+)

Install @zenstackhq/tanstack-query and the matching @tanstack/*-query. Provide settings near the root (React shown; Vue uses provideQuerySettingsContext, Svelte setQuerySettingsContext):

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { QuerySettingsProvider } from '@zenstackhq/tanstack-query/react';

    
        {children}
    
;

Hooks mirror the ORM client via 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' } });
  • Auto invalidation: mutations invalidate affected queries automatically (respects nested

reads/writes and cascades). Opt out per mutation with { invalidateQueries: false }.

  • Optimistic updates: client.post.useCreate({ optimisticUpdate: true }); customize with

optimisticUpdateProvider.

  • Also: useInfiniteFindMany, $procs..useQuery()/useMutation(),

$transaction.useSequential(), and DbNull/JsonNull/AnyNull exports for JSON nulls.

A community Pinia Colada integration exists as zenstack-pinia-colada (Vue 3).

OpenAPI spec

Both handlers can emit an OpenAPI spec via generateSpec() (RPC v3.6.0+, REST v3.5.0+):

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: title (default 'ZenStack Generated API'), version (default '1.0.0'), description, summary, respectAccessPolicies (default false). queryOptions.slicing/omit on the handler also shape what appears in the spec.

Reference docs

Full ZenStack documentation for this topic is bundled under [references/](references/):

  • [service-overview.md](references/service-overview.md) — automatic CRUD service overview
  • [server-adapter.md](references/server-adapter.md) — server adapter concepts
  • [api-handler-overview.md](references/api-handler-overview.md) — API handlers overview
  • [api-handler-rpc.md](references/api-handler-rpc.md) — RPC API handler
  • [api-handler-rest.md](references/api-handler-rest.md) — RESTful API handler
  • [client-sdk-overview.md](references/client-sdk-overview.md) — client SDK overview
  • [fetch-client.md](references/fetch-client.md) — fetch client
  • [tanstack-query.md](references/tanstack-query.md) — TanStack Query hooks
  • [openapi-overview.md](references/openapi-overview.md) — OpenAPI overview
  • [openapi-rpc.md](references/openapi-rpc.md) — OpenAPI for RPC
  • [openapi-restful.md](references/openapi-restful.md) — OpenAPI for REST
  • [api-reference.md](references/api-reference.md) — server API reference
  • Server adapters: [express](references/adapter-express.md), [fastify](references/adapter-fastify.md), [next](references/adapter-next.md), [nuxt](references/adapter-nuxt.md), [sveltekit](references/adapter-sveltekit.md), [hono](references/adapter-hono.md), [elysia](references/adapter-elysia.md), [tanstack-start](references/adapter-tanstack-start.md)

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.