On this Guide
- Lesson 06: Schema Composition with Merge, Extend, Pick, and Omit
- Lesson 07: Refinement, superRefine, and Custom Validators
- Lesson 08: Discriminated Unions and Tagged Types
- Lesson 09: Transforming and Preprocessing Data
- Lesson 10: Working with Records, Maps, Sets, and Enums
- Lesson 11: Async Validation with Promises
- Lesson 12: Reusable Schemas Across Frontend and Backend
Why Async Validation?
Some validation rules canβt be checked synchronously:
- Is this email already taken?
- Does the user exist in the database?
- Did a remote API respond successfully?
Zod supports asynchronous validation with methods like .refineAsync() and schema types like z.promise().
Using refineAsync()
It works just like .refine(), but returns a promise:
const EmailSchema = z
.string()
.email()
.refineAsync(
async (email) => {
const isTaken = await fakeCheckEmail(email);
return !isTaken;
},
{
message: "Email already in use",
}
);
async function fakeCheckEmail(email: string) {
return email === "taken@example.com"; // Simulated DB check
}
Usage
You must use await with .parseAsync():
const result = await EmailSchema.parseAsync("taken@example.com");
// β Throws: Email already in use
Validating Async Data Sources
You can validate an entire schema asynchronously:
const RegisterSchema = z
.object({
username: z.string(),
email: z.string().email(),
})
.refineAsync(
async (data) => {
return !(await isUserTaken(data.username));
},
{
message: "Username already taken",
path: ["username"],
}
);
This lets you apply async logic with context and specific field paths.
Using z.promise()
Sometimes your schema returns a promise. You can validate that too:
const AsyncNumberSchema = z.promise(z.number());
AsyncNumberSchema.parse(Promise.resolve(42)); // β
AsyncNumberSchema.parse(Promise.resolve("hello")); // β
Zod will wait for the promise to resolve and validate the result.
Caveats and Best Practices
- Always use
.parseAsync()or.safeParseAsync()when your schema usesrefineAsync() - Avoid mixing sync and async
parse()β it’s easy to forget - Use
.superRefine()andctx.addIssue()if you need detailed async logic across multiple fields
Summary
- Use
.refineAsync()for async field-level validation - Use
.parseAsync()or.safeParseAsync()to validate data that involvesawait - Use
z.promise()to validate promise-returning values - Async validation is ideal for working with remote APIs and databases
Next: Lesson 12 β Building Reusable Schemas Across Frontend and Backend
Master the Code, Be the Guru!