Menu
CoddyTech

One Error Shape

Part of the REST APIs section of Coddy's Node.js journey. Lesson 12 of 25.

Your API answers errors in two shapes now: a string for a 404 and zod's object for a 400. A client has to handle each one. Pick one shape for every error:

{ "error": { "code": "not_found", "message": "Invoice not found" } }

{ "error": { "code": "invalid_body", "message": "Too small: ...",
             "fields": { "amount": ["Too small: expected number to be >0"] } } }

code is for programs: it never changes, so a client can compare it. message is for people, and its wording may change. fields tells a form which input to mark. Write the shape in one function and call it from every route, so no route can drift:

sendError(res, 404, 'not_found', 'Invoice not found');
challenge icon

Challenge

Easy

Your API sends errors in two shapes now: a plain string for a 404, and zod's object for a 400. Give every error the same shape, so a client can read them all the same way. From now on every error looks like this, with fields added only when there is something to list:

{ "error": { "code": "not_found", "message": "Task not found" } }
  1. In errors.js, finish sendError(res, status, code, message, fields). It responds with the given status and the shape above. Add fields to the inner object only when that argument was given. sendInvalid, below it, is already written and calls your function.
  2. In routes/tasks.js, import both helpers:
    • Replace the four 404 responses (GET, PATCH, PUT and DELETE for one task) with sendError, using code not_found and message Task not found.
    • Replace the three 400 responses for a bad body (POST, PATCH and PUT) with sendInvalid(res, result.error).

    A bad body then looks like this (zod writes the message):

    {
      "error": {
        "code": "invalid_body",
        "message": "Invalid input: expected string, received number",
        "fields": { "title": ["Invalid input: expected string, received number"] }
      }
    }
  3. In server.js, import sendError and use it in the error handler at the bottom:
    • A body that is not valid JSON gets status 400, code invalid_json and message The body is not valid JSON.
    • Anything else gets status 500, code server_error and message Something went wrong.

Try it: DELETE /api/tasks/99 returns 404 with { "error": { "code": "not_found", "message": "Task not found" } }, and no fields. POST { "title": 123 } to /api/tasks: you get 400 with the invalid_body error above. The request bar refuses to send broken JSON, so the checks test the invalid_json case for you.

Try it yourself

import { z } from 'zod';

// Sends every error in one shape:
// { "error": { "code": "not_found", "message": "Task not found" } }
// with "fields" added only when it is given.
export const sendError = (res, status, code, message, fields) => {
    // Send the answer here
};

// A zod error as a 400: its first problem as the message, each field's
// problems in fields.
export const sendInvalid = (res, zodError) =>
    sendError(res, 400, 'invalid_body', zodError.issues[0].message, z.flattenError(zodError).fieldErrors);
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in REST APIs

Practice on your own: Express playground