Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Error handling

If a handler throws an error, Fastify catches it and answers the client with JSON and an error code. Your job is to throw errors with the right code and, if you like, to set up how they look.

An error with a code

Fastify takes the status code from the error's statusCode property. Without it you get 500:

// src/routes/posts.js
app.get('/posts/:id', async (request) => {
  const post = app.posts.find((item) => item.id === request.params.id);
  if (!post) {
    const error = new Error('Post not found');
    error.statusCode = 404;
    throw error;
  }
  return post;
});

/posts/99 returns {"statusCode":404,"error":"Not Found","message":"Post not found"}.

@fastify/sensible

Creating an error by hand is long. The official @fastify/sensible plugin adds ready-made functions:

npm install @fastify/sensible
// src/app.js
import sensible from '@fastify/sensible';
 
app.register(sensible);

Now app.httpErrors is available in any route:

// src/routes/posts.js
app.get('/posts/:id', async (request) => {
  const post = app.posts.find((item) => item.id === request.params.id);
  if (!post) {
    throw app.httpErrors.notFound('Post not found');
  }
  return post;
});

The reply is the same and the code is shorter. There are others: badRequest() — 400, unauthorized() — 401, forbidden() — 403, conflict() — 409.

Your own error handler

setErrorHandler sets a function every error goes through. Add to src/app.js:

// src/app.js
app.setErrorHandler((error, request, reply) => {
  const statusCode = error.statusCode ?? 500;
 
  if (statusCode >= 500) {
    request.log.error(error);
    return reply.code(500).send({ error: 'Internal server error' });
  }
 
  return reply.code(statusCode).send({ error: error.message });
});

Client errors (4xx codes) are returned with their original message, while server errors are logged and sent to the client without details. Schema validation errors come here too — with code 400 and an error.validation property.

The 404 page

Addresses without a route have a separate handler:

// src/app.js
app.setNotFoundHandler((request, reply) => {
  return reply.code(404).send({ error: `Address ${request.url} not found` });
});

http://localhost:3000/nothing returns {"error":"Address /nothing not found"}.

Where the handlers apply

Both methods follow encapsulation: called inside a plugin, they apply only to its routes. For example, the /admin section can have its own 404 page.

Common mistake

Sending error.message to the client for every error is dangerous: a database or code error message can reveal table names, file paths and other details. For code 500 answer with a general phrase and write the details to the log, as in the example above.

Thanks for reading!

Donate to the site — we work hard for you.