## Ex. Adding `HTTP` method support

<!-- Learn how to extend the existing router to support different HTTP methods like GET, POST, PUT, DELETE, etc in Nodejs. Understand the importance of differentiating between HTTP methods and how it can be implemented in a router. -->

So far, we've built a router capable of matching URL paths to specific handlers. This is a good starting point, but as of now, our router does not differentiate between different HTTP methods like GET, POST, PUT, DELETE, etc. In real-world applications, the same URL path can behave differently based on the HTTP method used, making our current router almost useless for such scenarios.

### Requirements

To make our router more useful and versatile, we need to extend the existing `TrieRouter` and `RouteNode` classes to support different HTTP methods (GET, POST, PUT, DELETE, etc.). This means that each node in the Trie could potentially have multiple handler functions, one for each HTTP method.

### More details

1. Continue with the existing router class `TrieRouter`. Add new functionalities to it.

2. The key in the `handler` `Map` will be the HTTP method as a string (like "GET", "POST") and the value will be the handler function for that HTTP method.

3. Modify the `addRoute` method of the `TrieRouter` class to take an additional parameter `method`.

- `method`: A string representing the HTTP method. This could be "GET", "POST", "PUT", "DELETE", etc.

4. Also update the `findRoute` method. Now it will have another parameter - `method`, to search for routes based on the HTTP method as well as the path.

5. If a handler for a specific path and HTTP method is already present, the new handler should override the old one.

### Example

Once implemented, the usage might look like this:

```js
const trieRouter = new TrieRouter();

function getHandler() {}
function postHandler() {}

trieRouter.addRoute("/home", "GET", getHandler);
trieRouter.addRoute("/home", "POST", postHandler);

console.log(trieRouter.findRoute("/home", "GET")); // -> fn getHandler() {..}
console.log(trieRouter.findRoute("/home", "PATCH")); // -> null or undefined
console.log(trieRouter.findRoute("/home", "POST")); // -> fn postHanlder() {..}
```

Go ahead and add the functionality to our `TrieRouter` class. This will involve making a lot of changes to the previous code. Feel free to share your implementation or ask for feedback in the [Github discussions](https://github.com/ishtms/learn-nodejs-hard-way/discussions) section.

### Hints

1. When you're adding or searching for a route, make sure to consider both the path and the HTTP method.

2. Take care to handle the HTTP method case-insensitively (prefer uppercase). It's common to receive HTTP method names in different cases.

3. Be careful with your error-handling logic to correctly manage the situation where the client does not provide a valid HTTP method.

4. As with Challenge 1, start by making sure the Trie works for a simple case before diving into the more complex functionalities.

5. Don't forget to update your utility functions and other methods to be compatible with these new requirements.

### Solution

Here's the solution I came up with:

```js
const HTTP_METHODS = {
  GET: "GET",
  POST: "POST",
  PUT: "PUT",
  DELETE: "DELETE",
  PATCH: "PATCH",
  HEAD: "HEAD",
  OPTIONS: "OPTIONS",
  CONNECT: "CONNECT",
  TRACE: "TRACE",
};

class RouteNode {
  constructor() {
    this.children = new Map();
    this.handler = new Map();
  }
}

class TrieRouter {
  constructor() {
    this.root = new RouteNode();
  }

  addRoute(path, method, handler) {
    if (typeof path !== "string" || path[0] !== "/") throw new Error("Malformed path provided.");
    if (typeof handler !== "function") throw new Error("Handler should be a function");
    if (!HTTP_METHODS[method]) throw new Error("Invalid HTTP Method");

    let currentNode = this.root;
    let routeParts = path.split("/").filter(Boolean);

    for (let idx = 0; idx < routeParts.length; idx++) {
      const segment = routeParts[idx].toLowerCase();
      if (segment.includes(" ")) throw new Error("Malformed `path` parameter");

      let childNode = currentNode.children.get(segment);
      if (!childNode) {
        childNode = new RouteNode();
        currentNode.children.set(segment, childNode);
      }

      currentNode = childNode;
    }
    currentNode.handler.set(method, handler); // Changed this line
  }

  findRoute(path, method) {
    let segments = path.split("/").filter(Boolean);
    let currentNode = this.root;

    for (let idx = 0; idx < segments.length; idx++) {
      const segment = segments[idx];

      let childNode = currentNode.children.get(segment);
      if (childNode) {
        currentNode = childNode;
      } else {
        return null;
      }
    }

    return currentNode.handler.get(method); // Changed this line
  }

  printTree(node = this.root, indentation = 0) {
    /** Unchanged **/
  }
}
```

The new HTTP method implementation introduces only some minor key changes to extend the existing router implementation to support HTTP methods. Below are the details of what was changed and why:

```js
const HTTP_METHODS = {
  GET: "GET",
  POST: "POST",
  PUT: "PUT",
  DELETE: "DELETE",
  PATCH: "PATCH",
  HEAD: "HEAD",
  OPTIONS: "OPTIONS",
  CONNECT: "CONNECT",
  TRACE: "TRACE",
};
```

Firstly, we've defined a constant object named `HTTP_METHODS` to represent the different HTTP methods. This serves as a reference for the HTTP methods that our `TrieRouter` class will support. We might even do some validation, but that is not necessary (we'll look at it in a later chapter why validation isn't required here)

```js
class TrieRouter {
    addRoute(path, method, handler) { ... }
    ...
}
```

In our `TrieRouter` class, we updated the `addRoute` method. It now takes an additional argument, `method`, which specifies the HTTP method for the route.

```js
addRoute(path, method, handler) {
  if (typeof path !== "string" || path[0] !== "/") throw new Error("Malformed path provided.");
  if (typeof handler !== "function") throw new Error("Handler should be a function");

  // New check for HTTP method
  if (!HTTP_METHODS[method]) throw new Error("Invalid HTTP Method");
  ...
}
```

The error handling has been updated to ensure the `method` is a valid HTTP method.

```js
this.handler = new Map();
```

The `handler` in `RouteNode` has changed from a single function reference to a `Map`. This allows you to store multiple handlers for the same path but with different HTTP methods.

```js
addRoute(path, method, handler) {
  ...
  // Previous -> currentNode.handler = handler;
  currentNode.handler.set(method, handler);
}

findRoute(path, method) {
  ...
  // Previous -> return currentNode.handler;
  return currentNode.handler.get(method); // Changed this line
}
```

In both the `addRoute` and `findRoute` methods, we've updated the line that sets and gets the handler for a specific path. Now, the handler is stored in the `handler` map of the current node, with the HTTP method as the key.

[![Read Next](/assets/imgs/next.png)](/chapters/ch06.09-ex-dynamic-routing.md)
