Node.js API Best Practices

Chapter 2 · Practice 1 of 4

Validate the body, params, query and headers of every request

From Validation and error handling

Why it matters

Everything in a request is untrusted: body, path params, query string, headers and cookies. Validate each one against a schema before your code uses it.

A good schema does three things:

  • Rejects unknown fields.
  • Enforces types, coercing query strings, which always arrive as text.
  • Sets limits on string lengths, array sizes and number ranges.

Where the validation lives depends on the framework:

  • Express: parse with a library such as Zod at the top of each handler or in a small middleware.
  • Fastify: JSON Schema validation is built into its route options, which also speeds up serialization.

On failure, return a 400 with field-level messages so clients can fix their request.

Do

  • Use safeParse and turn failures into a 400 with a list of field errors
  • Coerce and validate query strings, which always arrive as text

Avoid

  • Passing req.body straight into a database query or update

Example

TypeScript
const createUser = z.object({
  email: z.email().max(254),
  name: z.string().trim().min(1).max(80),
});

app.post('/users', async (req, res) => {
  const result = createUser.safeParse(req.body);
  if (!result.success) return res.status(400).json({ errors: result.error.issues });
  res.status(201).json(await users.create(result.data));
});