From 33aaf34f67eaae3c6cb412089fa27227763f49b4 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 09:56:19 +0800 Subject: [PATCH 1/2] docs: nestjs guide for v3 --- code-repos/zenstackhq/v3-doc-orm | 2 +- .../zenstackhq/v3-doc-orm-computed-fields | 2 +- code-repos/zenstackhq/v3-doc-orm-policy | 2 +- code-repos/zenstackhq/v3-doc-orm-polymorphism | 2 +- code-repos/zenstackhq/v3-doc-orm-typed-json | 2 +- code-repos/zenstackhq/v3-doc-orm-validation | 2 +- code-repos/zenstackhq/v3-doc-quick-start | 2 +- code-repos/zenstackhq/v3-doc-server-adapter | 2 +- code-repos/zenstackhq/v3-doc-tanstack-query | 2 +- versioned_docs/version-3.x/migrate-prisma.md | 4 +- versioned_docs/version-3.x/orm/client.md | 4 +- .../recipe/databases/_category_.yml | 2 +- versioned_docs/version-3.x/recipe/nestjs.md | 168 ++++++++++++++++++ .../recipe/postgres-multi-schema.md | 2 +- 14 files changed, 183 insertions(+), 15 deletions(-) create mode 100644 versioned_docs/version-3.x/recipe/nestjs.md diff --git a/code-repos/zenstackhq/v3-doc-orm b/code-repos/zenstackhq/v3-doc-orm index a3f7b09f..d8777abf 160000 --- a/code-repos/zenstackhq/v3-doc-orm +++ b/code-repos/zenstackhq/v3-doc-orm @@ -1 +1 @@ -Subproject commit a3f7b09fa6b4c39d6582b3dae0504e95aab7cc21 +Subproject commit d8777abf227bffa300df12cac16889cbf8ad0aed diff --git a/code-repos/zenstackhq/v3-doc-orm-computed-fields b/code-repos/zenstackhq/v3-doc-orm-computed-fields index 05c609c7..552d3eea 160000 --- a/code-repos/zenstackhq/v3-doc-orm-computed-fields +++ b/code-repos/zenstackhq/v3-doc-orm-computed-fields @@ -1 +1 @@ -Subproject commit 05c609c716ea30f7a30b094f828f51d0157ab2bd +Subproject commit 552d3eea41a4e32a9039bd19781df290f100474c diff --git a/code-repos/zenstackhq/v3-doc-orm-policy b/code-repos/zenstackhq/v3-doc-orm-policy index 33aec4f0..ab1fedc5 160000 --- a/code-repos/zenstackhq/v3-doc-orm-policy +++ b/code-repos/zenstackhq/v3-doc-orm-policy @@ -1 +1 @@ -Subproject commit 33aec4f07bccc094d202321c13393ddf356a10ac +Subproject commit ab1fedc5b57c1738dcf58a1b06163405565dc375 diff --git a/code-repos/zenstackhq/v3-doc-orm-polymorphism b/code-repos/zenstackhq/v3-doc-orm-polymorphism index a1b3e19f..3ef64e7d 160000 --- a/code-repos/zenstackhq/v3-doc-orm-polymorphism +++ b/code-repos/zenstackhq/v3-doc-orm-polymorphism @@ -1 +1 @@ -Subproject commit a1b3e19f5f911beee57181211c7bc4dc748ce8b6 +Subproject commit 3ef64e7d2a66d8bdfbf3c0ad379f4db9fea0aac1 diff --git a/code-repos/zenstackhq/v3-doc-orm-typed-json b/code-repos/zenstackhq/v3-doc-orm-typed-json index f04a076f..7245f9ac 160000 --- a/code-repos/zenstackhq/v3-doc-orm-typed-json +++ b/code-repos/zenstackhq/v3-doc-orm-typed-json @@ -1 +1 @@ -Subproject commit f04a076f104703a312510f943dedc831466b6139 +Subproject commit 7245f9acc2bb6e94acfb20d188e1a14b6fa288da diff --git a/code-repos/zenstackhq/v3-doc-orm-validation b/code-repos/zenstackhq/v3-doc-orm-validation index 42aa1d6e..20014efe 160000 --- a/code-repos/zenstackhq/v3-doc-orm-validation +++ b/code-repos/zenstackhq/v3-doc-orm-validation @@ -1 +1 @@ -Subproject commit 42aa1d6e4ae1c1b6cffcca911a5c4fea365506c9 +Subproject commit 20014efeabbcb54685d017846bc17f9ab8427ea8 diff --git a/code-repos/zenstackhq/v3-doc-quick-start b/code-repos/zenstackhq/v3-doc-quick-start index f0d2f9f4..64d688f0 160000 --- a/code-repos/zenstackhq/v3-doc-quick-start +++ b/code-repos/zenstackhq/v3-doc-quick-start @@ -1 +1 @@ -Subproject commit f0d2f9f415d37463a6425077b78a7b81212215ee +Subproject commit 64d688f0b0908a5526e88eae5015a5615023fe3d diff --git a/code-repos/zenstackhq/v3-doc-server-adapter b/code-repos/zenstackhq/v3-doc-server-adapter index 229984e1..4c71ba7b 160000 --- a/code-repos/zenstackhq/v3-doc-server-adapter +++ b/code-repos/zenstackhq/v3-doc-server-adapter @@ -1 +1 @@ -Subproject commit 229984e1298c9fb94a55c94f2ccb242f4f67f8ae +Subproject commit 4c71ba7bb78791633305a8287d2ef2016b0636ef diff --git a/code-repos/zenstackhq/v3-doc-tanstack-query b/code-repos/zenstackhq/v3-doc-tanstack-query index b57abffe..98a7a610 160000 --- a/code-repos/zenstackhq/v3-doc-tanstack-query +++ b/code-repos/zenstackhq/v3-doc-tanstack-query @@ -1 +1 @@ -Subproject commit b57abffec66d168bd3918b81776ddd1bb24fbeec +Subproject commit 98a7a610c0f63718386363f798ad2018197d56ed diff --git a/versioned_docs/version-3.x/migrate-prisma.md b/versioned_docs/version-3.x/migrate-prisma.md index d58681a8..7ce0546d 100644 --- a/versioned_docs/version-3.x/migrate-prisma.md +++ b/versioned_docs/version-3.x/migrate-prisma.md @@ -88,8 +88,8 @@ Replace `new PrismaClient()` with `new ZenStackClient(schema, ...)` where `schem ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; import { schema } from './zenstack/schema'; -import { PostgresDialect } from 'kysely'; import { Pool } from 'pg'; export const db = new ZenStackClient(schema, { @@ -106,7 +106,7 @@ export const db = new ZenStackClient(schema, { ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; -import { SqliteDialect } from 'kysely'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; import { schema } from './zenstack/schema'; diff --git a/versioned_docs/version-3.x/orm/client.md b/versioned_docs/version-3.x/orm/client.md index 7ae246cb..de786d6b 100644 --- a/versioned_docs/version-3.x/orm/client.md +++ b/versioned_docs/version-3.x/orm/client.md @@ -26,7 +26,7 @@ The samples below only show creating a client using SQLite (via [better-sqlite3] ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; -import { SqliteDialect } from 'kysely'; +import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import SQLite from 'better-sqlite3'; import { schema } from './zenstack/schema'; @@ -44,8 +44,8 @@ export const db = new ZenStackClient(schema, { ```ts title='db.ts' import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; import { schema } from './zenstack/schema'; -import { PostgresDialect } from 'kysely'; import { Pool } from 'pg'; export const db = new ZenStackClient(schema, { diff --git a/versioned_docs/version-3.x/recipe/databases/_category_.yml b/versioned_docs/version-3.x/recipe/databases/_category_.yml index 649e74c8..bb2ee145 100644 --- a/versioned_docs/version-3.x/recipe/databases/_category_.yml +++ b/versioned_docs/version-3.x/recipe/databases/_category_.yml @@ -1,4 +1,4 @@ -position: 1 +position: 2 label: Databases collapsible: true collapsed: true diff --git a/versioned_docs/version-3.x/recipe/nestjs.md b/versioned_docs/version-3.x/recipe/nestjs.md new file mode 100644 index 00000000..797824d6 --- /dev/null +++ b/versioned_docs/version-3.x/recipe/nestjs.md @@ -0,0 +1,168 @@ +--- +sidebar_position: 4 +--- + +# Using ZenStack With NestJS + +This guide describes different ways to use ZenStack in a [NestJS](https://nestjs.com/) application. + +## As a Plain ORM + +ZenStack offers a standard ORM component that you can use in your NestJS application just like any other ORM. To get started, create a `DbService` by extending the `ZenStackClient` constructor: + +```ts title="db.service.ts" +import { ZenStackClient } from '@zenstackhq/orm'; +import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; +import { schema, type SchemaType } from './zenstack/schema'; +import { Pool } from 'pg'; + +export class DbService extends ZenStackClient< + SchemaType, + ClientOptions +> { + constructor() { + super(schema, { + dialect: new PostgresDialect({ + pool: new Pool({ connectionString: process.env.DATABASE_URL }) + }), + }); + } +} +``` + +You can then register this service as a provider in a module: + +```ts title="app.module.ts" +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller'; +import { DbService } from './db.service'; + +@Module({ + controllers: [AppController], + providers: [DbService], +}) +``` + +Finally, inject the `DbService` in your controllers or other services to access the database: + +```ts title="app.controller.ts" +import { Controller, Get } from '@nestjs/common'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + constructor(private readonly dbService: DbService) {} + + @Get('/posts') + getPosts() { + return this.dbService.post.findMany(); + } +} +``` + +## As an Access-Controlled ORM + +To leverage ZenStack's [built-in access control features](../orm/access-control/), you can inject a request-scoped `DbService` that's bound to the current session user (usually extracted from the request). + +In the sample below, we provide an access-controlled `DbService` under the name "AUTH_DB": + +```ts title="app.module.ts" +import { Module, Scope } from '@nestjs/common'; +import { REQUEST } from '@nestjs/core'; +import type { Request } from 'express'; +import { PolicyPlugin } from '@zenstackhq/plugin-policy'; +import { DbService } from './db.service'; +// your authentication helper +import { getRequestUser } from './auth.util'; + +@Module({ + controllers: [AppController], + providers: [ + // the standard (no-access-control) ORM + DbService, + + // the access-controlled ORM + { + provide: 'AUTH_DB', + + // make sure it's request-scoped to capture per-request user context + scope: Scope.REQUEST, + + useFactory: (req: Request, db: DbService) => { + // extract the current user from the request, implementation depends on + // your authentication solution + const user = getRequestUser(req); + + // install the PolicyPlugin and set the current user context + return db.$use(new PolicyPlugin()).$setAuth(user); + }, + + inject: [REQUEST, DbService], + }, + ], +}) +``` + +Now you can choose to use the access-controlled `DbService` in your controllers or services by injecting it with the "AUTH_DB" token: + +```ts title="app.controller.ts" +import { Controller, Get, Inject } from '@nestjs/common'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + // inject the access-controlled DbService + constructor(@Inject('AUTH_DB') private readonly dbService: DbService) {} + + @Get('/posts') + getPosts() { + // only posts accessible to the current user will be returned + return this.dbService.post.findMany(); + } +} +``` + +## As an Automatic CRUD Service + +ZenStack also provides [API handlers](../service/api-handler/) that process CRUD requests automatically for you. To use it, create a catch-all route in your controller and forward the request to the API handler: + +```ts title="app.controller.ts" +import { Controller, All, Inject, Param, Query, Req, Res } from '@nestjs/common'; +import type { Request, Response } from 'express'; +import { DbService } from './db.service'; + +@Controller('api') +export class AppController { + // RESTful API Handler is used here for demonstration. You can also use the + // RPC-style API handler if preferred. See the API handlers docs for more + // details. + private readonly apiHandler = new RestApiHandler({ + schema, + endpoint: 'http://localhost:3000/api', + }); + + constructor(@Inject('AUTH_DB') private readonly dbService: DbService) {} + + @All('/*path') + async handleAll( + @Req() req: Request, + @Res() response: Response, + @Param('path') path: string[], + @Query() query: Record, + ) { + // forward the request to the API handler + const result = await this.apiHandler.handleRequest({ + method: req.method, + path: path.join('/'), + query, + requestBody: req.body, + client: authDb, + }); + + response.status(result.status).json(result.body); + } +} +``` + +With this setup, all requests to `/api/*` will be handled by the ZenStack API handler, which performs the necessary CRUD operations with access control enforced. Refer to the [API handlers documentation](../service/api-handler/) for request and response formats and other details. + diff --git a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md index fd886ecb..36698760 100644 --- a/versioned_docs/version-3.x/recipe/postgres-multi-schema.md +++ b/versioned_docs/version-3.x/recipe/postgres-multi-schema.md @@ -1,5 +1,5 @@ --- -sidebar_position: 2 +sidebar_position: 3 --- # Working With PostgreSQL Schemas From 207a71792b999d5c8cf78444a2ebe892b49e3425 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Thu, 18 Dec 2025 10:48:34 +0800 Subject: [PATCH 2/2] update --- versioned_docs/version-3.x/recipe/nestjs.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/versioned_docs/version-3.x/recipe/nestjs.md b/versioned_docs/version-3.x/recipe/nestjs.md index 797824d6..982dde75 100644 --- a/versioned_docs/version-3.x/recipe/nestjs.md +++ b/versioned_docs/version-3.x/recipe/nestjs.md @@ -128,6 +128,7 @@ ZenStack also provides [API handlers](../service/api-handler/) that process CRUD ```ts title="app.controller.ts" import { Controller, All, Inject, Param, Query, Req, Res } from '@nestjs/common'; +import { RestApiHandler } from '@zenstackhq/server/api'; import type { Request, Response } from 'express'; import { DbService } from './db.service'; @@ -146,7 +147,7 @@ export class AppController { @All('/*path') async handleAll( @Req() req: Request, - @Res() response: Response, + @Res() res: Response, @Param('path') path: string[], @Query() query: Record, ) { @@ -156,10 +157,10 @@ export class AppController { path: path.join('/'), query, requestBody: req.body, - client: authDb, + client: this.dbService, }); - response.status(result.status).json(result.body); + res.status(result.status).json(result.body); } } ```