Skip to main content
Version: 3.0 Beta

Server Adapter

Overview​

Server adapters are components that handle the integration with specific frameworks. They understand how to install API routes and handle framework-specific request and response objects.

Server adapters need to be configured with an API handler that defines the API specification. The following diagram illustrates the relationship between them:

Example​

Let's use a real example to see how API handlers and server adapters work together to serve an automatic secured data query API. A few notes about the example:

  • Express.js is used to demonstrate, but the same concept applies to other supported frameworks.
  • Authentication is simulated by using the "x-userid" header. In real applications, you would use a proper authentication mechanism.
  • ZModel schema is configured with access policies.
  • For each request, the getClient call back is called to get an ORM client instance bound to the current user.
Click here to open an interactive playground.
main.ts
import type { ClientContract } from '@zenstackhq/orm';
import { RPCApiHandler } from '@zenstackhq/server/api';
import { ZenStackMiddleware } from '@zenstackhq/server/express';
import express, { Request } from 'express';
import { createClient } from './db';
import { schema, type SchemaType } from './zenstack/schema';

const app = express();
const port = 3000;

// initialize the ORM client
let client: ClientContract<SchemaType> | undefined;
createClient().then(_client => {
client = _client;
});

app.use(express.json());

// install ZenStack's CRUD service at "/api/model"
app.use(
'/api/model',
ZenStackMiddleware({
// use RPC API handler
apiHandler: new RPCApiHandler({ schema }),

// `getClient` is called for each request to get a proper
// ORM client instance
getClient: (request) => getClient(request),
})
);

app.listen(port, () => {
console.log(`App listening on port ${port}`)
console.log('Run `npm run client` to test the service');
});

async function getClient(request: Request) {
// here we use the "x-userid" header to simulate user authentication, in a
// real application, you should use a proper auth mechanism
const uid = request.get('x-userid');
if (!uid) {
// returns the client unbound to a specific user (anonymous)
console.log('Using anonymous ORM client');
return client!;
} else {
// return a user-bound client
console.log('Using ORM client bound to user:', uid);
return client!.$setAuth({id: parseInt(uid)})
}
}
zenstack/schema.zmodel
datasource db {
provider = 'sqlite'
}

plugin policy {
provider = '@zenstackhq/plugin-policy'
}

/// User model
model User {
id Int @id @default(autoincrement())
email String @unique
posts Post[]

// everybody can signup, profiles are public
@@allow('create,read', true)

// user himself has full access
@@allow('all', auth() == this)
}

/// Post model
model Post {
id Int @id @default(autoincrement())
title String
published Boolean @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?

// no anonymous access
@@deny('all', auth() == null)

// published posts are readable to all
@@allow('read', published)

// author has full access
@@allow('all', auth() == author)
}

Catalog​

ZenStack currently maintains the following server adapters. New ones will be added over time based on popularity of frameworks.

Comments
Feel free to ask questions, give feedback, or report issues.

Don't Spam


You can edit/delete your comments by going directly to the discussion, clicking on the 'comments' link below