Day 4 of 5
⏱ ~60 minutes
Express.js in 5 Days — Day 4

Error Handling and Validation

Centralized error handling, input validation with Zod, custom error classes, and proper HTTP status codes.

Centralized Error Handling

middleware/errorHandler.js
// Error handler middleware: 4 params = Express treats it as error handler
module.exports = (err, req, res, next) => {
  console.error(err.stack);

  // Mongoose validation error
  if (err.name === 'ValidationError') {
    const errors = Object.values(err.errors).map(e => e.message);
    return res.status(400).json({ error: 'Validation failed', details: errors });
  }

  // MongoDB duplicate key
  if (err.code === 11000) {
    const field = Object.keys(err.keyValue)[0];
    return res.status(400).json({ error: `${field} already exists` });
  }

  // JWT errors
  if (err.name === 'JsonWebTokenError') return res.status(401).json({ error: 'Invalid token' });

  // Default
  res.status(err.status || 500).json({ error: err.message || 'Internal server error' });
};
server.js — register error handler
// Error handler must be last middleware
app.use(errorHandler);

// In async routes, pass errors to next()
router.get('/posts/:id', async (req, res, next) => {
  try {
    const post = await Post.findById(req.params.id);
    if (!post) return res.status(404).json({ error: 'Not found' });
    res.json(post);
  } catch (err) {
    next(err);  // sends to errorHandler
  }
});
Zod Validation
npm install zod

const { z } = require('zod');

const createUserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  password: z.string().min(8),
});

// Middleware
function validate(schema) {
  return (req, res, next) => {
    const result = schema.safeParse(req.body);
    if (!result.success) {
      return res.status(400).json({ errors: result.error.flatten() });
    }
    req.validatedBody = result.data;
    next();
  };
}

router.post('/users', validate(createUserSchema), async (req, res) => {
  // req.validatedBody is clean and type-safe
});
📝 Day 4 Exercise
Add Error Handling to Your API
  1. A
  2. d
  3. d
  4. t
  5. h
  6. e
  7. c
  8. e
  9. n
  10. t
  11. r
  12. a
  13. l
  14. i
  15. z
  16. e
  17. d
  18. e
  19. r
  20. r
  21. o
  22. r
  23. h
  24. a
  25. n
  26. d
  27. l
  28. e
  29. r
  30. t
  31. o
  32. y
  33. o
  34. u
  35. r
  36. s
  37. e
  38. r
  39. v
  40. e
  41. r
  42. .
  43. C
  44. o
  45. n
  46. v
  47. e
  48. r
  49. t
  50. a
  51. l
  52. l
  53. t
  54. r
  55. y
  56. /
  57. c
  58. a
  59. t
  60. c
  61. h
  62. b
  63. l
  64. o
  65. c
  66. k
  67. s
  68. t
  69. o
  70. u
  71. s
  72. e
  73. n
  74. e
  75. x
  76. t
  77. (
  78. e
  79. r
  80. r
  81. )
  82. .
  83. A
  84. d
  85. d
  86. Z
  87. o
  88. d
  89. v
  90. a
  91. l
  92. i
  93. d
  94. a
  95. t
  96. i
  97. o
  98. n
  99. t
  100. o
  101. y
  102. o
  103. u
  104. r
  105. P
  106. O
  107. S
  108. T
  109. a
  110. n
  111. d
  112. P
  113. U
  114. T
  115. r
  116. o
  117. u
  118. t
  119. e
  120. s
  121. .
  122. T
  123. e
  124. s
  125. t
  126. t
  127. h
  128. a
  129. t
  130. i
  131. n
  132. v
  133. a
  134. l
  135. i
  136. d
  137. i
  138. n
  139. p
  140. u
  141. t
  142. r
  143. e
  144. t
  145. u
  146. r
  147. n
  148. s
  149. p
  150. r
  151. o
  152. p
  153. e
  154. r
  155. 4
  156. 0
  157. 0
  158. e
  159. r
  160. r
  161. o
  162. r
  163. s
  164. .

Day 4 Summary

  • The 4-param middleware (err, req, res, next) is Express's error handler. Register it last.
  • next(err) in async routes sends errors to the error handler — don't swallow them.
  • Zod validates and parses input in one step. safeParse never throws — check result.success.
  • Consistent error response format matters for frontend developers consuming your API.
Finished this lesson?