forFeature
Bind a service to its table, and override per-entity configuration.
DrizzleCrudModule.forFeature(entities) is called inside each feature module. It binds one or more services to their tables and constructs them with the correct merged configuration.
Signature
DrizzleCrudModule.forFeature(entities: CrudFeature[]): DynamicModuleWhere CrudFeature is:
interface CrudFeature {
service: typeof SqlBaseCrudService;
table: any; // Drizzle table
config?: Partial<SqlCrudConfig>; // per-entity overrides
}Basic usage
import { Module } from '@nestjs/common';
import { DrizzleCrudModule } from 'nestjs-drizzle-crud';
import { users } from '../db/schema';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
@Module({
imports: [
DrizzleCrudModule.forFeature([
{ service: UsersService, table: users },
]),
],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}The module registers UsersService as a provider, passing it a fully-built SqlCrudConfig derived from the global forRoot defaults + any per-entity overrides you supply here.
Per-entity overrides
Anything in config overrides the project defaults for that entity. The type is CrudFeatureConfig: every SqlCrudConfig key is optional, and softDelete, timestamps, pagination and sql merge with the project defaults one level deep. pagination: { defaultLimit: 5 } keeps the project's maxLimit, and sql: { caseSensitive: true } keeps useReturning.
DrizzleCrudModule.forFeature([
{
service: PostsService,
table: posts,
config: {
primaryKey: 'id',
softDelete: { enabled: true, column: 'deleted_at' },
// Default ORDER BY for findAll() when the caller passes no sortBy.
defaultSort: [
{ column: 'position', order: 'asc' },
{ column: 'created_at', order: 'desc' }, // tiebreaker
],
relations: {
author: { table: users, localKey: 'author_id', references: 'id' },
},
},
},
])What you can override
| Field | Effect |
|---|---|
primaryKey | Key of the primary key column (default 'id'). |
primaryKeyType | Deprecated, no effect. The key type comes from the Drizzle column. See Primary keys. |
softDelete | { enabled?: boolean; column?: string }. column is the key in your Drizzle table object, 'deleted_at' by default. |
timestamps | { createdAt?: string; updatedAt?: string }. Keys of the timestamp columns, 'created_at' / 'updated_at' by default. timestamps: undefined turns them off for this entity. |
pagination | { defaultLimit?: number; maxLimit?: number }. Per-entity override of the global defaults.pagination; a missing key keeps the project value. |
defaultSort | Ordered column list for findAll() when no sortBy is passed. |
sql | Per-entity dialect tuning. |
relations | Belongs-to, has-many and many-to-many relations, keyed by relation name. See Relations. |
filterableColumns, sortableColumns, selectableColumns, searchableColumns, loadableRelations | What a caller may filter, sort, select, search and eager-load. Unset allows everything. See Allowlists. |
writableColumns | The columns create, update and upsert may write; anything else is a 400. Unset, the package ignores the columns it manages. See Allowlists. |
Does a camelCase schema need extra config?
Yes, when your keys differ from the defaults. Settings name the key in the Drizzle table object, not the database column. For deletedAt: timestamp('deleted_at') and friends:
config: {
softDelete: { column: 'deletedAt' },
timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' },
}The service checks these keys at startup. A key that is not on the table stops the app with a message such as softDelete.column is "deleted_at", but the table has no column under that key. The column "deleted_at" is declared under the key "deletedAt"; set softDelete.column to "deletedAt".
Multiple entities in one feature module
DrizzleCrudModule.forFeature([
{ service: UsersService, table: users },
{ service: PostsService, table: posts },
{ service: CommentsService, table: comments, config: { /* ... */ } },
])Each service is registered as its own provider and is independently constructed.
@CrudService decorator
You can also attach per-entity config to the service class itself with the @CrudService decorator. The forFeature config field still wins when both are set. The merge order is global defaults → decorator metadata → forFeature config.
import { CrudService } from 'nestjs-drizzle-crud';
@CrudService({
primaryKey: 'id',
softDelete: { enabled: true, column: 'deleted_at' },
})
export class TagsService extends SqlBaseCrudService<Tag> {}The decorator is mostly a convenience for keeping the config next to the service definition. Use forFeature config for config that's data-driven or varies per feature module.
Verifying the merged config
The fully-built config for each entity is available in the DRIZZLE_CRUD_CONFIG DI token. Inject it in tests to verify the merge worked:
import { Inject } from '@nestjs/common';
import { DRIZZLE_CRUD_CONFIG } from 'nestjs-drizzle-crud';
@Injectable()
export class ConfigInspector {
constructor(@Inject(DRIZZLE_CRUD_CONFIG) public readonly cfg: any) {}
}The injected DRIZZLE_CRUD_CONFIG is the normalized global config (the
result of applying defaults). It does not include the per-entity
overrides. To inspect a single entity's resolved config, use
DRIZZLE_DB + read it off the constructed service instance.