
Nestjs Drizzle Crud Generator
- 1.5k installs
- 311 repo stars
- Updated June 22, 2026
- giuseppe-trisciuoglio/developer-kit
nestjs-drizzle-crud-generator is an agent skill for generate nestjs crud modules with drizzle orm entities, dtos, and services.
About
The nestjs-drizzle-crud-generator skill is designed for generate NestJS CRUD modules with Drizzle ORM entities, DTOs, and services. NestJS Drizzle CRUD Generator Overview Automatically generates complete CRUD modules for NestJS applications using Drizzle ORM. Creates all necessary files following the zaccheroni-monorepo patterns: feature modules, controllers, services, Zod-validated DTOs, Drizzle schemas, and Jest unit tests. Invoke when the user needs NestJS CRUD modules, Drizzle schemas, or entity scaffolding.
- Creating new entity modules with full CRUD endpoints.
- Building database-backed features in NestJS.
- Generating type-safe DTOs with Zod validation.
- Adding services with Drizzle ORM queries.
- Creating unit tests with mocked database.
Nestjs Drizzle Crud Generator by the numbers
- 1,491 all-time installs (skills.sh)
- +59 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #318 of 4,386 Backend & APIs skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
nestjs-drizzle-crud-generator capabilities & compatibility
- Capabilities
- creating new entity modules with full crud endpo · building database backed features in nestjs · generating type safe dtos with zod validation · adding services with drizzle orm queries
What nestjs-drizzle-crud-generator says it does
Generates complete CRUD modules for NestJS applications with Drizzle ORM. Use when building server-side features in NestJS that require database operations, including creating new
Generates complete CRUD modules for NestJS applications with Drizzle ORM. Use when building server-side features in NestJS that require database operations, inc
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nestjs-drizzle-crud-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.5k |
|---|---|
| repo stars | ★ 311 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 22, 2026 |
| Repository | giuseppe-trisciuoglio/developer-kit ↗ |
How do I generate nestjs crud modules with drizzle orm entities, dtos, and services?
Generate NestJS CRUD modules with Drizzle ORM entities, DTOs, and services.
Who is it for?
Backend developers scaffolding NestJS database modules with Drizzle.
Skip if: Skip for non-NestJS stacks or ORMs other than Drizzle without adaptation.
When should I use this skill?
User needs NestJS CRUD modules, Drizzle schemas, or entity scaffolding.
What you get
Completed nestjs-drizzle-crud-generator workflow with documented commands, files, and expected deliverables.
- CRUD controller
- service with Drizzle queries
- Zod DTOs and unit tests
By the numbers
- Generates six layers per entity: module, controller, service, Drizzle queries, Zod DTOs, and unit tests
Files
NestJS Drizzle CRUD Generator
Overview
Automatically generates complete CRUD modules for NestJS applications using Drizzle ORM. Creates all necessary files following the zaccheroni-monorepo patterns: feature modules, controllers, services, Zod-validated DTOs, Drizzle schemas, and Jest unit tests.
When to Use
- Creating new entity modules with full CRUD endpoints
- Building database-backed features in NestJS
- Generating type-safe DTOs with Zod validation
- Adding services with Drizzle ORM queries
- Creating unit tests with mocked database
Instructions
Step 1: Define Entity Fields
Gather entity definition:
- Entity name (e.g.,
user,product,order) - List of fields with types (see
references/field-types.mdfor supported types) - Required fields vs optional fields with defaults
Step 2: Run the Generator
python scripts/generate_crud.py --feature <name> --fields '<json-array>' --output <path>Step 3: Verify Generated Files
Check that all expected files were created:
ls -la libs/server/<feature-name>/src/lib/Expected structure:
controllers/
services/
dto/
schema/
<feature>-feature.module.tsStep 4: Run TypeScript Compilation
cd libs/server && npx tsc --noEmitStep 5: Execute Unit Tests
cd libs/server && npm test -- --testPathPattern=<feature-name>Examples
Generate a User module
python scripts/generate_crud.py \
--feature user \
--fields '[{"name": "name", "type": "string", "required": true}, {"name": "email", "type": "email", "required": true}, {"name": "password", "type": "string", "required": true}]' \
--output ./libs/serverGenerate a Product module
python scripts/generate_crud.py \
--feature product \
--fields '[{"name": "title", "type": "string", "required": true}, {"name": "price", "type": "number", "required": true}, {"name": "description", "type": "text", "required": false}, {"name": "inStock", "type": "boolean", "required": false, "default": true}]' \
--output ./libs/serverGenerated Structure
libs/server/{feature-name}/
├── src/
│ ├── index.ts
│ └── lib/
│ ├── {feature}-feature.module.ts
│ ├── controllers/
│ │ ├── index.ts
│ │ └── {feature}.controller.ts
│ ├── services/
│ │ ├── index.ts
│ │ ├── {feature}.service.ts
│ │ └── {feature}.service.spec.ts
│ ├── dto/
│ │ ├── index.ts
│ │ └── {feature}.dto.ts
│ └── schema/
│ └── {feature}.table.tsFeatures
Module
- Uses
forRootAsyncpattern for lazy configuration - Exports generated service for other modules
- Imports DatabaseModule for feature tables
Controller
- Full CRUD endpoints: POST, GET, PATCH, DELETE
- Query parameter validation for pagination
- Zod validation pipe integration
Service
- Drizzle ORM query methods
- Soft delete support (via
deletedAtcolumn) - Pagination with limit/offset
- Filtering support
- Type-safe return types
DTOs
- Zod schemas for Create and Update
- Query parameter schemas for filtering
- NestJS DTO integration
Tests
- Jest test suite
- Mocked Drizzle database
- Test cases for all CRUD operations
Manual Integration
After generation, integrate into your app module:
// app.module.ts
import { {{FeatureName}}FeatureModule } from '@your-org/server-{{feature}}';
@Module({
imports: [
{{FeatureName}}FeatureModule.forRootAsync({
useFactory: () => ({
defaultPageSize: 10,
maxPageSize: 100,
}),
}),
],
})
export class AppModule {}Dependencies
Required packages:
@nestjs/common@nestjs/coredrizzle-ormdrizzle-zodzodnestjs-zod
Best Practices
1. Verify before commit: Always run tsc --noEmit and tests before committing generated code 2. Customize services: Add business logic to generated services after validation 3. Database migrations: Create migrations separately for generated Drizzle schemas 4. Use generated types: Reference generated types in your application code 5. Review DTOs: Adjust Zod validation rules based on your API requirements
Constraints and Warnings
- Soft delete only: Delete operations use soft delete (
deletedAttimestamp). Hard deletes require manual modification - No authentication: Generated code does not include auth guards - add them based on your security requirements
- Basic CRUD only: Complex queries, transactions, or business logic must be implemented manually
- JSON escaping: Use single quotes around the JSON array when passing fields on command line
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { ZodCustomPipe } from '@your-org/server-utils';
import { {{FeatureName}}Service } from './{{featureName}}.service';
import {
Create{{FeatureName}}Dto,
Create{{FeatureName}}Schema,
Update{{FeatureName}}Dto,
Update{{FeatureName}}Schema,
FindAll{{FeatureName}}QueryDto,
FindAll{{FeatureName}}QuerySchema,
} from './dto/{{featureName}}.dto';
@ApiTags('{{featureName}}')
@Controller('{{featureName}}')
export class {{FeatureName}}Controller {
constructor(private readonly service: {{FeatureName}}Service) {}
@Post()
async create(
@Body(new ZodCustomPipe(Create{{FeatureName}}Schema))
dto: Create{{FeatureName}}Dto,
) {
return this.service.create(dto);
}
@Get()
async findAll(
@Query(new ZodCustomPipe(FindAll{{FeatureName}}QuerySchema))
query: FindAll{{FeatureName}}QueryDto,
) {
return this.service.findAll(query);
}
@Get(':id')
async findOne(@Param('id') id: string) {
return this.service.findOne(id);
}
@Patch(':id')
async update(
@Param('id') id: string,
@Body(new ZodCustomPipe(Update{{FeatureName}}Schema))
dto: Update{{FeatureName}}Dto,
) {
return this.service.update(id, dto);
}
@Delete(':id')
async remove(@Param('id') id: string) {
return this.service.remove(id);
}
}
import { z } from 'zod';
import { createZodDto } from 'nestjs-zod/dto';
export const Create{{FeatureName}}Schema = z.object({
{{CreateFields}}
}).strict();
export const Update{{FeatureName}}Schema = Create{{FeatureName}}Schema.partial();
export const FindAll{{FeatureName}}QuerySchema = z.object({
page: z.coerce.number().int().positive().default(1),
limit: z.coerce.number().int().positive().max(100).default(10),
{{FilterFields}}
}).strict();
export type Create{{FeatureName}}Dto = z.infer<typeof Create{{FeatureName}}Schema>;
export type Update{{FeatureName}}Dto = z.infer<typeof Update{{FeatureName}}Schema>;
export type FindAll{{FeatureName}}QueryDto = z.infer<typeof FindAll{{FeatureName}}QuerySchema>;
export class Create{{FeatureName}}Dto extends createZodDto(Create{{FeatureName}}Schema) {}
export class Update{{FeatureName}}Dto extends createZodDto(Update{{FeatureName}}Schema) {}
export class FindAll{{FeatureName}}QueryDto extends createZodDto(FindAll{{FeatureName}}QuerySchema) {}
import { DynamicModule, Module } from '@nestjs/common';
import { AsyncFeatureModuleOptions } from '@nestjs/common/interfaces/features/async-options.interface';
import { DatabaseModule } from '@your-org/server-database';
import { {{FeatureName}}Controller } from './controllers';
import { {{FeatureName}}Service } from './services';
import { {{FeatureName}}Table } from './schema';
export const FEATURE_OPTIONS = '{{featureName}}.feature-options';
export interface {{FeatureName}}FeatureOptions {
defaultPageSize?: number;
maxPageSize?: number;
includeDeleted?: boolean;
}
export type Async{{FeatureName}}ModuleOptions = AsyncFeatureModuleOptions<{{FeatureName}}FeatureOptions>;
@Module({})
export class {{FeatureName}}FeatureModule {
static forRootAsync(options: Async{{FeatureName}}ModuleOptions): DynamicModule {
return {
global: true,
module: {{FeatureName}}FeatureModule,
imports: [DatabaseModule.forFeature([{{FeatureName}}Table])],
controllers: [{{FeatureName}}Controller],
providers: [
{
provide: FEATURE_OPTIONS,
useFactory: options.useFactory,
inject: options.inject,
},
{{FeatureName}}Service,
],
exports: [{{FeatureName}}Service],
};
}
}
import { Injectable, NotFoundException } from '@nestjs/common';
import { Inject } from '@nestjs/common/decorators';
import { eq, isNull, and } from 'drizzle-orm';
import { DrizzleD1Database, DrizzlePgDatabase } from 'drizzle-orm/cloud';
import { {{FeatureName}}Table } from '../schema/{{featureName}}.table';
import {
Create{{FeatureName}}Dto,
Update{{FeatureName}}Dto,
FindAll{{FeatureName}}QueryDto,
} from './dto/{{featureName}}.dto';
export const DrizzleProvider = Symbol('DrizzleProvider');
@Injectable()
export class {{FeatureName}}Service {
private readonly defaultPageSize: number;
private readonly maxPageSize: number;
private readonly includeDeleted: boolean;
constructor(
@Inject(DrizzleProvider)
private readonly db: DrizzleD1Database | DrizzlePgDatabase,
) {
this.defaultPageSize = 10;
this.maxPageSize = 100;
this.includeDeleted = false;
}
async create(dto: Create{{FeatureName}}Dto): Promise<{{FeatureName}}> {
const [created] = await this.db
.insert({{FeatureName}}Table)
.values(dto as any)
.returning();
return created as {{FeatureName}};
}
async findAll(
query: FindAll{{FeatureName}}QueryDto,
): Promise<{ rows: {{FeatureName}}[]; total: number }> {
const { page = 1, limit = this.defaultPageSize, ...filters } = query;
const offset = (page - 1) * Math.min(limit, this.maxPageSize);
const whereConditions = [];
if (!this.includeDeleted) {
whereConditions.push(isNull({{FeatureName}}Table.deletedAt));
}
// Apply filters
for (const [key, value] of Object.entries(filters)) {
if (value !== undefined && value !== null) {
whereConditions.push(
// @ts-ignore - dynamic field access
eq({{FeatureName}}Table[key], value),
);
}
}
const whereClause = and(...whereConditions);
const [rows, countResult] = await Promise.all([
this.db
.select()
.from({{FeatureName}}Table)
.where(whereClause)
.limit(Math.min(limit, this.maxPageSize))
.offset(offset),
this.db
.select({ count: {{FeatureName}}Table.id })
.from({{FeatureName}}Table)
.where(whereClause),
]);
const total = countResult.length;
return {
rows: rows as {{FeatureName}}[],
total,
};
}
async findOne(id: string): Promise<{{FeatureName}} | null> {
const conditions = [eq({{FeatureName}}Table.id, id)];
if (!this.includeDeleted) {
conditions.push(isNull({{FeatureName}}Table.deletedAt));
}
const [result] = await this.db
.select()
.from({{FeatureName}}Table)
.where(and(...conditions));
return (result as {{FeatureName}}) || null;
}
async update(
id: string,
dto: Update{{FeatureName}}Dto,
): Promise<{{FeatureName}}> {
const existing = await this.findOne(id);
if (!existing) {
throw new NotFoundException(`{{FeatureName}} with id ${id} not found`);
}
const [updated] = await this.db
.update({{FeatureName}}Table)
.set({ ...dto, updatedAt: new Date() } as any)
.where(eq({{FeatureName}}Table.id, id))
.returning();
return updated as {{FeatureName}};
}
async remove(id: string): Promise<void> {
const existing = await this.findOne(id);
if (!existing) {
throw new NotFoundException(`{{FeatureName}} with id ${id} not found`);
}
// Soft delete
await this.db
.update({{FeatureName}}Table)
.set({ deletedAt: new Date() } as any)
.where(eq({{FeatureName}}Table.id, id));
}
}
import { pgTable, text, timestamp, uuid, boolean, integer, real } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';
export const {{FeatureName}}Table = pgTable('{{tableName}}', {
id: uuid('id').primaryKey().defaultRandom(),
{{TableFields}}{{TableFieldsSuffix}}
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
deletedAt: timestamp('deleted_at'),
});
export const {{FeatureName}}Relations = relations({{FeatureName}}Table, ({ many }) => ({
{{RelationFields}}
}));
export type {{FeatureName}} = typeof {{FeatureName}}Table.$inferSelect;
export type New{{FeatureName}} = typeof {{FeatureName}}Table.$inferInsert;
import { Test, TestingModule } from '@nestjs/testing';
import { {{FeatureName}}Service } from './{{featureName}}.service';
import { DrizzleProvider } from './{{featureName}}.service';
import { {{FeatureName}}Table } from '../schema/{{featureName}}.table';
describe('{{FeatureName}}Service', () => {
let service: {{FeatureName}}Service;
let mockDb: {
select: jest.Mock;
insert: jest.Mock;
update: jest.Mock;
delete: jest.Mock;
};
const mock{{FeatureName}} = {
id: '123e4567-e89b-12d3-a456-426614174000',
{{MockFields}}
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
};
beforeEach(async () => {
mockDb = {
select: jest.fn().mockReturnValue({
from: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
limit: jest.fn().mockReturnValue({
offset: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
}),
}),
}),
insert: jest.fn().mockReturnValue({
values: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([mock{{FeatureName}}]),
}),
}),
update: jest.fn().mockReturnValue({
set: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([mock{{FeatureName}}]),
}),
}),
}),
delete: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
{{FeatureName}}Service,
{
provide: DrizzleProvider,
useValue: mockDb,
},
],
}).compile();
service = module.get<{{FeatureName}}Service>({{FeatureName}}Service);
});
describe('create', () => {
it('should create a new {{featureName}}', async () => {
const dto = {{CreateDtoMock}};
const result = await service.create(dto as any);
expect(mockDb.insert).toHaveBeenCalled();
expect(result).toEqual(mock{{FeatureName}});
});
});
describe('findAll', () => {
it('should return paginated results', async () => {
const query = { page: 1, limit: 10 };
const result = await service.findAll(query as any);
expect(result.rows).toEqual([mock{{FeatureName}}]);
expect(result.total).toBeGreaterThanOrEqual(0);
});
it('should return empty array when no results', async () => {
mockDb.select.mockReturnValue({
from: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
limit: jest.fn().mockReturnValue({
offset: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
}),
}),
});
const query = { page: 1, limit: 10 };
const result = await service.findAll(query as any);
expect(result.rows).toEqual([]);
expect(result.total).toBe(0);
});
});
describe('findOne', () => {
it('should return a {{featureName}} by id', async () => {
const result = await service.findOne(mock{{FeatureName}}.id);
expect(result).toEqual(mock{{FeatureName}});
});
it('should return null for non-existent id', async () => {
mockDb.select.mockReturnValue({
from: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
});
const result = await service.findOne('non-existent-id');
expect(result).toBeNull();
});
});
describe('update', () => {
it('should update a {{featureName}}', async () => {
const dto = {{UpdateDtoMock}};
const result = await service.update(mock{{FeatureName}}.id, dto as any);
expect(mockDb.update).toHaveBeenCalled();
expect(result).toEqual(mock{{FeatureName}});
});
it('should throw NotFoundException for non-existent id', async () => {
mockDb.select.mockReturnValue({
from: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
});
await expect(
service.update('non-existent-id', {} as any),
).rejects.toThrow('not found');
});
});
describe('remove', () => {
it('should soft delete a {{featureName}}', async () => {
await service.remove(mock{{FeatureName}}.id);
expect(mockDb.update).toHaveBeenCalled();
});
it('should throw NotFoundException for non-existent id', async () => {
mockDb.select.mockReturnValue({
from: jest.fn().mockReturnValue({
where: jest.fn().mockReturnValue({
returning: jest.fn().mockResolvedValue([]),
}),
}),
});
await expect(service.remove('non-existent-id')).rejects.toThrow(
'not found',
);
});
});
});
Supported Field Types
Field Type Mapping
| Type | Drizzle Column | Zod Schema |
|---|---|---|
| string | text | z.string() |
| text | text | z.string() |
| number | real | z.number() |
| integer | integer | z.number().int() |
| boolean | boolean | z.boolean() |
| date | timestamp | z.date() |
| uuid | uuid | z.string().uuid() |
| text | z.string().email() |
Field Options
Each field definition supports these properties:
| Property | Type | Description |
|---|---|---|
| name | string | Field name (camelCase recommended) |
| type | string | Data type from the table above |
| required | boolean | Whether the field is mandatory |
| default | any | Default value for optional fields |
| maxLength | number | Maximum length for string/text fields |
| minLength | number | Minimum length for string/text fields |
Example Field Definitions
[
{"name": "name", "type": "string", "required": true},
{"name": "email", "type": "email", "required": true},
{"name": "age", "type": "integer", "required": false},
{"name": "isActive", "type": "boolean", "required": false, "default": true},
{"name": "price", "type": "number", "required": true},
{"name": "bio", "type": "text", "required": false},
{"name": "userId", "type": "uuid", "required": false}
]#!/usr/bin/env python3
"""
NestJS Drizzle CRUD Generator
Usage:
generate_crud.py --feature <feature-name> --fields <fields-json> [--output <output-dir>]
Example:
generate_crud.py --feature user --fields '[{"name": "name", "type": "string", "required": true}, {"name": "email", "type": "string", "required": true}]' --output ./libs/server
"""
import argparse
import json
import os
import re
import sys
from pathlib import Path
from typing import Any
def to_camel_case(text: str) -> str:
"""Convert to camelCase"""
components = text.replace('-', '_').split('_')
return components[0] + ''.join(x.title() for x in components[1:])
def to_pascal_case(text: str) -> str:
"""Convert to PascalCase"""
return ''.join(x.title() for x in text.replace('-', '_').split('_'))
def to_snake_case(text: str) -> str:
"""Convert to snake_case"""
return re.sub(r'(?<!^)(?=[A-Z])', '_', text).lower()
def read_template(template_name: str) -> str:
"""Read template file from assets/templates directory"""
script_dir = Path(__file__).parent.parent
template_path = script_dir / 'assets' / 'templates' / template_name
if not template_path.exists():
raise FileNotFoundError(f"Template not found: {template_path}")
return template_path.read_text()
def map_field_type_to_drizzle(field_type: str) -> str:
"""Map TypeScript types to Drizzle column types"""
type_mapping = {
'string': 'text',
'text': 'text',
'number': 'real',
'integer': 'integer',
'boolean': 'boolean',
'date': 'timestamp',
'uuid': 'uuid',
'email': 'text',
}
return type_mapping.get(field_type, 'text')
def map_field_type_to_zod(field_type: str) -> str:
"""Map field types to Zod schema types"""
type_mapping = {
'string': 'z.string()',
'text': 'z.string()',
'number': 'z.number()',
'integer': 'z.number().int()',
'boolean': 'z.boolean()',
'date': 'z.date()',
'uuid': 'z.string().uuid()',
'email': 'z.string().email()',
}
return type_mapping.get(field_type, 'z.string()')
def generate_table_fields(fields: list[dict]) -> str:
"""Generate table field definitions"""
lines = []
for field in fields:
name = field['name']
field_type = field.get('type', 'string')
required = field.get('required', False)
default = field.get('default')
drizzle_type = map_field_type_to_drizzle(field_type)
if field_type == 'uuid':
line = f" {name}: {drizzle_type}('{name}').primaryKey().defaultRandom()"
elif field_type == 'boolean':
line = f" {name}: {drizzle_type}('{name}')"
if default is not None:
line += f".default({default})"
elif required:
line += ".notNull()"
elif field_type == 'number' or field_type == 'integer':
line = f" {name}: {drizzle_type}('{name}')"
if default is not None:
line += f".default({default})"
elif required:
line += ".notNull()"
else:
line = f" {name}: {drizzle_type}('{name}')"
if default:
line += f".default('{default}')"
elif required:
line += ".notNull()"
lines.append(line)
if not lines:
return ''
# Add comma to all lines except the last one
lines_with_commas = [line + ',' for line in lines[:-1]]
if lines:
lines_with_commas.append(lines[-1])
return '\n'.join(lines_with_commas)
def generate_create_fields(fields: list[dict]) -> str:
"""Generate create schema fields"""
lines = []
for field in fields:
name = field['name']
field_type = field.get('type', 'string')
required = field.get('required', False)
max_length = field.get('maxLength')
min_length = field.get('minLength')
zod_type = map_field_type_to_zod(field_type)
# Build chain
if not required:
zod_type = zod_type.replace('z.', 'z.')
chain = zod_type
if min_length:
chain += f".min({min_length})"
if max_length:
chain += f".max({max_length})"
if not required:
chain += ".optional()"
lines.append(f" {name}: {chain}")
return ',\n'.join(lines)
def generate_filter_fields(fields: list[dict]) -> str:
"""Generate filter query fields"""
lines = []
for field in fields:
name = field['name']
lines.append(f" {name}: z.{map_field_type_to_zod(field.get('type', 'string')).replace('z.', '')}")
return ',\n'.join(lines)
def generate_mock_fields(fields: list[dict]) -> str:
"""Generate mock fields for tests"""
lines = []
for field in fields:
name = field['name']
field_type = field.get('type', 'string')
if field_type == 'boolean':
lines.append(f" {name}: true,")
elif field_type == 'number' or field_type == 'integer':
lines.append(f" {name}: 1,")
elif field_type == 'date':
lines.append(f" {name}: new Date(),")
else:
lines.append(f" {name}: 'test_{name}',")
return '\n'.join(lines)
def generate_create_dto(fields: list[dict]) -> str:
"""Generate create DTO mock for tests"""
lines = []
for field in fields:
name = field['name']
field_type = field.get('type', 'string')
if field_type == 'boolean':
lines.append(f" {name}: true,")
elif field_type == 'number' or field_type == 'integer':
lines.append(f" {name}: 1,")
elif field_type == 'date':
lines.append(f" {name}: new Date(),")
else:
lines.append(f" {name}: 'test_{name}',")
return '\n'.join(lines)
def generate_update_dto(fields: list[dict]) -> str:
"""Generate update DTO mock for tests (all optional)"""
lines = []
for field in fields:
name = field['name']
field_type = field.get('type', 'string')
if field_type == 'boolean':
lines.append(f" {name}: true,")
elif field_type == 'number' or field_type == 'integer':
lines.append(f" {name}: 1,")
else:
lines.append(f" {name}: 'updated_{name}',")
return '\n'.join(lines)
def generate_relation_fields(fields: list[dict]) -> str:
"""Generate relation fields for Drizzle"""
lines = []
for field in fields:
if field.get('relation') == 'hasMany':
related = to_pascal_case(field.get('related', field['name'] + 's'))
lines.append(f" {to_camel_case(field['name'])}: many({related}Table),")
return '\n'.join(lines) if lines else ' // No relations defined'
def replace_placeholders(template: str, feature_name: str, fields: list[dict]) -> str:
"""Replace all placeholders in template"""
pascal_name = to_pascal_case(feature_name)
camel_name = to_camel_case(feature_name)
snake_name = to_snake_case(feature_name)
table_name = snake_name + 's'
result = template
# Replace FeatureName -> PascalCase
result = result.replace('{{FeatureName}}', pascal_name)
# Replace featureName -> camelCase
result = result.replace('{{featureName}}', camel_name)
# Replace tableName
result = result.replace('{{tableName}}', table_name)
# Generate field-specific content
result = result.replace('{{TableFields}}', generate_table_fields(fields))
result = result.replace('{{TableFieldsSuffix}}', ',\n' if fields else '')
result = result.replace('{{CreateFields}}', generate_create_fields(fields))
result = result.replace('{{FilterFields}}', generate_filter_fields(fields))
result = result.replace('{{MockFields}}', generate_mock_fields(fields))
result = result.replace('{{CreateDtoMock}}', '{' + generate_create_dto(fields) + '}')
result = result.replace('{{UpdateDtoMock}}', '{' + generate_update_dto(fields) + '}')
result = result.replace('{{RelationFields}}', generate_relation_fields(fields))
return result
def create_directory_structure(base_path: str, feature_name: str) -> Path:
"""Create the NestJS module directory structure"""
camel_name = to_camel_case(feature_name)
base = Path(base_path)
# Create directory structure
src_dir = base / camel_name / 'src'
lib_dir = src_dir / 'lib'
controllers_dir = lib_dir / 'controllers'
services_dir = lib_dir / 'services'
dto_dir = lib_dir / 'dto'
schema_dir = lib_dir / 'schema'
for dir_path in [src_dir, lib_dir, controllers_dir, services_dir, dto_dir, schema_dir]:
dir_path.mkdir(parents=True, exist_ok=True)
return lib_dir
def generate_files(feature_name: str, fields: list[dict], output_dir: str):
"""Generate all CRUD files"""
feature_dir = create_directory_structure(output_dir, feature_name)
camel_name = to_camel_case(feature_name)
pascal_name = to_pascal_case(feature_name)
# Generate module
module_template = read_template('module-template.ts')
module_content = replace_placeholders(module_template, feature_name, fields)
(feature_dir / f'{camel_name}-feature.module.ts').write_text(module_content)
# Generate controller
controller_template = read_template('controller-template.ts')
controller_content = replace_placeholders(controller_template, feature_name, fields)
(feature_dir / 'controllers' / f'{camel_name}.controller.ts').write_text(controller_content)
# Generate controller index
(feature_dir / 'controllers' / 'index.ts').write_text(
f"export * from './{camel_name}.controller';\n"
)
# Generate service
service_template = read_template('service-template.ts')
service_content = replace_placeholders(service_template, feature_name, fields)
(feature_dir / 'services' / f'{camel_name}.service.ts').write_text(service_content)
# Generate service index
(feature_dir / 'services' / 'index.ts').write_text(
f"export * from './{camel_name}.service';\n"
)
# Generate DTO
dto_template = read_template('dto-template.ts')
dto_content = replace_placeholders(dto_template, feature_name, fields)
(feature_dir / 'dto' / f'{camel_name}.dto.ts').write_text(dto_content)
# Generate DTO index
(feature_dir / 'dto' / 'index.ts').write_text(
f"export * from './{camel_name}.dto';\n"
)
# Generate table/schema
table_template = read_template('table-template.ts')
table_content = replace_placeholders(table_template, feature_name, fields)
(feature_dir / 'schema' / f'{camel_name}.table.ts').write_text(table_content)
# Generate index
(feature_dir / 'index.ts').write_text(
f"""export * from './{camel_name}-feature.module';
export * from './controllers';
export * from './services';
export * from './dto';
export * from './schema/{camel_name}.table';
"""
)
# Generate test
test_template = read_template('test-template.ts')
test_content = replace_placeholders(test_template, feature_name, fields)
(feature_dir / 'services' / f'{camel_name}.service.spec.ts').write_text(test_content)
# Generate src index
src_dir = feature_dir.parent.parent
(src_dir / 'index.ts').write_text(
f"export * from './lib';\n"
)
print(f"✅ Generated CRUD module for '{pascal_name}' at {feature_dir}")
def main():
parser = argparse.ArgumentParser(description='Generate NestJS Drizzle CRUD module')
parser.add_argument('--feature', required=True, help='Feature name (e.g., user, product)')
parser.add_argument('--fields', required=True, help='JSON array of field definitions')
parser.add_argument('--output', default='./libs/server', help='Output directory')
args = parser.parse_args()
try:
fields = json.loads(args.fields)
except json.JSONDecodeError as e:
print(f"❌ Error parsing fields JSON: {e}")
sys.exit(1)
if not isinstance(fields, list):
print("❌ Error: fields must be a JSON array")
sys.exit(1)
generate_files(args.feature, fields, args.output)
if __name__ == '__main__':
main()
Related skills
Forks & variants (1)
Nestjs Drizzle Crud Generator has 1 known copy in the catalog totaling 21 installs. They canonicalize to this original listing.
- giuseppe-trisciuoglio - 21 installs
How it compares
Use nestjs-drizzle-crud-generator for opinionated full-module codegen; hand-write services when CRUD shape diverges heavily from REST defaults.
FAQ
What does nestjs-drizzle-crud-generator do?
Generate NestJS CRUD modules with Drizzle ORM entities, DTOs, and services.
When should I use nestjs-drizzle-crud-generator?
User needs NestJS CRUD modules, Drizzle schemas, or entity scaffolding.
Is nestjs-drizzle-crud-generator safe to install?
Review the Security Audits panel on this page before installing in production.