typeorm-zod
v1.0.0
Published
Seamless integration between TypeORM entities and Zod validation using WeakMap-based metadata storage to prevent cross-entity pollution
Maintainers
Readme
typeorm-zod
Seamless integration between TypeORM entities and Zod validation using WeakMap-based metadata storage to prevent cross-entity pollution.
Features
- Pollution-Free Metadata Storage: WeakMap-based metadata storage prevents entity metadata pollution
- Inheritance-Aware Schema Generation: Includes base class properties automatically
- Circular Dependency Safe: Proper handling of circular dependencies between entities
- Property Name Conflict Resolution: Different entities can have properties with the same name
- Automatic Schema Variants: Create/update/patch schema variants generated automatically
- TypeORM Integration: Seamless integration with existing TypeORM decorators
- Production Ready: Comprehensive error handling and validation
Code Generation (Codegen)
Requirements: The codegen CLI requires Bun to be installed:
curl -fsSL https://bun.sh/install | bashTo further enhance developer experience and ensure perfect type safety, typeorm-zod now includes a powerful Code Generation (Codegen) feature. This feature automatically generates centralized schema files, static TypeScript type definitions (DTOs), and validation helper functions directly from your decorated entities.
Benefits of Codegen:
- Eliminates
z.inferlimitations: Provides true static type definitions for your DTOs, enabling full IDE autocompletion and compile-time type checking. - Single Source of Truth: All schemas, types, and validators are exported from a single generated file.
- Automated Boilerplate: Reduces manual schema and type definition, especially for complex entities and multiple schema variants.
- Seamless Integration: Generated types and validators are ready for use in API routes, service layers, and frontend applications.
How to Use Codegen:
- Create a Configuration File: Define
typeorm-zod.codegen.config.ts(or.js) in your project root. This file specifies entity locations, output paths, and custom naming conventions. - Run the Codegen CLI:
- Run the CLI using
bunx:bunx typeorm-zod-codegen - Alternatively, you can add a script to your
package.jsonto run the codegen:
Then, you can execute it with Bun:"scripts": { "codegen": "typeorm-zod-codegen" }bun run codegen
- Run the CLI using
- Integrate into Workflow: Add the
codegencommand to your build pipeline (e.g., pre-build script) and consider using--watchmode during development.
For detailed configuration, examples, and advanced usage, please refer to the Code Generation Feature Documentation.
Quick Start
With typeorm-zod, you define validation once and get comprehensive schemas:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { ZodProperty, ZodColumn, createEntitySchemas } from 'typeorm-zod';
import { z } from 'zod';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
@ZodProperty(z.string().uuid())
id: string;
@ZodColumn({ type: 'varchar', length: 255 }, z.string().min(1).max(255))
name: string;
@ZodColumn({ type: 'varchar', length: 255, unique: true }, z.string().email())
email: string;
@ZodColumn({ type: 'int', nullable: true }, z.number().int().positive().nullable())
age?: number;
}
// Generate comprehensive schema collection
const userSchemas = createEntitySchemas(User);
// Available: full, create, update, patch, query schemasInstallation
npm install typeorm-zod
# or
bun add typeorm-zod
# or
yarn add typeorm-zod
# or
pnpm add typeorm-zodInheritance Support
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
// Base entity class
export abstract class AppEntity {
@PrimaryGeneratedColumn('uuid')
@ZodProperty(z.string().uuid())
id: string;
@CreateDateColumn()
@ZodProperty(z.date())
createdAt: Date;
@UpdateDateColumn()
@ZodProperty(z.date())
updatedAt: Date;
}
// Child entity inherits validation
@Entity()
export class Product extends AppEntity {
@ZodColumn({ type: 'varchar', length: 255 }, z.string().min(1))
name: string;
@ZodColumn({ type: 'decimal', precision: 10, scale: 2 }, z.number().positive())
price: number;
}
// Automatically includes base class properties
const productSchemas = createEntitySchemas(Product);
// Schemas include: id, createdAt, updatedAt, name, priceAvailable Schema Variants
The createEntitySchemas() function generates 5 schema variants:
const userSchemas = createEntitySchemas(User);
// Full schema - includes all fields
userSchemas.full;
// Create schema - omits id, createdAt, updatedAt, deletedAt
userSchemas.create;
// Update schema - id required, everything else optional
userSchemas.update;
// Patch schema - all fields optional
userSchemas.patch;
// Query schema - all fields optional (for filtering)
userSchemas.query;Per-Property Schema Control
Use the enhanced @ZodProperty decorator to exclude fields from specific schema variants:
@Entity()
export class Note extends AppEntity {
@Column('varchar', { length: 500 })
@ZodProperty(z.string().min(1).max(500))
title: string;
@Column('longtext')
@ZodProperty(z.string())
content: string;
// Auto-managed version field - exclude from create/update/patch
@VersionColumn()
@ZodProperty({
schema: z.number().int().min(0),
skip: ['create', 'update', 'patch']
})
version: number;
// Auto-generated timestamp - exclude only from create
@UpdateDateColumn()
@ZodProperty({
schema: z.date(),
skip: ['create']
})
updatedAt: Date;
}
// Generated schemas automatically respect skip settings:
const noteSchemas = createEntitySchemas(Note);
// noteSchemas.create: { title, content } - no version or updatedAt
// noteSchemas.update: { id, title?, content?, updatedAt? } - no version
// noteSchemas.full: { id, title, content, version, createdAt, updatedAt } - all fieldsAdvanced Usage with Custom Options
const userSchemas = createEntitySchemas(User, {
// Additional fields to omit from create schema
omitFromCreate: ['internalId'],
// Additional fields to omit from update schema
omitFromUpdate: ['email'], // Email cannot be updated
// Custom field transformations
transforms: {
email: (schema) => schema.toLowerCase().trim(),
age: (schema) => schema.min(13).max(120) // Add age constraints
}
});Type Inference
All TypeScript types are automatically inferred:
type CreateUserDto = z.infer<typeof userSchemas.create>;
type UpdateUserDto = z.infer<typeof userSchemas.update>;
type UserQueryDto = z.infer<typeof userSchemas.query>;
// Perfect type safety
const createUser = (data: CreateUserDto) => {
// data.name is string
// data.email is string | undefined
// data is fully typed and validated
};API Route Validation
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
const app = new Hono();
// Create user endpoint
app.post('/users',
zValidator('json', userSchemas.create),
async (c) => {
const userData = c.req.valid('json');
// userData is fully typed and validated
const user = userRepository.create(userData);
await userRepository.save(user);
return c.json({ success: true, user });
}
);
// Update user endpoint
app.patch('/users/:id',
zValidator('json', userSchemas.patch),
async (c) => {
const updateData = c.req.valid('json');
const userId = c.req.param('id');
await userRepository.update(userId, updateData);
return c.json({ success: true });
}
);API Reference
Decorators
@ZodProperty(zodSchema) or @ZodProperty({ schema, skip? })
Adds Zod validation to any property with optional schema control:
Basic Usage:
@ZodProperty(z.string().min(1).max(100))
name: string;
@ZodProperty(z.number().int().positive())
age: number;
@ZodProperty(z.string().email().optional())
email?: string;Advanced Usage with Per-Property Schema Control:
// Skip validation for specific schema variants
@ZodProperty({
schema: z.number().int().min(0),
skip: ['create', 'update', 'patch'] // Exclude from these schemas
})
version: number; // Auto-managed by TypeORM @VersionColumn
@ZodProperty({
schema: z.date(),
skip: ['create'] // Only exclude from create schema
})
updatedAt: Date; // Auto-managed, but allow in update/patch
@ZodProperty({
schema: z.string().uuid(),
skip: ['create', 'update'] // Only include in full, patch, and query
})
id: string; // Primary key - exclude from create/updateSchema Variants:
'full'- Complete entity schema with all fields'create'- For creating new entities (typically excludes auto-generated fields)'update'- For updating existing entities (id required, others optional)'patch'- For partial updates (all fields optional)'query'- For filtering/searching (all fields optional)
@ZodColumn(columnOptions, zodSchema)
Combines TypeORM @Column() with Zod validation:
@ZodColumn(
{ length: 255, nullable: false },
z.string().min(1).max(255)
)
name: string;
// Equivalent to:
@Column({ length: 255, nullable: false })
@ZodProperty(z.string().min(1).max(255))
name: string;Schema Generation
createEntitySchemas<T>(entityClass, options?)
Generates all schema variants from an entity class.
Parameters:
entityClass: Entity class constructoroptions?: Schema generation options
Returns: EntitySchemas<T> with full, create, update, patch, query schemas.
createCreateSchema<T>(entityClass, options?)
Generates only the create schema (convenience function).
createUpdateSchema<T>(entityClass, options?)
Generates only the update schema (convenience function).
Options
interface SchemaGenerationOptions {
/** Additional fields to omit from create schema */
omitFromCreate?: string[];
/** Fields to omit from update schema */
omitFromUpdate?: string[];
/** Custom field transformations */
transforms?: Record<string, (schema: z.ZodTypeAny) => z.ZodTypeAny>;
}Advanced Usage
Custom Transforms
const UserSchemas = createEntitySchemas(User, {
transforms: {
// Transform email field for create schema
email: (schema) => schema.transform(email => email.toLowerCase())
}
});Migration Strategy
For existing projects, you can migrate gradually:
- Add decorators to existing entities:
// Before
@Column()
name: string;
// After
@ZodProperty(z.string().min(1).max(255))
@Column()
name: string;- Generate schemas and replace manual ones:
// Replace manual schemas
const UserSchemas = createEntitySchemas(User);
export const CreateUserSchema = UserSchemas.create;- Remove duplicate types and use inferred ones:
// Remove manual type definitions
type CreateUserDto = z.infer<typeof CreateUserSchema>;Benefits
- ✅ Zero Duplication - Single source of truth
- ✅ Type Safety - Perfect TypeScript integration
- ✅ Validation - Automatic request validation
- ✅ Productivity - Write less, get more
- ✅ Maintainability - Update once, everywhere benefits
- ✅ Migration Friendly - Works with existing projects
Requirements
- TypeORM >= 0.3.0
- Zod >= 4.0.0
- TypeScript >= 5.0.0
reflect-metadatapackage
License
MIT © Angel S. Moreno
Contributing
Contributions are welcome! Please read our Contributing Guidelines to get started.
Code of Conduct
This project follows a Code of Conduct. Please read it to understand the expected behavior when participating in this project.
