From fef789d7a22342fc77ec08535cbf4793f53b6119 Mon Sep 17 00:00:00 2001 From: 18166714330cl-maker <18166714330cl@gmail.com> Date: Mon, 1 Jun 2026 13:04:03 +0800 Subject: [PATCH 1/4] Handle validation errors with 400 responses Map Zod validation failures to a client error response instead of letting malformed payloads look like server faults. --- apps/api/src/middleware/errorHandler.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/api/src/middleware/errorHandler.js b/apps/api/src/middleware/errorHandler.js index 4beefc164f..885216fe80 100644 --- a/apps/api/src/middleware/errorHandler.js +++ b/apps/api/src/middleware/errorHandler.js @@ -1,9 +1,22 @@ +import { ZodError } from "zod"; + export function errorHandler(err, req, res, next) { console.error("Unhandled API error:", err); if (res.headersSent) { return next(err); } + if (err instanceof ZodError) { + return res.status(400).json({ + success: false, + message: "Validation error", + issues: err.issues.map((issue) => ({ + path: issue.path.join("."), + message: issue.message + })) + }); + } + return res.status(500).json({ success: false, message: "Unexpected server error" From 650517ef30dae55b0d63ef6d4c931d8c686b0cb5 Mon Sep 17 00:00:00 2001 From: 18166714330cl-maker <18166714330cl@gmail.com> Date: Mon, 1 Jun 2026 13:05:41 +0800 Subject: [PATCH 2/4] Catch async job route errors --- apps/api/src/routes/jobRoutes.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/api/src/routes/jobRoutes.js b/apps/api/src/routes/jobRoutes.js index f8ca6d3fc9..179952939f 100644 --- a/apps/api/src/routes/jobRoutes.js +++ b/apps/api/src/routes/jobRoutes.js @@ -1,7 +1,8 @@ import { Router } from "express"; import { getJobs, postJob } from "../controllers/jobController.js"; +import { asyncHandler } from "../utils/asyncHandler.js"; export const jobRoutes = Router(); -jobRoutes.get("/", getJobs); -jobRoutes.post("/", postJob); +jobRoutes.get("/", asyncHandler(getJobs)); +jobRoutes.post("/", asyncHandler(postJob)); From 614fc4d86ad972a3f5b891fcf107651a0d7e627d Mon Sep 17 00:00:00 2001 From: 18166714330cl-maker <18166714330cl@gmail.com> Date: Mon, 1 Jun 2026 13:05:53 +0800 Subject: [PATCH 3/4] Add async route error wrapper --- apps/api/src/utils/asyncHandler.js | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 apps/api/src/utils/asyncHandler.js diff --git a/apps/api/src/utils/asyncHandler.js b/apps/api/src/utils/asyncHandler.js new file mode 100644 index 0000000000..8d22255b84 --- /dev/null +++ b/apps/api/src/utils/asyncHandler.js @@ -0,0 +1,5 @@ +export function asyncHandler(handler) { + return (req, res, next) => { + Promise.resolve(handler(req, res, next)).catch(next); + }; +} From 6665cbdf46972107bbf16489b4bb6f85a87a5417 Mon Sep 17 00:00:00 2001 From: 18166714330cl-maker <18166714330cl@gmail.com> Date: Mon, 1 Jun 2026 13:06:05 +0800 Subject: [PATCH 4/4] Test validation error handling --- .../src/tests/validationErrorHandler.test.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 apps/api/src/tests/validationErrorHandler.test.js diff --git a/apps/api/src/tests/validationErrorHandler.test.js b/apps/api/src/tests/validationErrorHandler.test.js new file mode 100644 index 0000000000..01a0e160b8 --- /dev/null +++ b/apps/api/src/tests/validationErrorHandler.test.js @@ -0,0 +1,46 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { createApp } from "../app.js"; + +async function withServer(run) { + const app = createApp(); + const server = app.listen(0); + + await new Promise((resolve, reject) => { + server.once("listening", resolve); + server.once("error", reject); + }); + + try { + const { port } = server.address(); + await run(`http://127.0.0.1:${port}`); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +} + +test("validation errors return 400 instead of 500", async () => { + await withServer(async (baseUrl) => { + const response = await fetch(`${baseUrl}/api/jobs`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + title: "Bad", + description: "short", + budgetMin: -1, + budgetMax: 100, + categoryId: "", + skills: [""] + }) + }); + + const payload = await response.json(); + + assert.equal(response.status, 400); + assert.equal(payload.success, false); + assert.equal(payload.message, "Validation error"); + assert.ok(payload.issues.length > 0); + }); +});