Primary keys
serial, int, bigint, bigserial, and uuid: RETURNING vs insertId, and the ParseIntPipe warning.
Each entity names its primary key in the forFeature config:
| Field | Default | Notes |
|---|---|---|
primaryKey | 'id' | The key of the primary key column in your Drizzle table object. |
primaryKeyType | 'serial' | Deprecated, no effect. Nothing reads it: the key type comes from the Drizzle column. Still accepted so existing configs compile; removed in the next major version. |
The primary key is also the last ORDER BY column on every findAll, which keeps offset pagination stable. See Pagination & sorting.
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, same config
{ service: EventsService, table: events }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
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(),
});{
service: TagsService,
table: tags,
config: { primaryKey: 'id' },
}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 use different key types with no extra config. The type comes from each table's Drizzle column:
DrizzleCrudModule.forFeature([
{ service: UsersService, table: users }, // serial
{ service: TagsService, table: tags }, // uuid
{ service: EventsService, table: events }, // bigserial
])A key that is not named id still needs config: { primaryKey: '<key>' }.
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.