I'm building a microservice with Moleculer.js and TypeScript, using Zod v4 for schema validation. I need to validate API responses that return null for optional fields, while keeping strict validation for request payloads.
My API returns employee data with null values for optional fields:
{
"firstName": "John",
"lastName": "Doe",
"externalRefId": null, // null from API
"profilePhotoUrl": null, // null from API
"countryOfBirth": null, // null from API
"employeeStatus": null, // null from API
// ... more fields with null
}
My base schema uses .optional():
export const EmployeeSchema = z.object({
id: z.string().optional(),
firstName: z.string(),
lastName: z.string(),
externalRefId: z.string().optional(),
profilePhotoUrl: z.string().optional(),
employeeStatus: EmployeeStatusSchema.optional(),
// ... 20+ more fields
});
When I validate responses, I get errors:
{
"message": "Validation failed",
"errors": [
{
"field": "externalRefId",
"code": "invalid_type",
"message": "Invalid input: expected string, received null"
},
{
"field": "profilePhotoUrl",
"code": "invalid_type",
"message": "Invalid input: expected string, received null"
}
// ... more fields
]
}
I've Tried
Using
.nullish()on the entire schema - doesn't work, fields still rejectnull:export const CreateEmployeeResponseSchema = EmployeeSchema.nullish(); // Still fails validationMaking base fields nullable, then removing nullable for requests:
// Made base schema nullable export const EmployeeSchema = z.object({ externalRefId: z.string().nullable(), profilePhotoUrl: z.string().nullable(), // ... }); // Tried to make strict for requests by extending export const CreateEmployeeRequestSchema = EmployeeSchema.extend({ firstName: z.string().min(1), // But can't "remove" nullable from inherited fields }); // Problem: Request schema still accepts null values
Requirements
Request validation: Must enforce required fields (no null allowed)
Response validation: Must accept null values from API
DRY: Avoid duplicating the entire 30+ field schema manually
Current Service Structure:
// employee.models.ts
export const CreateEmployeeRequestSchema = EmployeeSchema.extend({
firstName: z.string().min(1, "First name is required"),
// ... strict validation
});
export const CreateEmployeeResponseSchema = EmployeeSchema.nullish();
// employee.helpers.ts
async createEmployee(payload: CreateEmployeeRequest): Promise<CreateEmployeeResponse | null> {
const employee = await this.tcatEmployeeProxy.createEmployee(payload);
return validateSchema(CreateEmployeeResponseSchema, employee); // Fails here
}
Environment:
Zod: v4 (latest)
TypeScript: 5.x
Moleculer: Latest
Node.js: 18+
What's the recommended approach in Zod v4 to:
Reuse a base schema for both strict input validation and flexible output validation?
Handle API responses with
nullvalues without manually redefining all fields?Remove nullable constraints when extending schemas for stricter validation?
Maintain type safety for both input and output types?
Is there a utility function or pattern in Zod v4 that can convert all .optional() fields to .nullish() for response schemas, or remove .nullable() from fields for request schemas?