|
2 | 2 | sidebar_position: 3 |
3 | 3 | --- |
4 | 4 |
|
5 | | -# Data Validation (Zod) |
| 5 | +# Data Validation |
6 | 6 |
|
7 | | -This data validation is came built-in with Yelix. It uses [Zod](https://zod.dev/). |
| 7 | +Yelix comes with a powerful built-in data validation system inspired by Zod but with additional features. |
8 | 8 |
|
9 | 9 | ## Initial Setup |
10 | 10 |
|
11 | | -Data Validation is built-in but by default it is not set. You need to set it in your main file. |
| 11 | +Data Validation is built-in but needs to be enabled in your main file. |
12 | 12 |
|
13 | 13 | ```ts title="main.ts" |
14 | | -import { Yelix, requestDataValidationMiddleware } from 'jsr:@murat/yelix'; |
| 14 | +import { Yelix, requestDataValidationYelixMiddleware } from 'jsr:@murat/yelix'; |
15 | 15 |
|
16 | 16 | export async function startServer() { |
17 | 17 | const app = new Yelix(); |
18 | | - |
19 | | - // highlight-next-line |
20 | | - app.setMiddleware('dataValidation', requestDataValidationMiddleware); |
21 | | - |
| 18 | + app.setMiddleware('dataValidation', requestDataValidationYelixMiddleware); |
22 | 19 | app.serve(); |
23 | 20 | } |
24 | 21 |
|
25 | 22 | await startServer(); |
26 | | - |
27 | 23 | ``` |
28 | 24 |
|
29 | | -## Query Parameters |
| 25 | +## Basic Example |
30 | 26 |
|
31 | 27 | ```ts title="hello.ts" |
32 | | -import { Ctx, ValidationType } from "jsr:@murat/yelix"; |
33 | | -import { z } from "npm:zod@^3.24.1"; |
| 28 | +import { Ctx, ValidationType, inp } from "jsr:@murat/yelix"; |
34 | 29 |
|
35 | | -export async function GET(ctx: Ctx) { |
36 | | - // highlight-start |
| 30 | +export async function POST(ctx: Ctx) { |
37 | 31 | const requestData = ctx.get('dataValidation').user; |
38 | | - const query: QueryType = requestData.query; |
39 | | - // highlight-end |
40 | | - |
41 | | - return await ctx.text('Hello, ' + query.name, 200); |
| 32 | + const { username, email } = requestData.body; |
| 33 | + return await ctx.text(`Hello, ${username}!`, 200); |
42 | 34 | } |
43 | 35 |
|
44 | 36 | export const path = '/api/hello'; |
45 | | -// highlight-next-line |
46 | 37 | export const middlewares = ['dataValidation']; |
47 | 38 |
|
48 | | -// highlight-start |
49 | 39 | export const validation: ValidationType = { |
50 | | - query: { |
51 | | - name: z.string(), |
52 | | - }, |
| 40 | + body: inp().object({ |
| 41 | + username: inp().string().min(3).max(255), |
| 42 | + email: inp().string().email() |
| 43 | + }) |
53 | 44 | }; |
54 | | -const querySchema = z.object(validation.query as z.ZodRawShape); |
55 | | -type QueryType = z.infer<typeof querySchema>; |
56 | | -// highlight-end |
57 | 45 | ``` |
58 | 46 |
|
59 | | -## Body Parameters |
| 47 | +## Available Validators |
| 48 | + |
| 49 | +### String Validation |
| 50 | +```ts |
| 51 | +inp().string() |
| 52 | + .min(3) // Minimum length |
| 53 | + .max(255) // Maximum length |
| 54 | + .length(10) // Exact length |
| 55 | + .email() // Email format |
| 56 | + .url() // URL format |
| 57 | + .regex(/pattern/) // Regular expression |
| 58 | + .includes("text") // Contains text |
| 59 | + .startsWith("prefix") // Starts with |
| 60 | + .endsWith("suffix") // Ends with |
| 61 | + .trim() // Trim whitespace |
| 62 | + .toLowerCase() // Convert to lowercase |
| 63 | + .toUpperCase() // Convert to uppercase |
| 64 | + .ip() // IP address (v4/v6) |
| 65 | + .date() // Date string format |
| 66 | + .time() // Time string format |
| 67 | + .datetime() // ISO datetime format |
| 68 | + .base64() // Base64 string |
| 69 | + .optional() // Make field optional |
| 70 | +``` |
60 | 71 |
|
61 | | -```ts title="hello.ts" |
62 | | -import { Ctx, ValidationType } from "jsr:@murat/yelix"; |
63 | | -import { z } from "npm:zod@^3.24.1"; |
| 72 | +### Number Validation |
| 73 | +```ts |
| 74 | +inp().number() |
| 75 | + .min(0) // Minimum value |
| 76 | + .max(100) // Maximum value |
| 77 | + .range(0, 100) // Value range |
| 78 | + .integer() // Must be integer |
| 79 | + .positive() // Must be positive |
| 80 | + .negative() // Must be negative |
| 81 | + .multipleOf(5) // Multiple of value |
| 82 | + .finite() // Must be finite |
| 83 | + .safe() // Safe integer |
| 84 | + .optional() // Make field optional |
| 85 | +``` |
64 | 86 |
|
65 | | -export async function POST(ctx: Ctx) { |
66 | | - // highlight-start |
67 | | - const requestData = ctx.get('dataValidation').user; |
68 | | - const { |
69 | | - fullname, |
70 | | - username, |
71 | | - email, |
72 | | - password, |
73 | | - }: BodyType = requestData.body; |
74 | | - // highlight-end |
75 | | - |
76 | | - return await ctx.text('Hello, ' + fullname, 200); |
77 | | -} |
| 87 | +### Array Validation |
| 88 | +```ts |
| 89 | +inp().array() |
| 90 | + .min(1) // Minimum length |
| 91 | + .max(10) // Maximum length |
| 92 | + .length(5) // Exact length |
| 93 | + .notEmpty() // Must not be empty |
| 94 | + .unique() // All elements unique |
| 95 | + .includes(value) // Must include value |
| 96 | + .every(validator) // All items must match |
| 97 | + .some(validator) // Some items must match |
| 98 | + .optional() // Make field optional |
| 99 | +``` |
78 | 100 |
|
79 | | -export const path = '/api/hello'; |
80 | | -// highlight-next-line |
81 | | -export const middlewares = ['dataValidation']; |
| 101 | +### Object Validation |
| 102 | +```ts |
| 103 | +inp().object({ |
| 104 | + name: inp().string(), |
| 105 | + age: inp().number(), |
| 106 | + tags: inp().array() |
| 107 | +}) |
| 108 | + .hasKey("field") // Must have key |
| 109 | + .minKeys(1) // Minimum keys |
| 110 | + .maxKeys(10) // Maximum keys |
| 111 | + .exactKeys(["id", "name"]) // Must have exact keys |
| 112 | + .optional() // Make field optional |
| 113 | +``` |
82 | 114 |
|
83 | | -// highlight-start |
84 | | -export const validation: ValidationType = { |
85 | | - body: z.object({ |
86 | | - fullname: z.string().min(3).max(255), |
87 | | - username: z |
88 | | - .string() |
89 | | - .min(3) |
90 | | - .max(255) |
91 | | - .refine((value) => !/@/.test(value), { |
92 | | - message: 'Username should not contain @', |
93 | | - }) |
94 | | - .refine((value) => !/\s/.test(value), { |
95 | | - message: 'Username should not contain whitespace', |
96 | | - }) |
97 | | - .refine((value) => value === value.toLowerCase(), { |
98 | | - message: 'Username should be lowercase', |
99 | | - }), |
100 | | - email: z.string().email(), |
101 | | - password: z.string().min(8).max(255), |
| 115 | +### Date Validation |
| 116 | +```ts |
| 117 | +inp().date() |
| 118 | + .min(new Date("2024-01-01")) // Minimum date |
| 119 | + .max(new Date("2024-12-31")) // Maximum date |
| 120 | + .format("yyyy-MM-dd") // Format date |
| 121 | + .timezone("America/New_York") // Set timezone |
| 122 | + .future() // Must be future |
| 123 | + .past() // Must be past |
| 124 | + .weekday([1,2,3,4,5]) // Valid weekdays |
| 125 | + .age(18) // Minimum age |
| 126 | + .optional() // Make field optional |
| 127 | +``` |
| 128 | + |
| 129 | +### File Validation |
| 130 | +```ts |
| 131 | +inp().file() |
| 132 | + .multipleFiles() // Allow multiple files |
| 133 | + .minFilesCount(1) // Minimum files |
| 134 | + .maxFilesCount(5) // Maximum files |
| 135 | + .minSize(1024) // Minimum size (bytes) |
| 136 | + .maxSize(5 * 1024 * 1024) // Maximum size (bytes) |
| 137 | + .mimeType(['image/jpeg', 'image/png']) // Valid mime types |
| 138 | + .optional() // Make field optional |
| 139 | +``` |
| 140 | + |
| 141 | +## Complete Example |
| 142 | + |
| 143 | +```ts |
| 144 | +export const validation: ValidationTypeBETA = { |
| 145 | + query: { |
| 146 | + page: inp().number().integer().min(1).optional(), |
| 147 | + limit: inp().number().integer().range(1, 100) |
| 148 | + }, |
| 149 | + body: inp().object({ |
| 150 | + username: inp().string().min(3).max(255), |
| 151 | + email: inp().string().email(), |
| 152 | + age: inp().number().range(18, 99), |
| 153 | + profile: inp().object({ |
| 154 | + bio: inp().string().max(1000).optional(), |
| 155 | + interests: inp().array().every(inp().string()).max(10) |
| 156 | + }), |
| 157 | + avatar: inp().file().maxSize(5 * 1024 * 1024).mimeType(['image/jpeg', 'image/png']) |
102 | 158 | }), |
| 159 | + formData: { |
| 160 | + files: inp().file() |
| 161 | + .multipleFiles() |
| 162 | + .maxFilesCount(5) |
| 163 | + .maxSize(10 * 1024 * 1024) |
| 164 | + .mimeType(['application/pdf']) |
| 165 | + } |
103 | 166 | }; |
104 | | -type BodyType = z.infer<typeof validation.body>; |
105 | | -// highlight-end |
106 | 167 | ``` |
0 commit comments