[{"data":1,"prerenderedAt":309},["ShallowReactive",2],{"blog-how-to/validate-user-input":3},{"id":4,"title":5,"body":6,"category":289,"date":290,"dateModified":291,"description":292,"draft":293,"extension":294,"faq":295,"featured":293,"headerVariant":296,"image":295,"keywords":295,"meta":297,"navigation":298,"ogDescription":299,"ogTitle":295,"path":300,"readTime":295,"schemaOrg":301,"schemaType":302,"seo":303,"sitemap":304,"stem":305,"tags":306,"twitterCard":307,"__hash__":308},"blog/blog/how-to/validate-user-input.md","How to Validate User Input Securely",{"type":7,"value":8,"toc":268},"minimark",[9,13,17,21,30,39,44,64,77,90,94,98,104,108,114,118,124,128,134,138,144,148,154,158,164,168,174,178,225,229,243,249],[10,11],"category-badge",{"category":12},"How-To Guide",[14,15,5],"h1",{"id":16},"how-to-validate-user-input-securely",[18,19,20],"p",{},"Client-side validation is for UX. Server-side validation is for security.",[22,23,24,27],"tldr",{},[18,25,26],{},"TL;DR",[18,28,29],{},"Always validate input on the server, even if you have client-side validation. Use Zod to define schemas. Check types, lengths, formats, and allowed values. Reject invalid input early with clear error messages.",[31,32,33,36],"warning-box",{},[18,34,35],{},"Why Client-Side Validation Isn't Enough",[18,37,38],{},"Users can bypass client-side validation by modifying JavaScript, using browser DevTools, or calling your API directly. Never trust client-side validation for security.",[40,41,43],"h2",{"id":42},"step-by-step-with-zod","Step-by-Step with Zod",[45,46,48,53],"step",{"number":47},"1",[49,50,52],"h3",{"id":51},"install-zod","Install Zod",[54,55,60],"pre",{"className":56,"code":58,"language":59},[57],"language-text","npm install zod\n","text",[61,62,58],"code",{"__ignoreMap":63},"",[45,65,67,71],{"number":66},"2",[49,68,70],{"id":69},"define-your-schema","Define your schema",[54,72,75],{"className":73,"code":74,"language":59},[57],"import { z } from 'zod';\n\nconst CreateUserSchema = z.object({\n  email: z.string()\n    .email('Invalid email format')\n    .max(254, 'Email too long'),\n\n  password: z.string()\n    .min(8, 'Password must be at least 8 characters')\n    .max(100, 'Password too long'),\n\n  name: z.string()\n    .min(1, 'Name is required')\n    .max(100, 'Name too long')\n    .regex(/^[a-zA-Z\\s]+$/, 'Name can only contain letters'),\n\n  age: z.number()\n    .int('Age must be a whole number')\n    .min(13, 'Must be at least 13')\n    .max(120, 'Invalid age')\n    .optional(),\n});\n",[61,76,74],{"__ignoreMap":63},[45,78,80,84],{"number":79},"3",[49,81,83],{"id":82},"validate-in-your-api-route","Validate in your API route",[54,85,88],{"className":86,"code":87,"language":59},[57],"export async function POST(request: Request) {\n  const body = await request.json();\n\n  // Validate input\n  const result = CreateUserSchema.safeParse(body);\n\n  if (!result.success) {\n    return Response.json(\n      { error: 'Validation failed', details: result.error.flatten() },\n      { status: 400 }\n    );\n  }\n\n  // result.data is now typed and validated\n  const { email, password, name, age } = result.data;\n\n  // Safe to use these values\n  const user = await createUser({ email, password, name, age });\n\n  return Response.json(user);\n}\n",[61,89,87],{"__ignoreMap":63},[40,91,93],{"id":92},"common-validation-patterns","Common Validation Patterns",[49,95,97],{"id":96},"email","Email",[54,99,102],{"className":100,"code":101,"language":59},[57],"const email = z.string()\n  .email('Invalid email')\n  .max(254)\n  .toLowerCase(); // Normalize to lowercase\n",[61,103,101],{"__ignoreMap":63},[49,105,107],{"id":106},"password","Password",[54,109,112],{"className":110,"code":111,"language":59},[57],"const password = z.string()\n  .min(8, 'Password too short')\n  .max(100, 'Password too long')\n  .regex(/[A-Z]/, 'Must contain uppercase')\n  .regex(/[a-z]/, 'Must contain lowercase')\n  .regex(/[0-9]/, 'Must contain number');\n",[61,113,111],{"__ignoreMap":63},[49,115,117],{"id":116},"url","URL",[54,119,122],{"className":120,"code":121,"language":59},[57],"const url = z.string()\n  .url('Invalid URL')\n  .startsWith('https://', 'Must use HTTPS');\n",[61,123,121],{"__ignoreMap":63},[49,125,127],{"id":126},"uuid","UUID",[54,129,132],{"className":130,"code":131,"language":59},[57],"const id = z.string().uuid('Invalid ID format');\n",[61,133,131],{"__ignoreMap":63},[49,135,137],{"id":136},"enum-values","Enum Values",[54,139,142],{"className":140,"code":141,"language":59},[57],"const status = z.enum(['draft', 'published', 'archived']);\n",[61,143,141],{"__ignoreMap":63},[49,145,147],{"id":146},"arrays","Arrays",[54,149,152],{"className":150,"code":151,"language":59},[57],"const tags = z.array(z.string())\n  .min(1, 'At least one tag required')\n  .max(10, 'Maximum 10 tags');\n",[61,153,151],{"__ignoreMap":63},[49,155,157],{"id":156},"dates","Dates",[54,159,162],{"className":160,"code":161,"language":59},[57],"const birthDate = z.string()\n  .datetime()\n  .refine((date) => new Date(date) \u003C new Date(), {\n    message: 'Birth date must be in the past',\n  });\n",[61,163,161],{"__ignoreMap":63},[40,165,167],{"id":166},"form-with-react-hook-form","Form with React Hook Form",[54,169,172],{"className":170,"code":171,"language":59},[57],"import { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\n\nconst schema = z.object({\n  email: z.string().email(),\n  password: z.string().min(8),\n});\n\ntype FormData = z.infer\u003Ctypeof schema>;\n\nfunction SignupForm() {\n  const { register, handleSubmit, formState: { errors } } = useForm\u003CFormData>({\n    resolver: zodResolver(schema),\n  });\n\n  const onSubmit = async (data: FormData) => {\n    // Client-side validated, but STILL validate server-side!\n    const response = await fetch('/api/signup', {\n      method: 'POST',\n      body: JSON.stringify(data),\n    });\n  };\n\n  return (\n    \u003Cform onSubmit={handleSubmit(onSubmit)}>\n      \u003Cinput {...register('email')} />\n      {errors.email && \u003Cspan>{errors.email.message}\u003C/span>}\n\n      \u003Cinput type=\"password\" {...register('password')} />\n      {errors.password && \u003Cspan>{errors.password.message}\u003C/span>}\n\n      \u003Cbutton type=\"submit\">Sign Up\u003C/button>\n    \u003C/form>\n  );\n}\n",[61,173,171],{"__ignoreMap":63},[40,175,177],{"id":176},"what-to-validate","What to Validate",[179,180,181,189,195,201,207,213,219],"ul",{},[182,183,184,188],"li",{},[185,186,187],"strong",{},"Type:"," Is it a string, number, boolean, array?",[182,190,191,194],{},[185,192,193],{},"Required:"," Is this field optional or mandatory?",[182,196,197,200],{},[185,198,199],{},"Length:"," Min/max characters for strings, items for arrays",[182,202,203,206],{},[185,204,205],{},"Format:"," Email, URL, UUID, date format",[182,208,209,212],{},[185,210,211],{},"Range:"," Min/max values for numbers",[182,214,215,218],{},[185,216,217],{},"Allowed values:"," Enums for status fields",[182,220,221,224],{},[185,222,223],{},"Custom rules:"," Business logic validation",[40,226,228],{"id":227},"error-handling-best-practices","Error Handling Best Practices",[179,230,231,234,237,240],{},[182,232,233],{},"Return specific error messages for each field",[182,235,236],{},"Don't reveal internal details in errors",[182,238,239],{},"Use 400 status code for validation errors",[182,241,242],{},"Return errors in a consistent format",[54,244,247],{"className":245,"code":246,"language":59},[57],"// Good error response\n{\n  \"error\": \"Validation failed\",\n  \"details\": {\n    \"fieldErrors\": {\n      \"email\": [\"Invalid email format\"],\n      \"password\": [\"Password too short\"]\n    }\n  }\n}\n",[61,248,246],{"__ignoreMap":63},[250,251,252,258,263],"related-articles",{},[253,254],"related-card",{"description":255,"href":256,"title":257},"Step-by-step guide to implementing secure database backups. Automated backups, encryption, retention policies, and disas","/blog/how-to/database-backups","How to Set Up Secure Database Backups",[253,259],{"description":260,"href":261,"title":262},"Step-by-step guide to database encryption. Implement encryption at rest, in transit, and application-level encryption fo","/blog/how-to/database-encryption","How to Encrypt Database Data",[253,264],{"description":265,"href":266,"title":267},"Complete guide to setting up .env files for local development. Learn the dotenv package, file naming conventions, and ho","/blog/how-to/dotenv-setup","How to Set Up .env Files - Complete Guide",{"title":63,"searchDepth":269,"depth":269,"links":270},2,[271,277,286,287,288],{"id":42,"depth":269,"text":43,"children":272},[273,275,276],{"id":51,"depth":274,"text":52},3,{"id":69,"depth":274,"text":70},{"id":82,"depth":274,"text":83},{"id":92,"depth":269,"text":93,"children":278},[279,280,281,282,283,284,285],{"id":96,"depth":274,"text":97},{"id":106,"depth":274,"text":107},{"id":116,"depth":274,"text":117},{"id":126,"depth":274,"text":127},{"id":136,"depth":274,"text":137},{"id":146,"depth":274,"text":147},{"id":156,"depth":274,"text":157},{"id":166,"depth":269,"text":167},{"id":176,"depth":269,"text":177},{"id":227,"depth":269,"text":228},"how-to","2026-01-27","2026-02-09","Step-by-step guide to validating user input. Zod schemas, server-side validation, common validation patterns, and why client-side validation isn't enough.",false,"md",null,"yellow",{},true,"Step-by-step guide to validating user input with Zod and server-side checks.","/blog/how-to/validate-user-input","[object Object]","HowTo",{"title":5,"description":292},{"loc":300},"blog/how-to/validate-user-input",[],"summary_large_image","_CDOkBs0QAytuc9XP0rArERoO2_ErWsbGDWMEp11xAU",1775843927165]