Setup
Install, one-time VoidResourceModule.forRoot() app defaults, and the response envelope.
#Install
pnpm add @void-snippets/core @void-snippets/nestjs
# plus whichever ORM you use
pnpm add typeorm @nestjs/typeorm # TypeORM
# or
pnpm add mongoose @nestjs/mongoose # MongooseYou only need class-validator and class-transformer if you attach DTO classes for request-body validation (covered on the defineResource page):
pnpm add class-validator class-transformer#One-time setup — VoidResourceModule.forRoot()
Every resource shares some configuration — how cursors are signed, the maximum page size, the default page size. Instead of repeating those in every defineResource() call, set them once in your root AppModule:
// app.module.ts
import { Module } from '@nestjs/common';
import { VoidResourceModule } from '@void-snippets/nestjs';
import { ContactsModule } from './contacts/contacts.module';
@Module({
imports: [
VoidResourceModule.forRoot({
cursorSecret: process.env.CURSOR_SECRET,
maxLimit: 200,
defaultLimit: 25,
paginationMode: 'both', // "offset" | "cursor" | "both" — see defineResource
}),
ContactsModule,
],
})
export class AppModule {}Call forRoot() exactly once, before any feature module that registers a resource. Anything a resource sets for itself in its own defineResource() config always wins over the app-wide default here.
#Every forRoot() option
| Option | Default | What it does |
|---|---|---|
cursorSecret | none | HMAC secret used to sign cursors so they can't be forged. Set this from an environment variable in production. |
maxLimit | 100 | Hard ceiling on ?limit= — protects your database from a request asking for a million rows. |
defaultLimit | 10 | Page size used when a request omits ?limit=. |
paginationMode | "offset" | Default pagination style for resources that don't set their own — see defineResource. |
maxBulk | 500 | Cap on how many items a single bulk create/delete request can touch. |
envelope | data => ({ data }) | Wraps every successful response body. See below. |
#The response envelope
Every successful response gets wrapped by an envelope function before it's sent — by default, data => ({ data }). So a single item response looks like { "data": { ...the entity } }, and a list response looks like { "data": { "items": [...], "page": 1, ... } }.
This default is exactly what @void-snippets/react and @void-snippets/angular expect with zero configuration — if your frontend also uses void-snippets, the backend and frontend already agree on the response shape.
If your team already has a house response format, override the envelope instead of reshaping it on the frontend:
VoidResourceModule.forRoot({
envelope: (data) => ({ success: true, result: data, timestamp: Date.now() }),
});You can also override the envelope for a single resource in its own defineResource() config — that takes priority over the app-wide one.