Reference
For AI agents
Condensed, accurate package facts for code generation. Prefer these over guessing.
This page is a tight, fact-only summary designed to be read by code-generation tools. Prefer the rules and gotchas below over re-deriving them from the rest of the docs.
Package
- Name:
nestjs-drizzle-crud - Version:
3.3.0 - License: MIT
- Peers:
@nestjs/common,@nestjs/core,drizzle-orm(>=0.28.0 <1.0.0),reflect-metadata. Optional:postgres(PG) /mysql2(MySQL).
Setup is two steps and no per-service connection wiring
DrizzleCrudModule.forRoot({ dialect, connectionString | db, schema, defaults })once inAppModule.DrizzleCrudModule.forFeature([{ service, table, config? }])in each feature module.
A service is an empty subclass: do NOT inject the db or pass dialect/db
class XService extends SqlBaseCrudService<X, CreateXDto, UpdateXDto, XFilterDto> {}Rules / gotchas
- Generics order:
SqlBaseCrudService<Entity, CreateDto, UpdateDto, FilterDto>. All butEntitydefault toPartial<Entity>. - Do not add an
@Inject('DRIZZLE_DB')constructor,forFeatureconstructs the service for you. Adding a constructor that callssuper({...})is a legacy/manual pattern and is unnecessary. - The table is passed in
forFeature, not in the service. - If tables lack timestamp / soft-delete columns, set
defaults: { softDelete: false, timestamps: false }, else inserts will reference non-existent columns. findAllreturns{ data, total, page, limit }. Not a bare array.- Filter operators live inside an object value:
{ age: { gte: 18 } }.like/ilikerequire caller-supplied%wildcards; a bare string is an exact, case-insensitive (whencaseSensitive === false) match,%/_/\in the value are literal data, never wildcards. findAllandcountacceptoptions.search: { term, columns, mode? }. Defaultmodeis'ilike'and joins cross-column contains checks withOR;mode: 'fullText'is PostgreSQL-only. Search is applied to both data and count queries.delete/softDeletereturnboolean;update/restorereturn the entity and throwEntityNotFoundException(→ 404) when missing. On Postgres,restore()returns the row viaRETURNINGrather than re-fetching it.- All exceptions extend
@nestjs/common'sHttpException, so Nest's default filter maps them to status codes automatically: 404 (not found), 409 (unique violation), 400 (validation / bad input), 500 (connection / transaction). Custom filters caninstanceofthem. - Relations: declare in
forFeatureconfig.relations, then eager-load withoptions.relations: ['relName'](a dotted path nests:['states', 'states.cities'],['state.country'], and the parent path is pulled in automatically).{ table, localKey, references? }is belongs-to:LEFT JOINed into the main query, resolves to an object ornull.{ table, type: 'hasMany', foreignKey, parentKey?, orderBy? }resolves to an array.{ table, type: 'manyToMany', through, throughLocalKey, throughForeignKey, references?, parentKey?, orderBy? }resolves to an array; the join-table key never appears in the result.- Nested relations go in a
relationsmap on the relation itself. - Every collection and every nested level costs exactly ONE extra query for the whole result set, never one per row. A page of 50 countries with their states and each state's cities is 4 queries: data, count, states, cities.
- A collection is never joined. Filter via
findAll({ relName: { col: value } })with the same operators: belongs-to uses the join, has-many and many-to-many compile toparentKey IN (SELECT ... WHERE ...), sototaland pagination stay correct. Filters are one level deep; go through the intermediate table's own columns, e.g.state.country_id. - A relation missing its required keys throws when the service is constructed (nested included, with the full path in the message).
options.selectnarrows the projection and accepts dotted relation paths at any depth:['id', 'name', 'region.code', 'states.cities.name']. Unknown names are ignored. When every entry is dotted the base row is returned in full. The parent key, the child foreign key and any key a nested level needs are re-added automatically, because the grouping needs them.- The package ships an MCP server,
npx nestjs-drizzle-crud-mcp(stdio), withlist_docs,get_docandsearch_docsoverAGENT.md/README.md/CHANGELOG.md. Rules templates for every agent:AGENTS.template.mdandCLAUDE.template.md. - Primary keys:
primaryKey(default'id') +primaryKeyType('serial' | 'bigserial' | 'int' | 'bigint' | 'uuid') per entity inforFeatureconfig. UUID works on Postgres viaRETURNING; withuuidkeys, route params are strings. Don't useParseIntPipe. - Full-text search is PostgreSQL-only.
- Bulk methods are transactional. A failure throws
BulkOperationExceptionand rolls back the entire operation. executeSqlTransaction(async (tx) => ...)opens a transaction; pass{ transaction: tx }inoptionsto any CRUD method to enlist it.
Quick reference
Configure
DrizzleCrudModule.forRoot({
dialect: 'postgresql',
connectionString: process.env.DATABASE_URL,
schema,
});Service
class UsersService extends SqlBaseCrudService<User> {}Bind
DrizzleCrudModule.forFeature([{ service: UsersService, table: users }]);Filter
await service.findAll({
status: 'active', // exact
role: ['admin', 'editor'], // IN
age: { gte: 18, lt: 65 }, // operators
name: { ilike: 'jo%' }, // pattern (caller wildcards)
deletedAt: { isNull: true },
});List search
await service.findAll(
{ status: 'active' },
{ page: 1, limit: 20 },
{ search: { term: 'john', columns: ['name', 'email'] } },
);Eager load relations
await service.findAll({}, { page: 1, limit: 20 }, { relations: ['state'] });Public exports (alphabetical)
and, asc, BaseCrudSpecHelper, BulkOperationException, CrudFeature,
CrudService, DatabaseConnectionException, desc, DRIZZLE_CONNECTION,
DRIZZLE_CRUD_CONFIG, DRIZZLE_DB, DrizzleConnection, DrizzleCrudConfig,
DrizzleCrudModule, DuplicateEntityException, EntityInUseException, eq,
EntityNotFoundException, ForeignKeyViolationException, gt, gte, ICrudService,
ilike, inArray, isNotNull, isNull, like, lt, lte, ne,
or, PrimaryKeyType, RelationConfig, RelationsConfig, SortColumn, SortOrder,
sql, SqlBaseCrudService, SqlCrudConfig, SqlDialect, SqlOperationOptions,
TestCrudFactory, TransactionException, ValidationFailedExceptionAnti-patterns: do not generate these
// ❌ wrong: db is not injected
class UsersService extends SqlBaseCrudService<User> {
constructor(@Inject(DRIZZLE_DB) private db: any) {
super({ dialect: 'postgresql' as const, db, table: users });
}
}
// ❌ wrong: super() takes no arguments
class UsersService extends SqlBaseCrudService<User> {
constructor() {
super(/* nothing */);
}
}
// ❌ wrong: table is bound in forFeature, not the service
class UsersService extends SqlBaseCrudService<User, /* ... */, users> {}
// ❌ wrong: returning a bare array from a controller
@Get()
async findAll() {
return this.users.findAll({}); // returns { data, total, page, limit }
}
// ❌ wrong: ParseIntPipe on a UUID primary key
@Get(':id')
find(@Param('id', ParseIntPipe) id: number) { /* ... */ }Sources of truth
When in doubt, prefer:
- The TypeScript types,
import type { ... } from 'nestjs-drizzle-crud'is the canonical definition. - The package README on npm.
- The repo at
github.com/myviliha/viliha-drizzle-crud. - The Changelog. Release-by-release behavior changes since 3.0.0.
This page summarizes (1) and (2) and may lag minor patch releases. If a generated snippet disagrees with the TypeScript types, trust the types.