Overview
Use Zod schemas for validation in Elysia routes.
Features
- Zod integration
- Error formatting
- Validation middleware
- Common schemas
Installation
npx hanma add elysia-zod --framework elysia
Dependencies
zod
Code
import { Elysia, t } from "elysia";
import { z, ZodSchema, ZodError } from "zod";
/**
* Convert Zod schema to TypeBox schema for route validation
* Note: This is a basic conversion for common types
*/
export const zodToTypeBox = (schema: ZodSchema): ReturnType<typeof t.Unknown> => {
// For complex schemas, use raw validation
return t.Unknown();
};
/**
* Format Zod validation errors into a structured format
*/
export const formatZodErrors = (error: ZodError) => {
return error.errors.map((err) => ({
path: err.path.join("."),
message: err.message,
code: err.code,
}));
};
/**
* Create a validation middleware using Zod schema
*/
export const zodValidate = <T extends ZodSchema>(
schema: T,
source: "body" | "query" | "params" = "body"
) => {
return ({ [source]: data, set }: { [key: string]: unknown; set: { status: number } }) => {
const result = schema.safeParse(data);
if (!result.success) {
set.status = 400;
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Validation failed",
details: formatZodErrors(result.error),
},
};
}
// Replace with validated data
return { validated: result.data as z.infer<T> };
};
};
/**
* Elysia plugin for Zod validation
*/
export const zodPlugin = new Elysia({ name: "zod-plugin" })
.error({
ZodValidationError: class ZodValidationError extends Error {
constructor(
message: string,
public readonly errors: ReturnType<typeof formatZodErrors>
) {
super(message);
this.name = "ZodValidationError";
}
},
})
.onError(({ code, error, set }) => {
if (code === "ZodValidationError") {
set.status = 400;
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: error.message,
details: (error as any).errors,
},
};
}
});
/**
* Validate body with Zod schema
*/
export const validateBody = <T extends ZodSchema>(schema: T) => {
return async ({ body, set }: { body: unknown; set: { status: number } }) => {
const result = schema.safeParse(body);
if (!result.success) {
set.status = 400;
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Request body validation failed",
details: formatZodErrors(result.error),
},
};
}
};
};
/**
* Validate query params with Zod schema
*/
export const validateQuery = <T extends ZodSchema>(schema: T) => {
return async ({ query, set }: { query: unknown; set: { status: number } }) => {
const result = schema.safeParse(query);
if (!result.success) {
set.status = 400;
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Query params validation failed",
details: formatZodErrors(result.error),
},
};
}
};
};
/**
* Common Zod schemas
*/
export const zodSchemas = {
email: z.string().email("Invalid email address"),
uuid: z.string().uuid("Invalid UUID format"),
url: z.string().url("Invalid URL format"),
phone: z.string().regex(/^\+?[1-9]\d{1,14}$/, "Invalid phone number"),
slug: z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "Invalid slug format"),
pagination: z.object({
page: z.coerce.number().int().min(1).default(1),
perPage: z.coerce.number().int().min(1).max(100).default(20),
}),
sort: z.object({
sortBy: z.string().optional(),
sortOrder: z.enum(["asc", "desc"]).default("asc"),
}),
id: z.object({
id: z.string().uuid(),
}),
};
/* USAGE:
import { Elysia } from "elysia";
import { z } from "zod";
import { zodPlugin, validateBody, validateQuery, zodSchemas } from "./validation/zod";
// Define schemas with Zod
const CreateUserSchema = z.object({
email: zodSchemas.email,
password: z.string().min(8, "Password must be at least 8 characters"),
name: z.string().min(2).max(100),
age: z.number().int().positive().optional(),
});
const UpdateUserSchema = CreateUserSchema.partial();
const app = new Elysia()
.use(zodPlugin)
.post("/users", async ({ body }) => {
// Validate with Zod
const result = CreateUserSchema.safeParse(body);
if (!result.success) {
return {
success: false,
error: {
code: "VALIDATION_ERROR",
message: "Validation failed",
details: result.error.errors,
},
};
}
// result.data is typed as z.infer<typeof CreateUserSchema>
const user = result.data;
return { success: true, user };
})
// Using beforeHandle for validation
.put("/users/:id", ({ body, params }) => {
return { updated: true };
}, {
beforeHandle: [validateBody(UpdateUserSchema)],
})
// Query validation
.get("/users", ({ query }) => {
const result = zodSchemas.pagination.merge(zodSchemas.sort).safeParse(query);
return { users: [], pagination: result.success ? result.data : {} };
})
.listen(3000);
*/
Usage
import { z } from 'zod';
import { validateBody, zodSchemas } from './validation/zod';
const CreateUserSchema = z.object({
email: zodSchemas.email,
password: z.string().min(8),
});
app.post('/users', () => {}, { beforeHandle: [validateBody(CreateUserSchema)] });
Dependencies
zod
Frequently Asked Questions
How do I install Zod Integration?
Run the following command:
npx hanma add elysia-zod --framework elysiaWhat dependencies are required?
This snippet requires: zod. Make sure to install them before using the snippet.
How do I use this snippet?
Copy the code and paste it into your project. The snippet will be saved to
src/validation/zod.ts.Can I customize this snippet?
Yes! All snippets are copy-paste code that you own. Feel free to modify them to fit your needs.
Does this work with other frameworks?
This snippet is designed for the specific framework. Check the other framework pages for equivalent implementations.