{ "email": 42, "password": "" } to the register endpoint and crashed the server. None of the API inputs are validated. Validate ALL inputs with Zod. Share schemas between frontend and backend.npm install zod
// schemas/auth.js
const { z } = require('zod');
const registerSchema = z.object({
email: z.string()
.email('Invalid email address'),
password: z.string()
.min(8, 'Min 8 characters')
.max(100, 'Max 100 characters')
.regex(/[A-Z]/,
'Must contain uppercase')
.regex(/[0-9]/,
'Must contain a number'),
name: z.string()
.min(2, 'Min 2 characters')
.max(50, 'Max 50 characters')
.trim(),
});
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1,
'Password is required'),
});
module.exports = {
registerSchema, loginSchema
};
function validate(schema) {
return (req, res, next) => {
const result =
schema.safeParse(req.body);
if (!result.success) {
const errors =
result.error.issues.map(
(issue) => ({
field: issue.path.join('.'),
message: issue.message,
})
);
return res.status(400).json({
error: 'Validation failed',
details: errors,
});
}
// Replace body with parsed
// (trimmed, coerced) data
req.body = result.data;
next();
};
}
// Usage on routes
router.post('/auth/register',
validate(registerSchema),
registerHandler
);
router.post('/auth/login',
validate(loginSchema),
loginHandler
);
const paginationSchema = z.object({
page: z.coerce.number()
.int().positive().default(1),
limit: z.coerce.number()
.int().min(1).max(100).default(20),
sort: z.enum([
'createdAt', 'name', 'size'
]).default('createdAt'),
});
function validateQuery(schema) {
return (req, res, next) => {
const result =
schema.safeParse(req.query);
if (!result.success) {
return res.status(400).json({
error: 'Invalid query parameters',
details: result.error.issues,
});
}
req.query = result.data;
next();
};
}
const imageIdSchema = z.object({
id: z.string()
.regex(/^[a-f0-9]{24}$/,
'Invalid image ID'),
});
router.get('/images/:id',
validateParams(imageIdSchema),
getImage
);
// shared/schemas.js — used by BOTH
const { z } = require('zod');
const imageUploadSchema = z.object({
title: z.string()
.min(1).max(100).trim(),
description: z.string()
.max(500).optional(),
tags: z.array(z.string().max(30))
.max(10).optional(),
});
module.exports = { imageUploadSchema };
// --- Backend: Express route ---
const { imageUploadSchema }
= require('../shared/schemas');
router.post('/images/upload',
validate(imageUploadSchema), handler);
// --- Frontend: React form ---
// import { imageUploadSchema }
// from '../shared/schemas';
// const result =
// imageUploadSchema.safeParse(formData);
// if (!result.success)
// setErrors(result.error.issues);
git switch -c feature/PIXELCRAFT-069-zod-validation
git add server/ shared/ src/
git commit -m "Add Zod validation to all API inputs (PIXELCRAFT-069)"
git push origin feature/PIXELCRAFT-069-zod-validation
# PR → Review → Merge → Close ticket ✅
Input validation is the first line of defense.
Most security vulnerabilities start with unvalidated input. SQL injection, XSS, buffer overflows — all exploit the gap between expected and actual input.