ServerError Handling

Error Handling in feTS

In feTS, there are several methods available to handle errors effectively.

It is suggested to use HTTP status codes for error handling in lieu of throwing errors. When an error is anticipated, it is beneficial to catch them using try/catch blocks and return a response with a specific status code.

This approach allows you to also take advantage of response schemas. If multiple status codes are defined in the response schema for a specific endpoint, feTS enforces that only those status codes can be returned within the handler. Additionally, it provides type-checking for the response body associated with each status code.

Here is a sample use case:

import { App } from 'uWebSockets.js';
import { createRouter, FromSchema } from 'fets';
 
const users = [
    { id: "1", name: 'John Doe' },
    { id: "2", name: 'Jane Doe' },
];
 
const router = createRouter().route({
  method: 'GET',
  path: '/user/:id',
  schemas: {
    request: {
      params: {
        type: 'object',
        properties: {
          id: { type: 'string' }
        },
        required: ['id']
        additionalProperties: false
      }
    },
    responses: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          name: { type: 'string' }
        },
        required: ['id', 'name'],
        additionalProperties: false,
      },
      404: {
        type: 'object',
        properties: {
          message: { type: 'string' }
        },
        required: ['message'],
        additionalProperties: false,
      }
    }
  },
  handler: ({ params }) => {
    const user = users.find(user => user.id === params.id);
    if (!user) {
      return Response.json({ message: 'User not found' }, { status: 404 });
    }
    if (user.name === 'Jane Doe') {
      // @ts-expect-error - 401 is not defined in the response schema
      return Response.json({ message: 'Jane is not allowed'}, { status: 401 });
    }
    if (user.name === 'John Doe') {
        // @ts-expect-error - 200 response schema doesn't have a `surname` property
        return Response.json({ id: user.id, name: user.name, surname: 'Doe' });
    }
    return Response.json(user);
  }
});
 
App().any('/*', router).listen(3000, () => {
    console.log(`Swagger UI is available at http://localhost:3000/docs`)
});

Throwing Errors

When an unexpected error is thrown, the response will have a 500 status code. Although it is highly recommended to handle errors inside the handlers using try/catch blocks, you can also use the error handling plugin to catch errors.

Error Handling

feTS allows you to handle all errors thrown in the handlers. This way, you can prevent exposing internal errors, such as database errors that might contain sensitive information, to the consumer.

Here’s an example:

import { createRouter } from 'fets'
 
const router = createRouter({
  onError(error, request, context) {
    if (error.name === 'MyError') {
      return Response.json(
        {
          message: error.message
        },
        {
          status: 400
        }
      )
    }
    console.error(`Unexpected error in ${request.method} ${request.url}:\n${error.stack}`)
    return Response.json(
      {
        message: 'Something went wrong'
      },
      {
        status: 500
      }
    )
  }
}).route({
  path: '/users',
  method: 'GET',
  handler: async () => {
    throw new Error('Something went wrong')
  }
})

Utilizing HTTPError

In certain scenarios, you might find it helpful to throw an HTTPError instead of returning a Response. This can be particularly useful when you are unable to return a Response in some parts of your code.

The HTTPError can be thrown by specifying the status code, message, headers, and body. If you pass a JSON object as the body, the response will be formatted as a JSON response.

Here’s an example of how to use HTTPError:

import { createRouter, HTTPError } from 'fets'
 
import { HTTPError } from 'fets'
 
const router = createRouter().route({
  path: '/me',
  method: 'GET',
  schemas: {
    responses: {
      200: {
        type: 'object',
        properties: {
          name: { type: 'string' }
        }
      }
    }
  } ,
  handler: request => {
    if (!request.headers.get('Authorization')) {
      // The `HTTPError` can be used to return a custom error response.
      // The function accepts a status code, a message, headers, and a body.
      throw new HTTPError(
        401,
        'Unauthorized',
        {
          'WWW-Authenticate': 'Basic'
        },
        {
          message: 'You need to be authenticated to access this resource.'
        }
      )
    }
  }
})