Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion code-repos/zenstackhq/v3-doc-orm
Submodule v3-doc-orm updated 3 files
+2,483 −2,483 package-lock.json
+2 −2 package.json
+4 −4 zenstack/input.ts
2 changes: 1 addition & 1 deletion code-repos/zenstackhq/v3-doc-orm-typed-json
4 changes: 2 additions & 2 deletions versioned_docs/version-3.x/migrate-prisma.md
Original file line number Diff line number Diff line change
Expand Up @@ -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, {
Expand All @@ -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';

Expand Down
4 changes: 2 additions & 2 deletions versioned_docs/version-3.x/orm/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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, {
Expand Down
2 changes: 1 addition & 1 deletion versioned_docs/version-3.x/recipe/databases/_category_.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
position: 1
position: 2
label: Databases
collapsible: true
collapsed: true
Expand Down
168 changes: 168 additions & 0 deletions versioned_docs/version-3.x/recipe/nestjs.md
Original file line number Diff line number Diff line change
@@ -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<SchemaType>
> {
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<string, any>,
) {
// 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.

2 changes: 1 addition & 1 deletion versioned_docs/version-3.x/recipe/postgres-multi-schema.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
sidebar_position: 2
sidebar_position: 3
---

# Working With PostgreSQL Schemas
Expand Down