Overview
Data schemas are defined inside each API resource folder (e.g. apps/api/src/resources/users/users.schema.ts). They are auto-copied to packages/shared/src/schemas/ during codegen (pnpm --filter shared generate).
Data schema — is a Zod 4 schema that defines shape of the entity. It extends dbSchema from resources/base.schema.ts which provides _id, createdOn, updatedOn, and deletedOn fields.
import { z } from 'zod';
import dbSchema from 'resources/base.schema';
const schema = dbSchema.extend({
firstName: z.string(),
lastName: z.string(),
fullName: z.string(),
email: z.email(),
passwordHash: z.string().nullable().optional(),
isEmailVerified: z.boolean().default(false),
signupToken: z.string().nullable().optional(),
resetPasswordToken: z.string().nullable().optional(),
avatarUrl: z.string().nullable().optional(),
oauth: z.object({
google: z.boolean().default(false),
}).optional(),
lastRequest: z.date().optional(),
}).strict();
export default schema;
This repo uses Zod 4. Use z.email(), z.url(), z.uuid() instead of Zod 3’s z.string().email().
Validation
Zod schemas are used both server-side (in createEndpoint via the schema option) and client-side (via useApiForm or manual zodResolver):
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const schema = z.object({
firstName: z.string().min(1, 'Please enter First name').max(100),
lastName: z.string().min(1, 'Please enter Last name').max(100),
email: z.email('Email format is incorrect.'),
password: z.string().min(6, 'Password must be at least 6 characters'),
});
type SignUpParams = z.infer<typeof schema>
const SignUp = () => {
const methods = useForm<SignUpParams>({
resolver: zodResolver(schema),
});
return (
//your code here
)
}
export default SignUp;
Additionally, data can be validated using the safeParse method:
const parsed = zodSchema.safeParse(data);
if (!parsed.success) {
throw new Error('Invalid data');
}
For more details on Zod, check the documentation.