nnestjs-drizzle-crud
Guides

Primary keys

serial, int, bigint, bigserial, and uuid — RETURNING vs insertId, and the ParseIntPipe warning.

Each entity declares its primary key via two fields in the forFeature config:

FieldDefaultNotes
primaryKey'id'The column name.
primaryKeyType'serial'One of 'serial' | 'bigserial' | 'int' | 'bigint' | 'uuid'.

auto-increment (serial / int / bigint / bigserial)

The default. Use this for any numeric primary key, regardless of size:

// serial — int4, default
{ service: UsersService, table: users }

// bigserial — int8
{
  service: EventsService,
  table: events,
  config: { primaryKey: 'id', primaryKeyType: 'bigserial' },
}

On PostgreSQL the created row is returned via RETURNING; on MySQL the driver fills in insertId and the service re-reads the row. Both code paths produce the same Promise<T> result.

uuid

db/schema.ts
import { pgTable, uuid, varchar } from 'drizzle-orm/pg-core';

export const tags = pgTable('tags', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: varchar('name', { length: 100 }).notNull().unique(),
});
tags/tags.module.ts
{
  service: TagsService,
  table: tags,
  config: { primaryKey: 'id', primaryKeyType: 'uuid' },
}

PostgreSQL

Works fully. The created row is returned via RETURNING with the DB-generated UUID.

MySQL

MySQL has no RETURNING. The package needs to know the UUID to re-read the row. Supply the UUID in your create payload — the service will use it as the lookup key:

const newTag = await tagsService.create({ id: crypto.randomUUID(), name: 'urgent' });

Auto-increment keys on MySQL continue to use the driver's insertId automatically — only UUIDs require the explicit value.

Don't use ParseIntPipe on uuid routes

With UUID primary keys, route params are strings. @Param('id', ParseIntPipe) will reject every valid request with a 400. Use either no pipe or ParseUUIDPipe:

@Get(':id')
find(@Param('id', ParseUUIDPipe) id: string) {
  return this.tags.find(id);
}

Composite primary keys

Not supported out of the box. The package assumes a single-column primary key on every CRUD method (find(id), update(id, ...), etc.). For composite keys, write a custom service method that takes a structured where clause and uses raw Drizzle.

Mixed primary-key strategies

Different entities can have different primaryKeyType values. The setting is per-entity in the forFeature config:

DrizzleCrudModule.forFeature([
  { service: UsersService, table: users },                        // serial
  { service: TagsService, table: tags, config: { primaryKeyType: 'uuid' } },
  { service: EventsService, table: events, config: { primaryKeyType: 'bigserial' } },
])

The CRUD methods on each service are typed against the right column shape automatically.

Choosing between serial and uuid

  • serial — simpler, smaller index, faster joins. Predictable for debugging.
  • uuid — safer for distributed systems (no auto-increment collisions when merging data), safer for public-facing IDs (no enumeration), but larger index and slightly slower joins.

For internal entities (join tables, audit logs), serial is usually fine. For entities that get exposed in URLs or APIs to other systems, uuid is the safer default.

Next

On this page