NestJS Adapter
The @zenstackhq/server/nestjs
module provides a quick way to install a ZenStack-enhanced Prisma service as a dependency injection provider onto a NestJS application.
Installation
npm install @zenstackhq/server
Registering the provider
You can register the enhanced Prisma service by importing the ZenStackModule
NestJS module.
import { ZenStackModule } from '@zenstackhq/server/nestjs';
import { enhance } from '@zenstackhq/runtime';
import { PrismaService } from './prisma.service';
@Module({
imports: [
ZenStackModule.registerAsync({
useFactory: (prisma: PrismaService) => {
return {
getEnhancedPrisma: () => enhance(prisma, { user: ... }),
};
},
inject: [PrismaService],
extraProviders: [PrismaService],
}),
],
})
export class AppModule {}
The registerAsync
API takes as input a factory function that returns a config used for creating an enhanced prisma service. The config contains a callback function where you should create and return an enhanced PrismaClient
. It'll be called each time a Prisma method is invoked.
You'll usually pass in a user context when calling enhance
inside the callback. The way how the user context is fetched depends on your authentication mechanism. You can check the NestJS quick start guide for a reference solution.
Using the enhanced Prisma service
Inside your NestJS controllers or services, you can inject the enhanced Prisma service and use it as you would with the regular Prisma service. Just use the special token name ENHANCED_PRISMA
when injecting the service.
import { ENHANCED_PRISMA } from '@zenstackhq/server/nestjs';
@Controller()
export class MyController {
constructor(
@Inject(ENHANCED_PRISMA) private readonly prismaService: PrismaService,
) {}
...
}
You can still use the regular Prisma service by injecting as usual.
Using the API handler service to automate request handling
This module also provides an ApiHandlerService
that can be used to automate the request handling using API handlers. This is useful if you want to handle requests in a more structured way, such as using a controller or middleware. Before using it, you should ensure ENHANCED_PRISMA
is registered in the module. For example:
import { ZenStackModule, ApiHandlerService } from '@zenstackhq/server/nestjs';
import { enhance } from '@zenstackhq/runtime';
import { PrismaService } from './prisma.service';
@Module({
imports: [
// Register the ZenStack module
// The module exports the ENHANCED_PRISMA token and could be used in the ApiHandlerService
ZenStackModule.registerAsync({
useFactory: (prisma: PrismaService) => {
return {
getEnhancedPrisma: () => enhance(prisma, { user: ... }),
};
},
inject: [PrismaService],
extraProviders: [PrismaService],
}),
],
providers: [ApiHandlerService]
})
export class AppModule {}
Then, you can inject the ApiHandlerService
into your code and use it to handle requests. The service provides a method handleRequest
to handle request using API handler automatically.
RPC API Handler:
import { Controller, Get, Post } from '@nestjs/common';
import { ApiHandlerService } from '@zenstackhq/server/nestjs';
@Controller('post')
export class PostController {
constructor(private readonly apiHandlerService: ApiHandlerService) {}
// should align with the route generated by API handler
@Get('findMany')
async findMany() {
return this.apiHandlerService.handleRequest()
}
@Post('create')
async create() {
return this.apiHandlerService.handleRequest()
}
}
RESTful API Handler:
import { Controller, Get, Post } from '@nestjs/common';
import { ApiHandlerService } from '@zenstackhq/server/nestjs';
import RESTApiHandler from '@zenstackhq/server/api/rest';
const ENDPOINT = 'http://localhost';
@Controller('post')
export class PostController {
constructor(private readonly apiHandlerService: ApiHandlerService) {}
// should align with the route generated by API handler
@Get()
async list() {
return this.apiHandlerService.handleRequest(
{
handler: RESTApiHandler({
endpoint: ENDPOINT,
}),
}
)
}
@Post()
async create() {
return this.apiHandlerService.handleRequest(
{
handler: RESTApiHandler({
endpoint: ENDPOINT,
}),
}
)
}
}
API reference
ZenStackModule.registerAsync
Signature
registerAsync(options: ZenStackModuleAsyncOptions): DynamicModule;
Parameter options
interface ZenStackModuleAsyncOptions {
/**
* Optional list of imported modules that export the providers which are
* required in this module.
*/
imports?: Array<Type<any> | DynamicModule | Promise<DynamicModule> | ForwardReference>;
/**
* Whether the module is global-scoped.
*/
global?: boolean;
/**
* The token to export the enhanced Prisma service. Default is `'ENHANCED_PRISMA'`.
*/
exportToken?: string;
/**
* The factory function to create the enhancement options.
*/
useFactory: (...args: unknown[]) => Promise<ZenStackModuleOptions> | ZenStackModuleOptions;
/**
* The dependencies to inject into the factory function.
*/
inject?: FactoryProvider['inject'];
/**
* Extra providers to facilitate dependency injection.
*/
extraProviders?: Provider[];
}
interface ZenStackModuleOptions {
/**
* A callback for getting an enhanced `PrismaClient`.
*/
getEnhancedPrisma: (model?: string | symbol) => unknown;
}
ApiHandlerService.handleRequest
Signature
handleRequest(options?: ApiHandlerOptions): Promise<unknown>;
Parameter options
interface ApiHandlerOptions {
/**
* Logger settings
*/
logger?: LoggerConfig;
/**
* 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.
*/
modelMeta?: ModelMeta;
/**
* Zod schemas for validating request input. Pass `true` to load from standard location
* (need to enable `@core/zod` plugin in schema.zmodel) or omit to disable input validation.
*/
zodSchemas?: ZodSchemas | boolean;
/**
* Api request handler function. Can be created using `@zenstackhq/server/api/rest` or `@zenstackhq/server/api/rpc` factory functions.
* Defaults to RPC-style API handler.
*/
handler?: HandleRequestFn;
/**
* The base URL for the API handler. This is used to determine the base path for the API requests.
* If you are using the ApiHandlerService in a route with a prefix, you should set this to the prefix.
*
* e.g.
* without baseUrl(API handler default route):
* - RPC API handler: [model]/findMany
* - RESTful API handler: /:type
*
* with baseUrl(/api/crud):
* - RPC API handler: /api/crud/[model]/findMany
* - RESTful API handler: /api/crud/:type
*/
baseUrl?: string;
}