Build an Express API endpoint with Zod request validation and typed responses.
1 import { z } from 'zod'; 2 3 // Schema = runtime validation + static type (single source of truth) 4 const CreatePostSchema = z.object({ 5 title: z.string().min(3).max(200), 6 body: z.string().min(10).max(10000), 7 tags: z.array(z.string().min(1).max(50)).max(10).default([]), 8 published: z.boolean().default(false), 9 authorId: z.string().uuid(), 10 }); 11 12 const UpdatePostSchema = CreatePostSchema.partial().omit({ authorId: true }); 13 14 // Types derived from schemas — never write them twice 15 type CreatePostDto = z.infer<typeof CreatePostSchema>; 16 type UpdatePostDto = z.infer<typeof UpdatePostSchema>; 17 18 // Simulated repository 19 const posts: Array<CreatePostDto & { id: string; createdAt: Date }> = []; 20 21 // Validate and create 22 function createPost(raw: unknown): { success: true; post: typeof posts[0] } | { success: false; errors: Record<string, string> } { 23 const result = CreatePostSchema.safeParse(raw); 24 if (!result.success) { 25 const errors: Record<string, string> = {}; 26 result.error.issues.forEach(issue => { 27 const path = issue.path.join('.'); 28 errors[path] = issue.message; 29 }); 30 return { success: false, errors }; 31 } 32 const post = { ...result.data, id: crypto.randomUUID(), createdAt: new Date() }; 33 posts.push(post); 34 return { success: true, post }; 35 } 36 37 // Test 38 const r1 = createPost({ title: 'Hi', body: 'Short', authorId: 'not-uuid' }); 39 console.log(r1); // { success: false, errors: { title: '...', body: '...', authorId: '...' } } 40 41 const r2 = createPost({ 42 title: 'TypeScript Deep Dive', 43 body: 'TypeScript is a superset of JavaScript that adds static typing...', 44 authorId: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', 45 }); 46 console.log(r2); // { success: true, post: { id: '...', title: '...', ... } }
POST /posts validates body, returns 400 with field-level errors, or 201 with created post
Sign in to share your feedback and join the discussion.