| name | create-schema |
| description | Create Zod schemas for new entities/resources in the backend. Use when adding a new resource, creating data models, defining DTOs (CreateType, UpdateType), or setting up validation schemas. Triggers on "new schema", "add entity", "create model", "define type". |
Create Schema
Creates Zod schemas with TypeScript type inference for new entities in this backend template.
Quick Reference
Location: src/schemas/{entity-name}.schema.ts
Naming: Singular, kebab-case (e.g., note.schema.ts, course-registration.schema.ts)
Instructions
Step 1: Create the Schema File
Create a new file at src/schemas/{entity-name}.schema.ts
Step 2: Define the Entity ID Schema
Always start with a dedicated ID schema:
import { z } from "zod";
export const {entity}IdSchema = z.string();
export type {Entity}IdType = z.infer<typeof {entity}IdSchema>;
Step 3: Define the Base Entity Schema
export const {entity}Schema = z.object({
id: {entity}IdSchema, // Primary key
// ... entity-specific fields ...
createdBy: userIdSchema, // If entity is user-owned (import from user.schemas)
createdAt: z.date().optional(), // Set by DB/service
updatedAt: z.date().optional(), // Set by DB/service
});
export type {Entity}Type = z.infer<typeof {entity}Schema>;
Step 4: Define Create DTO Schema
Omit system-managed fields and add validation:
export const create{Entity}Schema = {entity}Schema
.omit({
id: true, // Generated by service/system
createdBy: true, // Set from authenticated user context
createdAt: true, // Set by service/database
updatedAt: true, // Set by service/database
})
.extend({
// Add stricter validation for required fields
fieldName: z.string().min(1, "{Entity} field is required for creation."),
});
export type Create{Entity}Type = z.infer<typeof create{Entity}Schema>;
Step 5: Define Update DTO Schema
Make all mutable fields optional:
export const update{Entity}Schema = {entity}Schema
.omit({
id: true, // Part of URL, not body
createdBy: true, // Immutable
createdAt: true, // Immutable
updatedAt: true, // Set by service/database
})
.partial(); // All fields optional for updates
export type Update{Entity}Type = z.infer<typeof update{Entity}Schema>;
Step 6: Define Query Parameters Schema (if needed)
Extend the base query params for filtering:
import { queryParamsSchema } from "./shared.schema";
export const {entity}QueryParamsSchema = queryParamsSchema.extend({
// Entity-specific filters
createdBy: userIdSchema.optional(),
status: z.enum(["active", "inactive"]).optional(),
});
export type {Entity}QueryParamsType = z.infer<typeof {entity}QueryParamsSchema>;
Patterns & Rules
Naming Conventions
- File name:
{entity-name}.schema.ts(singular, kebab-case) - Schema variables:
{entity}Schema,create{Entity}Schema(camelCase) - Type names:
{Entity}Type,Create{Entity}Type(PascalCase)
Import Rules
- Always use path aliases:
import { x } from "@/schemas/..." - Import shared schemas from
./shared.schema - Import user-related schemas from
./user.schemaswhen needed
Schema Design Rules
- Always export both schema and type for each definition
- ID schemas are separate - allows reuse and type narrowing
- Timestamps are optional on the base schema (not present on creation)
- Create schemas omit system-managed fields (id, createdBy, timestamps)
- Update schemas are partial - all fields optional
- Extend base queryParamsSchema for entity-specific filters
Validation Guidelines
- Add
.min(1)for required string fields in create schemas - Use
.optional()for truly optional fields - Use
z.coerce.number()for numeric query params (they come as strings) - Add descriptive error messages:
z.string().min(1, "Field is required")
Cross-Reference Pattern
When referencing other entities:
import { userIdSchema } from "./user.schemas";
import { otherEntityIdSchema } from "./other-entity.schema";
export const myEntitySchema = z.object({
id: myEntityIdSchema,
ownerId: userIdSchema, // Reference to user
relatedId: otherEntityIdSchema, // Reference to another entity
});
Complete Example
See src/schemas/note.schema.ts for a complete reference implementation.
import { z } from "zod";
import { queryParamsSchema } from "./shared.schema";
import { userIdSchema } from "./user.schemas";
// ID Schema
export const noteIdSchema = z.string();
export type NoteIdType = z.infer<typeof noteIdSchema>;
// Base Entity Schema
export const noteSchema = z.object({
id: noteIdSchema,
content: z.string(),
createdBy: userIdSchema,
createdAt: z.date().optional(),
updatedAt: z.date().optional(),
});
export type NoteType = z.infer<typeof noteSchema>;
// Create DTO
export const createNoteSchema = noteSchema
.omit({
id: true,
createdBy: true,
createdAt: true,
updatedAt: true,
})
.extend({
content: z.string().min(1, "Note content is required for creation."),
});
export type CreateNoteType = z.infer<typeof createNoteSchema>;
// Update DTO
export const updateNoteSchema = noteSchema
.omit({
id: true,
createdBy: true,
createdAt: true,
updatedAt: true,
})
.partial();
export type UpdateNoteType = z.infer<typeof updateNoteSchema>;
// Query Parameters
export const noteQueryParamsSchema = queryParamsSchema.extend({
createdBy: userIdSchema.optional(),
});
export type NoteQueryParamsType = z.infer<typeof noteQueryParamsSchema>;
Shared Schemas Reference
The following are available from @/schemas/shared.schema:
queryParamsSchema- Base pagination/sorting params (search, sortBy, sortOrder, page, limit)paginatedResultsSchema(dataSchema)- Generic paginated response wrapperentityIdParamSchema(paramName)- URL path parameter validationDEFAULT_PAGE,DEFAULT_LIMIT- Pagination defaults
What NOT to Do
- Do NOT use
process.env- environment config belongs insrc/env.ts - Do NOT create barrel exports (index.ts) - use explicit imports
- Do NOT use plural names (
notes.schema.ts) - use singular (note.schema.ts) - Do NOT define business logic in schemas - schemas are for structure/validation only
- Do NOT skip type exports - always export both schema and inferred type