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 HttpError
which also extends Error
. I would like to replace CustomError
with a customized HttpError
or 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.