Chapter 06
Improving the Router API
The utility method on the Router
class - addRoute
is a bit too verbose. You need to specify the HTTP method as a string. It would get tedious when there are suppose hundreds of API routes in an application. Also, devs might not know whether the HTTP methods should be sent in lower-case or upper-case without looking at the source.
Let's abstract that functionality away from the developer, making sure the developers only need to worry about the important pieces.
Current way to add routes:
// file: index.js
class Router {
constructor() {
this.routes = {};
}
addRoute(method, path, handler) {
this.routes[`${method} ${path}`] = handler;
}
...
}
Let's add two new methods named get
and post
, and add some type checks in the addRoute
method:
// file: index.js
const HTTP_METHODS = {
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
PATCH: "PATCH",
HEAD: "HEAD",
OPTIONS: "OPTIONS",
CONNECT: "CONNECT",
TRACE: "TRACE",
};
class Router {
constructor() {
this.routes = {}
}
#addRoute(method, path, handler) {
if (typeof path !== "string" || typeof handler !== "function") {
throw new Error("Invalid argument types: path must be a string and handler must be a function");
}
this.routes.set(`${method} ${path}`, handler);
}
get(path, handler) {
this.#addRoute(HTTP_METHODS.GET, path, handler);
}
post(path, handler) {
this.#addRoute(HTTP_METHODS.POST, path, handler);
}
put(path, handler) {
this.#addRoute(HTTP_METHODS.PUT, path, handler);
}
delete(path, handler) {
this.#addRoute(HTTP_METHODS.DELETE, path, handler);
}
patch(path, handler) {
this.#addRoute(HTTP_METHODS.PATCH, path, handler);
}
head(path, handler) {
this.#addRoute(HTTP_METHODS.HEAD, path, handler);
}
options(path, handler) {
this.#addRoute(HTTP_METHODS.OPTIONS, path, handler);
}
connect(path, handler) {
this.#addRoute(HTTP_METHODS.CONNECT, path, handler);
}
trace(path, handler) {
this.#addRoute(HTTP_METHODS.TRACE, path, handler);
}
...
}
Let's go through the new additions in our code:
get(path, handler) {
this.#addRoute(HTTP_METHODS.GET, path, handler);
}
post(path, handler) {
this.#addRoute(HTTP_METHODS.POST, path, handler);
}
/** rest HTTP method handlers **/
We've created new utility methods on the Router
class. Each one of these methods call the addRoute
method by passing in required parameters. You'd notice that we've also made the addRoute
method private. Since we wish to use it internally in our library and not expose it, it's a good practice to hide it from any external use.
const HTTP_METHODS = { ... }
We've created an object of all the HTTP methods, so that we can use their names with the HTTP_METHODS
namespace, instead of directly passing in strings as an argument, for example:
this.#addRoute("GET", path, handler);
There's nothing wrong with this approach too, but I prefer to avoid raw strings. "GET"
can mean many things, but HTTP_METHODS.GET
gives us the actual idea of what it is all about.
Let's update our testing code to call the newly created http methods instead:
// file: index.js
...
router.get("/", function handleGetBasePath(req, res) {
console.log("Hello from GET /");
res.end();
});
router.post("/", function handlePostBasePath(req, res) {
console.log("Hello from POST /");
res.end()
});
...
If we do a quick test on both the endpoints, every thing seems to be working alright:
$ curl -X POST http://localhost:5255/ -v
## Success
$ curl -X POST http://localhost:5255/foo -v
## Not found
$ curl -X POST http://localhost:5255/foo/bar -v
## Not found
$ curl http://localhost:5255/ -v
## Success
$ curl http://localhost:5255/foo -v
## Not found
$ curl http://localhost:5255/foo -v
## Not found
Great! This looks much better than the previous implementation.