Error Handling
There are different ways to handle errors in feTS.
HTTP Status Codes (Recommended)
We recommend using the status codes per HTTP specification instead of throwing errors.
If an error is expected in somewhere, it'd better to catch them with try/catch
blocks and return a
response with a specific status code.
In this case, you can also benefit from the response schemas.
If multiple status codes are defined in the response schema for a specific endpoint, feTS doesn't allow you to return a different status code inside the handler. And it also provides type-checking for the response body of that specific status code.
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,
}
}
} as const /* schemas should always be const */,
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`)
});
Error Throwing
If an unexpected error is thrown, the response will have a 500
status code. We highly recommend to
handle errors inside the handlers with try/catch
blocks, but you can also catch the errors with
the error handling plugin.
Error Handling Plugin
You can handle all errors thrown in the handlers. So you can prevent exposing internal errors to the consumer such as DB errors that might contain some sensitive information.
import { createRouter, useErrorHandling } from 'fets'
const router = createRouter({
plugins: [
useErrorHandling((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')
}
})
HTTPError
You can throw an HTTPError
by determining the status code, message and the headers instead of a
Response
. This might be helpful if you are not able to return a Response
in some places;
import { createRouter, HTTPError } from 'fets'
import { HTTPError, useErrorHandling } from 'fets'
const router = createRouter({
plugins: [useErrorHandling()]
}).route({
path: '/me',
method: 'GET',
schemas: {
responses: {
200: {
type: 'object',
properties: {
name: { type: 'string' }
}
}
}
},
handler: request => {
if (!request.headers.get('Authorization')) {
// You can use `HTTPError` to return a custom error response.
// It accepts a status code, a message, headers and a body.
// If you pass a json object as the body, the response will be a json response.
throw new HTTPError(
401,
'Unauthorized',
{
'WWW-Authenticate': 'Basic'
},
{
message: 'You need to be authenticated to access this resource.'
}
)
}
}
})