Current State: Manual Middleware Validation
In the previous lesson, we extracted validation into three reusable middleware functions: validateUserId, validateRequiredUserData, and validatePartialUserData. This created three distinct patterns:
- Parameter validation: Checking URL parameters like user IDs.
- Required field validation: All fields must be present (POST/PUT).
- Partial field validation: At least one field must be present (PATCH).
However, these manual middleware functions still have limitations:
- Poor error messages: Generic errors don’t help users understand specific problems.
- Limited validation: Basic existence checks don’t validate format, length, or content.
- Manual maintenance: Adding new validation rules requires writing more
ifstatements. - No consistency: Different developers might write validation differently.
Real-World Validation Requirements
Professional APIs need comprehensive validation. For user registration, we might need to validate:
- Username: 2-50 characters, alphanumeric only.
- Email: Must be valid email format.
- Password: 8+ characters with uppercase, lowercase, number, and special characters.
{
username: "john123",
email: "john@test.com",
password: "SecurePass123!",
}
Manual validation would require many lines of if statements and regular expressions - especially for password complexity requirements.
The Schema Validation Solution
Schema validation libraries define data requirements in a declarative way. Instead of writing multiple if statements, we define the exact shape and rules we expect:
const registerSchema = z.object({
username: z.string().min(2).max(50),
email: z.email(),
password: z
.string()
.regex(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[^A-Za-z0-9]).{8,}$/);
});
Benefits of schema validation
- Declarative: Describe what you want, not how to check it.
- Comprehensive: Built-in validators for emails, URLs, numbers, etc.
- Type safety: Automatic TypeScript integration.
- Reusable: One schema can be used across multiple endpoints.