create-schema
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".
$ インストール
git clone https://github.com/madooei/backend-template /tmp/backend-template && cp -r /tmp/backend-template/.claude/skills/create-schema ~/.claude/skills/backend-template// tip: Run this command in your terminal to install the skill
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
Repository
