Error Handling
COMPATIBILITY NOTE
This guide requires Node.js >= 8 and will target express. We currently recommend using Yarn, npm should work but was not tested. This guide assumes you followed the getting started guide or have a similar setup.
As you may have noticed after following all the steps from the getting started guide, our server does not allow for invalid parameters, but the response isn't very ideal yet.
For the Client, it looks something like this:
Setting up error handling
Handling Validation Errors
Let's first make sure that, whenever the Client triggers a Validation Error, instead of printing the stack trace, instead we show a properly formatted json response.
At the end of our app.ts, after the call to RegisterRoutes(app), we'll add a global express error handler:
import express,{
ResponseasExResponse,
RequestasExRequest,
NextFunction,
}from"express";
import{ValidateError}from"tsoa";
// ...
app.use(functionerrorHandler(
err:unknown,
req:ExRequest,
res:ExResponse,
next:NextFunction
):ExResponse|void{
if (errinstanceofValidateError) {
console.warn(`Caught Validation Error for ${req.path}:`,err.fields);
returnres.status(422).json({
message:"Validation Failed",
details:err?.fields,
});
}
if (errinstanceofError) {
returnres.status(500).json({
message:"Internal Server Error",
});
}
next();
});Now, the same request will respond like this:
Additionally, our console will show:
Handling missing routes
In order to handle missing urls more gracefully, we can add a "catch-all" route handler:
// app.ts
import express,{
ResponseasExResponse,
RequestasExRequest,
NextFunction,
}from"express";
// ...
RegisterRoutes(app)
app.use(functionnotFoundHandler(_req,res:ExResponse){
res.status(404).send({
message:"Not Found",
});
});
app.use(functionerrorHandler(
// ...Specifying error response types for OpenAPI
If you check out the Documentation endpoint, you'll notice that we don't have any documentation for our Errors yet. Since TypeScript does not check throwing Errors, tsoa can't infer the type of response we're sending out in these cases.
WARNING
Please note that @Response has to be the tsoa import and can not currently be renamed (i.e. import {Response as TsoaResponse} from "tsoa")
However, we have a way for you to manually specify these returns:
import{Body,Controller,Post,Route,Response,SuccessResponse}from"tsoa";
import{User}from"./user";
import{UsersService,UserCreationParams}from"./usersService";
interfaceValidateErrorJSON{
message:"Validation failed";
details:{ [name:string]:unknown};
}
@Route("users")
exportclassUsersControllerextendsController{
// more code here
@Response<ValidateErrorJSON>(422,"Validation Failed")
@SuccessResponse("201","Created") // Custom success response
@Post()
publicasynccreateUser(
@Body() requestBody:UserCreationParams
):Promise<void>{
this.setStatus(201);// set return status 201
newUsersService().create(requestBody);
return;
}
}This should make our docs show something like this:
👁 SwaggerUI showing our 422 Response
TIP
OpenAPI allows matching status codes such as '2xx' or matching all codes using 'default'. tsoa will support this:
@Response<ErrorResponse>('default','Unexpected error')
@Get('Response')
public async getResponse(): Promise<TestModel>{
return new ModelService().getModel();
}Typechecked alternate responses
WARNING
This section applies to tsoa >=3.1
In recent versions of tsoa, we have the option to inject a framework-agnostic responder function into our function that we can call to formulate a response that does not comply with the return type of our controller method/status code and headers (which is used for the success response). This is especially useful to reply with an error response without the risk of type mismatches associated with throwing errors. In order to inject one/more responders, we can use the @Res() decorator:
import{Route,Controller,Get,Query,Res,TsoaResponse}from'tsoa'
@Route('/greeting')
exportclassGreetingsControllerextendsController{
/**
* @paramnotFoundResponse The responder function for a not found response
*/
@Get('/')
publicasyncgreet(@Query() name?:string,@Res() notFoundResponse:TsoaResponse<404,{reason:string}>):Promise<string>{
if (!name) {
returnnotFoundResponse(404,{ reason:"We don't know you yet. Please provide a name"});
}
return`Hello, ${name}`;
}