Cerial

NestJS Integration

How to use Cerial with NestJS — modules, services, dependency injection, and multi-schema setups.

Cerial works with NestJS out of the box since the generated client is plain TypeScript. This guide shows how to wrap it in NestJS modules and services for proper dependency injection.

Installation

Install Cerial in your NestJS project:

npm install cerial

Generate your client (adjust paths to match your project structure):

npx cerial generate -s ./schemas -o ./src/db-client

Basic Setup

Create a global CerialModule with a CerialService that manages the connection lifecycle:

// src/cerial/cerial.module.ts
import { Module, Global } from '@nestjs/common';
import { CerialService } from './cerial.service';

@Global()
@Module({
  providers: [CerialService],
  exports: [CerialService],
})
export class CerialModule {}
// src/cerial/cerial.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { CerialClient } from '../db-client';

@Injectable()
export class CerialService extends CerialClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.connect({
      url: process.env.SURREAL_URL ?? 'http://localhost:8000',
      namespace: process.env.SURREAL_NS ?? 'main',
      database: process.env.SURREAL_DB ?? 'main',
      auth: {
        username: process.env.SURREAL_USER ?? 'root',
        password: process.env.SURREAL_PASS ?? 'root',
      },
    });
    await this.migrate();
  }

  async onModuleDestroy() {
    await this.disconnect();
  }
}

Register the module in your AppModule:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { CerialModule } from './cerial/cerial.module';
import { UsersModule } from './users/users.module';

@Module({
  imports: [CerialModule, UsersModule],
})
export class AppModule {}

Since CerialModule is @Global(), CerialService is available for injection in any module without re-importing.

Using in Feature Modules

Inject CerialService into your feature services. Since it extends CerialClient, you access models directly:

// src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { CerialService } from '../cerial/cerial.service';

@Injectable()
export class UsersService {
  constructor(private readonly cerial: CerialService) {}

  async findAll() {
    return this.cerial.User.findMany();
  }

  async findOne(id: string) {
    return this.cerial.User.findUnique({ where: { id } });
  }

  async create(data: { email: string; name: string }) {
    return this.cerial.User.create({ data });
  }
}

Multi-Schema Setup

When your project has multiple independent schemas (e.g., auth and cms), create a separate module and service for each. See Multi-Schema for how to configure schema generation.

Directory Structure

schemas/
├── auth/
│   ├── schema.cerial
│   └── client/          # Generated: AuthCerialClient
└── cms/
    ├── schema.cerial
    └── client/          # Generated: CmsCerialClient

Config

{
  "schemas": {
    "auth": { "path": "./schemas/auth" },
    "cms": { "path": "./schemas/cms" }
  }
}

Separate Modules per Schema

// src/auth-cerial/auth-cerial.module.ts
import { Module, Global } from '@nestjs/common';
import { AuthCerialService } from './auth-cerial.service';

@Global()
@Module({
  providers: [AuthCerialService],
  exports: [AuthCerialService],
})
export class AuthCerialModule {}
// src/auth-cerial/auth-cerial.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { CerialClient as AuthCerialClient } from '../../schemas/auth/client';

@Injectable()
export class AuthCerialService extends AuthCerialClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    await this.connect({
      url: process.env.SURREAL_URL ?? 'http://localhost:8000',
      namespace: process.env.SURREAL_NS ?? 'main',
      database: process.env.SURREAL_DB ?? 'auth',
      auth: {
        username: process.env.SURREAL_USER ?? 'root',
        password: process.env.SURREAL_PASS ?? 'root',
      },
    });
    await this.migrate();
  }

  async onModuleDestroy() {
    await this.disconnect();
  }
}

Create the same pattern for CmsCerialModule and CmsCerialService, importing from ../../schemas/cms/client instead.

Register Both Modules

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AuthCerialModule } from './auth-cerial/auth-cerial.module';
import { CmsCerialModule } from './cms-cerial/cms-cerial.module';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';

@Module({
  imports: [AuthCerialModule, CmsCerialModule, UsersModule, PostsModule],
})
export class AppModule {}

Using a Specific Schema

Inject the schema-specific service in your feature services:

// src/posts/posts.service.ts
import { Injectable } from '@nestjs/common';
import { CmsCerialService } from '../cms-cerial/cms-cerial.service';

@Injectable()
export class PostsService {
  constructor(private readonly cms: CmsCerialService) {}

  async findAll() {
    return this.cms.Post.findMany();
  }
}

Config File Note

NestJS runs on Node.js, which doesn't natively execute TypeScript files. For your Cerial config:

  • Use cerial.config.json (recommended for NestJS projects)
  • Or use cerial.config.ts if you run the CLI with Bun (bunx cerial generate) or Deno

See Runtime Compatibility for more details on config file format support.

Transactions

Use Cerial's $transaction inside NestJS service methods:

import { Injectable } from '@nestjs/common';
import { CerialService } from '../cerial/cerial.service';

@Injectable()
export class OrdersService {
  constructor(private readonly cerial: CerialService) {}

  async createOrder(userId: string, items: { productId: string; quantity: number }[]) {
    return this.cerial.$transaction(async (tx) => {
      const order = await tx.Order.create({
        data: { userId, status: 'pending' },
      });

      for (const item of items) {
        await tx.OrderItem.create({
          data: { orderId: order.id, ...item },
        });
      }

      return order;
    });
  }
}

See Transactions for the full transaction API including array mode, manual mode, and retry configuration.

On this page