Action | Type | Resolved On |
|---|---|---|
| API Validation Utilities | refactoring | - - - |
All API routes repeat similar form validation patterns, leading to code duplication and inconsistent validation approaches across the application.
Each API route repeats validation logic like this example from api/new/goal.ts:
if (!owner) {
return new Response(JSON.stringify({ error: "Owner is required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
if (!year || isNaN(year)) {
return new Response(JSON.stringify({ error: "Valid year is required" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}
if (!month || isNaN(month) || month < 1 || month > 12) {
return new Response(
JSON.stringify({ error: "Valid month is required (1-12)" }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
All API routes in src/pages/api/ repeat similar validation patterns:
api/new/goal.tsapi/new/backlog.tsapi/open/*.tsapi/close/*.tsapi/update/*.tsCreate a validation utility module at src/lib/validation.ts with common validation functions.
// Validation helpers
export function required(value: any, fieldName: string): string | null
export function validNumber(value: any, fieldName: string): string | null
export function validRange(value: number, min: number, max: number, fieldName: string): string | null
// FormData extractors with validation
export function getString(formData: FormData, field: string): { value: string; error: string | null }
export function getNumber(formData: FormData, field: string): { value: number; error: string | null }
export function getNumberInRange(formData: FormData, field: string, min: number, max: number): { value: number; error: string | null }
// Composite validator
export class FormValidator {
private errors: string[] = []
required(value: any, fieldName: string): this
number(value: any, fieldName: string): this
range(value: number, min: number, max: number, fieldName: string): this
isValid(): boolean
getErrors(): string[]
}
// Before
const year = Number(formData.get("year"));
if (!year || isNaN(year)) {
return badRequest("Valid year is required");
}
// After - Simple approach
const yearResult = getNumber(formData, "year");
if (yearResult.error) {
return badRequest(yearResult.error);
}
// After - Fluent API approach
const validator = new FormValidator();
validator
.required(formData.get("owner"), "Owner")
.number(formData.get("year"), "Year")
.number(formData.get("month"), "Month")
.range(Number(formData.get("level")), 0, 3, "Level");
if (!validator.isValid()) {
return badRequest(validator.getErrors().join(", "));
}
src/pages/api/new/goal.tssrc/pages/api/new/backlog.tssrc/pages/api/open/*.tssrc/pages/api/close/*.tssrc/pages/api/update/*.ts