Express.js Adapter
The @zenstackhq/server/express
module provides a quick way to install API routes onto a Express.js project for database CRUD operations. Combined with ZenStack's power of enhancing Prisma with access policies, it's surprisingly simple to achieve a secure data backend without manually coding it.
Installation
npm install @zenstackhq/server
Mounting the API
You can integrate ZenStack into your project with the ZenStackMiddleware
express middleware:
import { PrismaClient } from '@prisma/client';
import { enhance } from '@zenstackhq/runtime';
import { ZenStackMiddleware } from '@zenstackhq/server/express';
import express from 'express';
const prisma = new PrismaClient();
const app = express();
app.use(express.json());
app.use(
'/api/model',
ZenStackMiddleware({
// getSessionUser extracts the current session user from the request, its
// implementation depends on your auth solution
getPrisma: (request) => enhance(prisma, { user: getSessionUser(request) }),
})
);
The Express.js adapter takes the following options to initialize:
-
getPrisma (required)
(request: Request, response: Response) => unknown | Promise<unknown>
A callback for getting a PrismaClient instance for talking to the database. Usually you'll use an enhanced instance created with ZenStack's
enhance
API to ensure access policies are enforced. -
logger (optional)
LoggerConfig
Configuration for customizing logging behavior.
-
modelMeta (optional)
ModelMeta
Model metadata. By default loaded from the
node_module/.zenstack/model-meta
module. You can pass it in explicitly if you configured ZenStack to output to a different location. E.g.:require('output/model-meta').default
. -
zodSchemas (optional)
ModelZodSchema | boolean | undefined
Provides the Zod schemas used for validating CRUD request input. The Zod schemas can be generated with the
@core/zod
plugin. Passtrue
for this option to load the schemas from the default location. If you configured@core/zod
plugin to output to a custom location, you can load the schemas explicitly and pass the loaded module to this option. E.g.:factory({
...
zodSchemas: require('./zod'),
});Not passing this option or passing in
undefined
disables input validation. -
useSuperJson (optional)
boolean
Whether to use superjson for data serialization. Defaults to
false
.NOTE: This options is decprecated. The server adapters now always use superjson for data serialization.
-
handler (optional)
(req: RequestContext) => Promise<Response>
The request handler function. This option determines the API endpoints and its input and output formats. Currently ZenStack supports two styles of APIs: RPC (the default) and RESTful.
-
RPC
The goal of the RPC-style API handler is to fully mirror PrismaClient's API across the network, so that developers can continue enjoying the convenience and flexibility of Prisma's query syntax. This is the default choice for the
handler
option.The RPC-style handler can be created like:
import RPCApiHandler from '@zenstackhq/server/api/rpc';
const handler = RPCApiHandler();For more details, please check out RPC API Handler.
-
RESTful
The goal of RESTful-style API handler is to provide a resource-centric RESTful API using JSON:API as transportation format.
The RESTful-style handler can be created like:
import RestApiHandler from '@zenstackhq/server/api/rest';
const handler = RestApiHandler({ endpoint: 'http://myhost/api' });For more details, please check out RESTful API Handler.
-
-
sendResponse (optional)
boolean
Controls if the middleware directly sends a response. If set to false, the response is stored in the
res.locals
object and then the middleware calls thenext()
function to pass the control to the next middleware. Subsequent middleware or request handlers need to make sure to send a response.Defaults to
true
.
Using the API
The APIs can be used in the following three ways:
-
With generated client hooks
ZenStack provides plugins to generate client hooks from the ZModel targeting the most popular frontend data fetching libraries: TanStack Query and SWR. The generated hooks can be used to make API calls to the server adapters. Refer to the follow docs for detailed usage:
infoThe generated client hooks assumes the server adapter uses RPC-style API handler (which is the default setting).
-
With direct HTTP calls
You can make direct HTTP calls to the server adapter using your favorite client libraries like
fetch
oraxios
. Refer to the documentation of the API Handlers for the API endpoints and data formats.Here's an example using
fetch
:- RPC Handler
- RESTful Handler
// create a user with two posts
const r = await fetch(`/api/user/create`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
include: { posts: true },
data: {
email: 'user1@abc.com',
posts: {
create: [{ title: 'Post 1' }, { title: 'Post 2' }],
},
},
}),
});
console.log(await r.json());Output:
{
"id": 1,
"email": "user1@abc.com",
"posts": [
{
"id": 1,
"createdAt": "2023-03-14T07:45:04.036Z",
"updatedAt": "2023-03-14T07:45:04.036Z",
"title": "Post 1",
"authorId": 1
},
{
"id": 2,
"createdAt": "2023-03-14T07:45:04.036Z",
"updatedAt": "2023-03-14T07:45:04.036Z",
"title": "Post 2",
"authorId": 1
}
]
}// create a user and attach two posts
const r = await fetch(`/api/user`, {
method: 'POST',
headers: { 'Content-Type': 'application/vnd.api+json' },
body: JSON.stringify({
data: {
type: 'user',
attributes: {
email: 'user1@abc.com'
},
relationships: {
posts: {
data: [
{ type: 'post', id: 1 },
{ type: 'post', id: 2 }
]
}
}
}
})
});
console.log(await r.json());Output:
{
"jsonapi": { "version": "1.1" },
"data": {
"type": "user",
"id": 1,
"attributes": {
"email": "user1@abc.com",
},
"links": {
"self": "http://localhost/api/user/1",
},
"relationships": {
"posts": {
"links": {
"self": "http://localhost/api/user/1/relationships/posts",
"related": "http://localhost/api/user/1/posts",
},
"data": [
{ "type": "post", "id": 1 },
{ "type": "post", "id": 2 },
],
},
},
},
} -
With third-party client generators
ZenStack provides an OpenAPI plugin for generating Open API 3.x specification from the ZModel. The generated OpenAPI spec can be used to generate client libraries for various languages and frameworks. For example, you can use openapi-typescript to generate a typescript client.
Error Handling
Refer to the specific sections for RPC Handler and RESTful Handler.
Fully working example
You can find a fully working example here.