--- 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: ```typescript import { z } from "zod"; export const {entity}IdSchema = z.string(); export type {Entity}IdType = z.infer; ``` ### Step 3: Define the Base Entity Schema ```typescript 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; ``` ### Step 4: Define Create DTO Schema Omit system-managed fields and add validation: ```typescript 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; ``` ### Step 5: Define Update DTO Schema Make all mutable fields optional: ```typescript 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; ``` ### Step 6: Define Query Parameters Schema (if needed) Extend the base query params for filtering: ```typescript 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; ``` ## 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.schemas` when needed ### Schema Design Rules 1. **Always export both schema and type** for each definition 2. **ID schemas are separate** - allows reuse and type narrowing 3. **Timestamps are optional** on the base schema (not present on creation) 4. **Create schemas omit** system-managed fields (id, createdBy, timestamps) 5. **Update schemas are partial** - all fields optional 6. **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: ```typescript 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. ```typescript 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; // 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; // 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; // Update DTO export const updateNoteSchema = noteSchema .omit({ id: true, createdBy: true, createdAt: true, updatedAt: true, }) .partial(); export type UpdateNoteType = z.infer; // Query Parameters export const noteQueryParamsSchema = queryParamsSchema.extend({ createdBy: userIdSchema.optional(), }); export type NoteQueryParamsType = z.infer; ``` ## 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 wrapper - `entityIdParamSchema(paramName)` - URL path parameter validation - `DEFAULT_PAGE`, `DEFAULT_LIMIT` - Pagination defaults ## What NOT to Do - Do NOT use `process.env` - environment config belongs in `src/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