MX Space project Zod schema patterns. Apply when creating DTOs, validation schemas, or handling request validation.
Nest 12 validates request parameters with StandardSchemaValidationPipe and
@Body / @Query / @Param({ schema }). Keep Zod schemas; infer types from
them. Do not introduce createZodDto or nestjs-zod.
import { z } from 'zod'
// Define Schema
export const MySchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().positive().optional(),
})
export type MyInput = z.infer<typeof MySchema>
// Partial schema for updates
export const PartialMySchema = MySchema.partial()
export type PartialMyInput = z.infer<typeof PartialMySchema>
Controller wiring:
@Post('/')
async create(@Body({ schema: MySchema }) body: MyInput) {
return this.service.create(body)
}
@Patch('/:id')
async patch(
@Param({ schema: EntityIdSchema }) params: EntityIdInput,
@Body({ schema: PartialMySchema }) body: PartialMyInput,
) {}
Location: apps/core/src/common/zod/
import {
// From primitives.ts:
zNonEmptyString, // Non-empty string (z.string().min(1))
zCoerceInt, // Coerced integer
zCoercePositiveInt, // Coerced positive integer
zCoerceBoolean, // Coerced boolean (handles 'true'/'1'/1/etc.)
zCoerceDate, // Coerced date
zOptionalDate, // Optional date (null/empty → undefined)
zOptionalBoolean, // Optional coerced boolean
zEmptyStringToNull, // Empty string → null, else string
zNilOrString, // string | null | undefined
zHexColor, // Hex color (#fff or #ffffff)
zAllowedUrl, // HTTP or HTTPS URL
zStrictUrl, // Strict URL validation
zHttpsUrl, // HTTPS-only URL
zPaginationPage, // Coerced int, min 1, default 1
zPaginationSize, // Coerced int, min 1, max 50, default 20
zSortOrder, // 1 | -1 | undefined (accepts 'asc'/'desc')
zArrayUnique, // Unique array elements (generic)
zUniqueStringArray, // Unique non-empty string array
// From custom.ts:
zBooleanOrString, // boolean | string union
zTransformEmptyNull, // Empty string → null (generic wrapper)
zTransformBoolean, // Transform to optional boolean
zPinDate, // Pin date (Date | null | undefined, true=now, false=null)
zSlug, // Slug string (trimmed)
zEmail, // Email with custom message
zUrl, // URL with custom message
zMaxLengthString, // Max length string factory
zRefTypeTransform, // Content ref type ('post'→'Post', etc.)
zPrefer, // 'lexical' enum optional
zLang, // 2-char language code
// From shared/id/entity-id.ts:
zEntityId, // Snowflake entity ID string validation
zEntityIdOrInt, // Entity ID or positive integer union
} from '~/common/zod'
import { zEntityId } from '~/common/zod'
const Schema = z.object({
id: zEntityId, // Snowflake ID string
categoryId: zEntityId, // Foreign key reference
relatedIds: z.array(zEntityId), // Array of entity IDs
})
// For path params:
import { EntityIdSchema, type EntityIdDto } from '~/shared/dto/id.dto'
// @Param({ schema: EntityIdSchema }) params: EntityIdDto
// Compose schemas using .extend()
const PostSchema = z.object({
title: zNonEmptyString,
slug: zSlug,
categoryId: zEntityId,
tags: z.array(z.string()).optional(),
contentFormat: z.enum(['markdown', 'lexical']),
})
z.boolean().default(true).optional()
z.number().default(0).optional()
z.array(z.string()).default([]).optional()
// Empty string to null
z.preprocess(
(val) => (val === '' ? null : val),
z.string().nullable(),
).optional()
// String to number
z.preprocess(
(val) => (typeof val === 'string' ? parseInt(val, 10) : val),
z.number(),
)
z.union([z.string(), z.number()])
z.enum(['draft', 'published', 'archived'])
// Basic array
z.array(z.string())
// Length constraints
z.array(z.string()).min(1).max(10)
// Unique elements
zArrayUnique(z.string())
const AddressSchema = z.object({
street: z.string(),
city: z.string(),
})
const UserSchema = z.object({
name: z.string(),
address: AddressSchema.optional(),
addresses: z.array(AddressSchema).optional(),
})
// refine for custom validation
z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: 'Passwords must match',
})
// Infer type from Schema
type MyType = z.infer<typeof MySchema>
// Use in Service
async create(data: z.infer<typeof MySchema>) {
return this.repository.create(data)
}
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer