Skip to content

Commit 0ab94cd

Browse files
committed
adds chapter 6.8 - http methods API
1 parent 2b3fab3 commit 0ab94cd

5 files changed

+170
-86
lines changed

Readme.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -293,21 +293,21 @@ The repo for our backend framework- [Velocy](https://github.com/ishtms/velocy).
293293
- [Example](/chapters/ch06.7-ex-adding-http-methods#example)
294294
- [Hints](/chapters/ch06.7-ex-adding-http-methods#hints)
295295
- [Solution](/chapters/ch06.7-ex-adding-http-methods#solution)
296-
- [Exercise 4 - Implementing Dynamic Routing](/chapters/ch06.8-ex-dynamic-routing#exercise-4-implementing-dynamic-routing)
297-
- [Why Dynamic Routing?](/chapters/ch06.8-ex-dynamic-routing#why-dynamic-routing)
298-
- [Flexibility](/chapters/ch06.8-ex-dynamic-routing#flexibility)
299-
- [Better User Experience](/chapters/ch06.8-ex-dynamic-routing#better-user-experience)
300-
- [Better Developer Experience](/chapters/ch06.8-ex-dynamic-routing#better-developer-experience)
301-
- [Better SEO](/chapters/ch06.8-ex-dynamic-routing#better-seo)
302-
- [Anatomy of a dynamic route](/chapters/ch06.8-ex-dynamic-routing#anatomy-of-a-dynamic-route)
303-
- [Challenge 1: Update the `getRouteParts()` function](/chapters/ch06.8-ex-dynamic-routing#challenge-1-update-the-getrouteparts-function)
304-
- [Requirement](/chapters/ch06.8-ex-dynamic-routing#requirement)
305-
- [More Details:](/chapters/ch06.8-ex-dynamic-routing#more-details)
306-
- [Solution](/chapters/ch06.8-ex-dynamic-routing#solution)
307-
- [Challenge 2: Add the dynamic routing functionality in `Router` class](/chapters/ch06.8-ex-dynamic-routing#challenge-2-add-the-dynamic-routing-functionality-in-router-class)
308-
- [Requirements](/chapters/ch06.8-ex-dynamic-routing#requirements)
309-
- [More Details](/chapters/ch06.8-ex-dynamic-routing#more-details-1)
310-
- [Steps](/chapters/ch06.8-ex-dynamic-routing#steps)
311-
- [Solution](/chapters/ch06.8-ex-dynamic-routing#solution-1)
312-
- [Challenge 3: Adding Wildcard (Catch-All) Route Support](/chapters/ch06.8-ex-dynamic-routing#challenge-3-adding-wildcard-catch-all-route-support) - [More Details](/chapters/ch06.8-ex-dynamic-routing#more-details-2) - [Steps](/chapters/ch06.8-ex-dynamic-routing#steps-1)
313-
- [Solution](/chapters/ch06.8-ex-dynamic-routing#solution-2)
296+
- [Exercise 5 - Implementing Dynamic Routing](/chapters/ch06.9-ex-dynamic-routing#exercise-4-implementing-dynamic-routing)
297+
- [Why Dynamic Routing?](/chapters/ch06.9-ex-dynamic-routing#why-dynamic-routing)
298+
- [Flexibility](/chapters/ch06.9-ex-dynamic-routing#flexibility)
299+
- [Better User Experience](/chapters/ch06.9-ex-dynamic-routing#better-user-experience)
300+
- [Better Developer Experience](/chapters/ch06.9-ex-dynamic-routing#better-developer-experience)
301+
- [Better SEO](/chapters/ch06.9-ex-dynamic-routing#better-seo)
302+
- [Anatomy of a dynamic route](/chapters/ch06.9-ex-dynamic-routing#anatomy-of-a-dynamic-route)
303+
- [Challenge 1: Update the `getRouteParts()` function](/chapters/ch06.9-ex-dynamic-routing#challenge-1-update-the-getrouteparts-function)
304+
- [Requirement](/chapters/ch06.9-ex-dynamic-routing#requirement)
305+
- [More Details:](/chapters/ch06.9-ex-dynamic-routing#more-details)
306+
- [Solution](/chapters/ch06.9-ex-dynamic-routing#solution)
307+
- [Challenge 2: Add the dynamic routing functionality in `Router` class](/chapters/ch06.9-ex-dynamic-routing#challenge-2-add-the-dynamic-routing-functionality-in-router-class)
308+
- [Requirements](/chapters/ch06.9-ex-dynamic-routing#requirements)
309+
- [More Details](/chapters/ch06.9-ex-dynamic-routing#more-details-1)
310+
- [Steps](/chapters/ch06.9-ex-dynamic-routing#steps)
311+
- [Solution](/chapters/ch06.9-ex-dynamic-routing#solution-1)
312+
- [Challenge 3: Adding Wildcard (Catch-All) Route Support](/chapters/ch06.9-ex-dynamic-routing#challenge-3-adding-wildcard-catch-all-route-support) - [More Details](/chapters/ch06.9-ex-dynamic-routing#more-details-2) - [Steps](/chapters/ch06.9-ex-dynamic-routing#steps-1)
313+
- [Solution](/chapters/ch06.9-ex-dynamic-routing#solution-2)

chapters/ch06.6-ex-implementing-router.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,3 +519,5 @@ null
519519
```
520520
521521
Everything seems to be working well. This is it for the `findRoute` method. This was much easier than our `addRoute` implementation, since we only cared about searching. Excellent, we've grasped the basics well! Now let's move on to the more advanced features in the next chapter, ie Implementing HTTP methods with our router.
522+
523+
[![Read Next](/assets/imgs/next.png)](/chapters/ch06.7-ex-adding-http-methods.md)

chapters/ch06.7-ex-adding-http-methods.md

Lines changed: 60 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,6 @@ Go ahead and add the functionality to our `TrieRouter` class. This will involve
5757
Here's the solution I came up with:
5858

5959
```js
60-
function getRouteParts(path) {
61-
/** stays the same **/
62-
}
63-
6460
const HTTP_METHODS = {
6561
GET: "GET",
6662
POST: "POST",
@@ -73,88 +69,66 @@ const HTTP_METHODS = {
7369
TRACE: "TRACE",
7470
};
7571

76-
class Router {
72+
class RouteNode {
7773
constructor() {
78-
this.rootNode = new RouteNode();
74+
this.children = new Map();
75+
this.handler = new Map();
7976
}
77+
}
8078

81-
addRoute(path, method, handler) {
82-
if (typeof path != "string" || typeof handler != "function" || typeof method != "string") {
83-
throw new Error(
84-
"Invalid params sent to the `addRoute` method. `path` should be of the type `string`, `method` should be a valid HTTP verb and of type `string` and `handler` should be of the type `function`"
85-
);
86-
}
87-
88-
method = method.toUpperCase();
89-
90-
let routeParts = getRouteParts(path);
91-
92-
if (routeParts[routeParts.length - 1] == "") {
93-
routeParts = routeParts.slice(0, routeParts.length - 1);
94-
}
95-
96-
this.#addRouteParts(routeParts, method, handler);
79+
class TrieRouter {
80+
constructor() {
81+
this.root = new RouteNode();
9782
}
9883

99-
#addRouteParts(routeParts, method, handler) {
100-
let node = this.rootNode;
101-
102-
for (let idx = 0; idx < routeParts.length; idx++) {
103-
let currPart = routeParts[idx];
84+
addRoute(path, method, handler) {
85+
if (typeof path !== "string" || path[0] !== "/") throw new Error("Malformed path provided.");
86+
if (typeof handler !== "function") throw new Error("Handler should be a function");
87+
if (!HTTP_METHODS[method]) throw new Error("Invalid HTTP Method");
10488

105-
let nextNode = node.children.get(currPart);
89+
let currentNode = this.root;
90+
let routeParts = path.split("/").filter(Boolean);
10691

107-
if (!nextNode) {
108-
nextNode = new RouteNode();
109-
node.children.set(currPart, nextNode);
110-
}
92+
for (let idx = 0; idx < routeParts.length; idx++) {
93+
const segment = routeParts[idx].toLowerCase();
94+
if (segment.includes(" ")) throw new Error("Malformed `path` parameter");
11195

112-
if (idx == routeParts.length - 1) {
113-
nextNode.handler.set(method, handler);
96+
let childNode = currentNode.children.get(segment);
97+
if (!childNode) {
98+
childNode = new RouteNode();
99+
currentNode.children.set(segment, childNode);
114100
}
115101

116-
node = nextNode;
102+
currentNode = childNode;
117103
}
104+
currentNode.handler.set(method, handler); // Changed this line
118105
}
119106

120107
findRoute(path, method) {
121-
if (path.endsWith("/")) path = path.substring(0, path.length - 1);
122-
123-
let routeParts = getRouteParts(path);
124-
let node = this.rootNode;
125-
let handler = null;
108+
let segments = path.split("/").filter(Boolean);
109+
let currentNode = this.root;
126110

127-
for (let idx = 0; idx < routeParts.length; idx++) {
128-
let currPart = routeParts[idx];
129-
130-
let nextNode = node.children.get(currPart);
131-
132-
if (!nextNode) break;
111+
for (let idx = 0; idx < segments.length; idx++) {
112+
const segment = segments[idx];
133113

134-
if (idx == routeParts.length - 1) {
135-
handler = nextNode.handler.get(method);
114+
let childNode = currentNode.children.get(segment);
115+
if (childNode) {
116+
currentNode = childNode;
117+
} else {
118+
return null;
136119
}
137-
138-
node = nextNode;
139120
}
140121

141-
return handler;
122+
return currentNode.handler.get(method); // Changed this line
142123
}
143124

144-
printTree(node = this.rootNode, indentation = 0) {
145-
/** unchanged **/
146-
}
147-
}
148-
149-
class RouteNode {
150-
constructor() {
151-
this.handler = new Map();
152-
this.children = new Map();
125+
printTree(node = this.root, indentation = 0) {
126+
/** Unchanged **/
153127
}
154128
}
155129
```
156130

157-
The new HTTP method implementation introduces several key changes to extend the existing router implementation to support HTTP methods. Below are the details of what was changed and why:
131+
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:
158132

159133
```js
160134
const HTTP_METHODS = {
@@ -182,19 +156,38 @@ class TrieRouter {
182156
In our `TrieRouter` class, we updated the `addRoute` method. It now takes an additional argument, `method`, which specifies the HTTP method for the route.
183157

184158
```js
185-
if (typeof path != "string" || typeof handler != "function" || typeof method != "string") { ... }
159+
addRoute(path, method, handler) {
160+
if (typeof path !== "string" || path[0] !== "/") throw new Error("Malformed path provided.");
161+
if (typeof handler !== "function") throw new Error("Handler should be a function");
162+
163+
// New check for HTTP method
164+
if (!HTTP_METHODS[method]) throw new Error("Invalid HTTP Method");
165+
...
166+
}
186167
```
187168

188-
The error handling has been updated to ensure the `method` is also a string.
169+
The error handling has been updated to ensure the `method` is a valid HTTP method.
189170

190171
```js
191-
method = method.toUpperCase();
172+
this.handler = new Map();
192173
```
193174

194-
The `method` string is converted to uppercase to standardize the HTTP methods.
175+
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.
195176

196177
```js
197-
this.handler = new Map();
178+
addRoute(path, method, handler) {
179+
...
180+
// Previous -> currentNode.handler = handler;
181+
currentNode.handler.set(method, handler);
182+
}
183+
184+
findRoute(path, method) {
185+
...
186+
// Previous -> return currentNode.handler;
187+
return currentNode.handler.get(method); // Changed this line
188+
}
198189
```
199190

200-
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.
191+
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.
192+
193+
[![Read Next](/assets/imgs/next.png)](/chapters/ch06.9-ex-dynamic-routing.md)
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
## Exercise 4 - Adding HTTP methods to the Router
2+
3+
In this exercise, we will improve our `TrieRouter` API by implementing support for HTTP verbs (GET, POST, PUT, DELETE, etc.) directly instead of using raw strings. Let's look at our current implementation of the `TrieRouter`:
4+
5+
```js
6+
trieRouter.addRoute("GET", "/users", () => {});
7+
trieRouter.addRoute("GET", "/", () => {});
8+
```
9+
10+
As you could already feel, this approach is not very flexible and uses raw strings, which can lead to typing errors, and has no auto-completion support unfortunately. Let's improve this by adding support for HTTP methods directly to the `TrieRouter` API.
11+
12+
Firstly, we've added dedicated methods for each HTTP method in the `TrieRouter` class. This allows users to define routes more intuitively using method-specific calls like `trieRouter.get('/home', handler)` for the GET method and `trieRouter.post('/home', handler)` for the POST method.
13+
14+
### Update the `TrieRouter` class
15+
16+
```js
17+
const HTTP_METHODS = { ... };
18+
19+
class TrieRouter {
20+
...
21+
22+
#addRoute(path, method, handler) {
23+
...
24+
}
25+
26+
get(path, handler) {
27+
this.#addRoute(path, HTTP_METHODS.GET, handler);
28+
}
29+
30+
post(path, handler) {
31+
this.#addRoute(path, HTTP_METHODS.POST, handler);
32+
}
33+
34+
put(path, handler) {
35+
this.#addRoute(path, HTTP_METHODS.PUT, handler);
36+
}
37+
38+
delete(path, handler) {
39+
this.#addRoute(path, HTTP_METHODS.DELETE, handler);
40+
}
41+
42+
patch(path, handler) {
43+
this.#addRoute(path, HTTP_METHODS.PATCH, handler);
44+
}
45+
46+
head(path, handler) {
47+
this.#addRoute(path, HTTP_METHODS.HEAD, handler);
48+
}
49+
50+
options(path, handler) {
51+
this.#addRoute(path, HTTP_METHODS.OPTIONS, handler);
52+
}
53+
54+
connect(path, handler) {
55+
this.#addRoute(path, HTTP_METHODS.CONNECT, handler);
56+
}
57+
58+
trace(path, handler) {
59+
this.#addRoute(path, HTTP_METHODS.TRACE, handler);
60+
}
61+
...
62+
}
63+
```
64+
65+
Firstly, we've added dedicated methods for each HTTP method in the `TrieRouter` class. This allows users to define routes more intuitively using method-specific calls like `trieRouter.get('/home', handler)` for the GET method and `trieRouter.post('/home', handler)` for the POST method.
66+
67+
In each of these methods, we call the existing `addRoute` method, passing the appropriate HTTP method from the `HTTP_METHODS` object.
68+
69+
This change allows for a consistent and clear way to find routes based on the HTTP method.
70+
71+
Sedcondly, we've made the `addRoute` method private by prefixing it with a `#`. This means that the `#addRoute` method can now only be accessed from within the `TrieRouter` class and not from outside.
72+
73+
Now, to test the new API, let's update our previous example:
74+
75+
```js
76+
const trieRouter = new TrieRouter();
77+
78+
trieRouter.get("/users", function get1() {});
79+
trieRouter.post("/users", function post1() {});
80+
trieRouter.put("/users", function put1() {});
81+
trieRouter.delete("/users", function delete1() {});
82+
83+
console.log(trieRouter.findRoute("/users/e", HTTP_METHODS.GET)); // null
84+
console.log(trieRouter.findRoute("/users", HTTP_METHODS.POST)); // function post1() {}
85+
console.log(trieRouter.findRoute("/users", HTTP_METHODS.PUT)); // function put1() {}
86+
console.log(trieRouter.findRoute("/users", HTTP_METHODS.TRACE)); // undefined
87+
```
88+
89+
Looks good, and now we have a more intuitive way to define routes based on HTTP methods. Let's move on to the next exercise to add support for route parameters.

chapters/ch06.8-ex-dynamic-routing.md renamed to chapters/ch06.9-ex-dynamic-routing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Exercise 4 - Implementing Dynamic Routing
1+
## Exercise 5 - Implementing Dynamic Routing
22

33
When we're building a server application, dynamic routing is an essential feature for creating flexible and scalable applications. To fully grasp its significance and how we can enhance our router to support dynamic routes like `/users/:id`, let's delve into the concept of dynamic routing.
44

0 commit comments

Comments
 (0)