Deno API Server — How to implement Error Handler by oak

Taka
3 min readApr 30, 2021
error-handler

Today’s topic is how to implement a generic error Handler in Deno API server.

It should be executed in middleware in order to catch all errors in one spot. An error Hander usually looks complicated in initial development.

In this article, sharing the simple error handler code. However, it does not exactly follow the original document implementation here. The reason is that I am still not sure the defact standard to use HttpError object by oak properly. The original one returns the error response as content-type: text/plain but we sometimes would like to respond errors as application/json. This is the future try.

Version Infomation

deno 1.8.2 (release, x86_64-apple-darwin)
v8 9.0.257.3
typescript 4.2.2

oak: https://deno.land/x/oak@v6.5.0/mod.ts

import modules: Context, isHttpError, Status,STATUS_TEXT

The goal in this article is when an error is thrown from wherever components, the error handler in a middleware catches it then responds the error object to the requester.

The error should be thrown like this,

throw new CustomError(Status.BadRequest, [`param: ${req.param1}`]);

CustomError extends JavaScript Error object. This is an example.

export class CustomError extends Error {
code: number;
name: string;
contents: string[];

constructor(statusCode: number, contents: string[]) {
super();
this.code = statusCode;
this.name = STATUS_TEXT.get(statusCode);
this.contents = contents;
}
}

The first argument is HTTP status code, and the second one is the contents for logger in my project.

About Status and STATUS_TEXT, refer to deno standard documentation.

Note that, as I mentioned, the oak original Error class is HttpErrorwhich also extends Error. I would like to replace CustomError with a customized HttpErroror original one in the future so CustomError is implemented as it is refactored easily in the future.

Let’s implement the error handler. In this case, it is supposed to be called last among some other middleware.

import { Context, isHttpError, Status } from "../mod.ts";
export const errorHandler = async (ctx: Context, next: () => Promise<void>) => {
try {
await next();
} catch (err) {
if (!isHttpError(err)) {
ctx.response.status = err.code;
ctx.response.body = createError(err);
} else {
ctx.throw(Status.InternalServerError, "Unexpected Error");
}
}
};

It is only one if statement in catch block. There is no swtich statement like judging whether the error is 400 or 404 or others. Just state the error code when the error object is created as I showed before.

isHttpError function judges whether the err object is created byHttpError instance defined in oak.

When an error is created by CustomError, the error object is returned according to the error type by Context.

ctx.response.status = err.code;
ctx.response.body = createError(err);

As a side note, createError will be

const createError = (err: CustomError): TResponse<TErrorFormat> => {
return {
data: {
code: err.code,
name: err.name,
},
};
};

type TErrorFormat = {
code: number;
name: string;
};

Context interface has Response. This returns the response like the below.

HTTP/1.1 400 Bad Request
content-length: 42
content-type: application/json; charset=utf-8
{
"data": {
"code": 400,
"name": "Bad Request"
}
}

Content-Type is automatically detected by oak, the body should be application/json in this case.

Whereas,

ctx.throw(Status.InternalServerError, "Unexpected Error");

throws HttpError defined originally in oak. The first argument is status code and the second one is error content. More detail, here.

HTTP/1.1 500 Internal Server Error
content-length: 21
content-type: text/plain; charset=utf-8
Internal Server Error

Note that, the error response as content-type: text/plain not application/json.

At the End

Of course, it is not the main topic to call middleware itself so just share the middleware implementation briefly in the bellow.

app.use(errorHandler)

If you would like to know the implementation of middleware, refer to here.

Thank you for reading.

--

--