Skip to content

Commit d5bf0ec

Browse files
committed
chore: Added jsdocs to router class and also deleted unnecessary files
1 parent a34a427 commit d5bf0ec

2 files changed

Lines changed: 127 additions & 301 deletions

File tree

src/core/router/router.ts

Lines changed: 127 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,51 @@ import { validateHandler, validateRoute } from "@/core/router/validation";
33
import { handleError, jssert } from "@/core/error";
44
import logger from "@/helpers/joorLogger";
55

6+
/**
7+
* Router class for managing HTTP routes and their handlers.
8+
* It provides methods to register routes for different HTTP methods (GET, POST, PUT, PATCH, DELETE).
9+
* It also validates the routes and their handlers to ensure they are correctly defined.
10+
*
11+
* @class Router
12+
* @example
13+
* const router = new Router();
14+
* router.get('/api/users', (req, res) => {
15+
* res.send('User list');
16+
* });
17+
* router.post('/api/users', (req, res) => {
18+
* res.send('User created');
19+
* });
20+
* router.put('/api/users/:id', (req, res) => {
21+
* res.send(`User ${req.params.id} updated`);
22+
* });
23+
* router.delete('/api/users/:id', (req, res) => {
24+
* res.send(`User ${req.params.id} deleted`);
25+
* });
26+
* router.patch('/api/users/:id', (req, res) => {
27+
* res.send(`User ${req.params.id} partially updated`);
28+
* });
29+
* router.get('/api/users/:id', (req, res) => {
30+
* res.send(`User ${req.params.id} details`);
31+
* });
32+
* router.get('/api/users/:id/friends', (req, res) => {
33+
* res.send(`User ${req.params.id} friends`);
34+
* });
35+
* router.get('/api/users/:id/friends/:friendId', (req, res) => {
36+
* res.send(`User ${req.params.id} friend ${req.params.friendId} details`);
37+
* });
38+
*
39+
* @rules
40+
* - Routes must be unique and not conflict with existing routes.
41+
* - Dynamic routes (e.g., `/update/:id`) should not conflict with other dynamic routes in the same parent. Foe example, `/update/:id` and `/update/:name` are not allowed.
42+
* - Only one root level route (`/`) is allowed. Additional root level routes will be ignored.
43+
* - Handlers or middlewares must be functions.
44+
* - Routes must start with `/`.
45+
* - Routes cannot be empty.
46+
* - Routes can have multiple middlewares.
47+
* - Route handler or middleware can be synchronous or asynchronous.
48+
* - Route handler or middleware should return a `JoorResponse` object or `undefined`.
49+
* - If handler or middleware returns `undefined`, the request will be passed to the next handler or middleware, otherwise it will be sent as a response.
50+
*/
651
class Router {
752
// Static property to store routes.
853
static routes: ROUTES = {
@@ -16,8 +61,8 @@ class Router {
1661
* @param handlers - The route handlers.
1762
*/
1863
public get(route: string, ...handlers: ROUTE_HANDLER[]) {
19-
this.addRoute("GET", route, handlers);
20-
}
64+
this.addRoute("GET", route, handlers);
65+
}
2166

2267
/**
2368
* Registers a POST route with the specified handlers.
@@ -26,35 +71,35 @@ class Router {
2671
* @param handlers - The route handlers.
2772
*/
2873
public post(route: string, ...handlers: ROUTE_HANDLER[]) {
29-
this.addRoute("POST", route, handlers);
30-
}
74+
this.addRoute("POST", route, handlers);
75+
}
3176
/**
3277
* Registers a PUT route with the specified handlers.
3378
*
3479
* @param route - The route path.
3580
* @param handlers - The route handlers.
3681
*/
3782
public put(route: string, ...handlers: ROUTE_HANDLER[]) {
38-
this.addRoute("PUT", route, handlers);
39-
}
83+
this.addRoute("PUT", route, handlers);
84+
}
4085
/**
4186
* Registers a PATCH route with the specified handlers.
4287
*
4388
* @param route - The route path.
4489
* @param handlers - The route handlers.
4590
*/
4691
public patch(route: string, ...handlers: ROUTE_HANDLER[]) {
47-
this.addRoute("PATCH", route, handlers);
48-
}
92+
this.addRoute("PATCH", route, handlers);
93+
}
4994
/**
5095
* Registers a DELETE route with the specified handlers.
5196
*
5297
* @param route - The route path.
5398
* @param handlers - The route handlers.
5499
*/
55-
public delete(route: string, ...handlers: ROUTE_HANDLER[]) {
56-
this.addRoute("DELETE", route, handlers);
57-
}
100+
public delete (route: string, ...handlers: ROUTE_HANDLER[]) {
101+
this.addRoute("DELETE", route, handlers);
102+
}
58103

59104
/**
60105
* Adds a route to the router.
@@ -66,86 +111,86 @@ class Router {
66111
* @throws {Jrror} If there is a route conflict or duplicate.
67112
*/
68113
private addRoute(
69-
httpMethod: ROUTE_METHOD,
70-
route: ROUTE_PATH,
71-
handlers: ROUTE_HANDLER[]
72-
) {
73-
try {
74-
validateRoute(route);
75-
handlers.forEach(validateHandler);
76-
if (!Object.keys(Router.routes).includes('/')) {
77-
Router.routes['/'] = {};
78-
}
114+
httpMethod: ROUTE_METHOD,
115+
route: ROUTE_PATH,
116+
handlers: ROUTE_HANDLER[]
117+
) {
118+
try {
119+
validateRoute(route);
120+
handlers.forEach(validateHandler);
121+
if (!Object.keys(Router.routes).includes('/')) {
122+
Router.routes['/'] = {};
123+
}
79124

80-
if (Object.keys(Router.routes).length > 1) {
81-
Router.routes = {
82-
'/': Router.routes['/'],
83-
};
84-
logger.warn(
85-
'Multiple root level routes detected. Only the first root level route will be considered. Rest will be ignored.'
86-
);
87-
}
125+
if (Object.keys(Router.routes).length > 1) {
126+
Router.routes = {
127+
'/': Router.routes['/'],
128+
};
129+
logger.warn(
130+
'Multiple root level routes detected. Only the first root level route will be considered. Rest will be ignored.'
131+
);
132+
}
88133

89-
const routeParts = route.split('/').filter((part) => part !== '');
134+
const routeParts = route.split('/').filter((part) => part !== '');
90135

91-
if (routeParts.length === 0) {
92-
Router.routes['/'] = {
93-
...Router.routes['/'],
94-
localMiddlewares: Router.routes['/'].localMiddlewares ?? [],
95-
globalMiddlewares: Router.routes['/'].globalMiddlewares ?? [],
96-
[httpMethod]: {
97-
handlers,
98-
},
99-
};
100-
return;
101-
}
136+
if (routeParts.length === 0) {
137+
Router.routes['/'] = {
138+
...Router.routes['/'],
139+
localMiddlewares: Router.routes['/'].localMiddlewares ?? [],
140+
globalMiddlewares: Router.routes['/'].globalMiddlewares ?? [],
141+
[httpMethod]: {
142+
handlers,
143+
},
144+
};
145+
return;
146+
}
102147

103-
let currentNode = Router.routes['/'];
148+
let currentNode = Router.routes['/'];
104149

105-
for (const routePart of routeParts) {
106-
// Remove query params and hash from route
107-
const [node] = routePart.split('#')[0].split('?');
108-
// check if current node has children
109-
currentNode.children = currentNode.children ?? {};
110-
// check if current node is dynamic
111-
if (node.startsWith(':')) {
112-
// check if current parent node has other dynamic routes
113-
const keys = Object.keys(currentNode.children).filter(
114-
(key) => key.startsWith(':') && key !== node
115-
);
116-
// check if current node has other static routes
117-
jssert(
118-
keys.length === 0,
119-
`Route conflict: ${route} conflicts with existing route ${keys[0]}. You cannot have multiple dynamic routes in same parent`,
120-
'/route',
121-
'error'
122-
)
123-
}
124-
// check if current node has the same route, if no create a new node with middlwares
125-
currentNode.children[node] = currentNode.children[node] ?? {
126-
// these middlwares will be used by all the children of this node
127-
globalMiddlewares: currentNode.globalMiddlewares ?? [],
128-
localMiddlewares: currentNode.localMiddlewares ?? [],
129-
};
130-
currentNode = currentNode.children[node];
150+
for (const routePart of routeParts) {
151+
// Remove query params and hash from route
152+
const [node] = routePart.split('#')[0].split('?');
153+
// check if current node has children
154+
currentNode.children = currentNode.children ?? {};
155+
// check if current node is dynamic
156+
if (node.startsWith(':')) {
157+
// check if current parent node has other dynamic routes
158+
const keys = Object.keys(currentNode.children).filter(
159+
(key) => key.startsWith(':') && key !== node
160+
);
161+
// check if current node has other static routes
162+
jssert(
163+
keys.length === 0,
164+
`Route conflict: ${route} conflicts with existing route ${keys[0]}. You cannot have multiple dynamic routes in same parent`,
165+
'/route',
166+
'error'
167+
)
131168
}
132-
133-
// if same route with same method is already registered, show warning
134-
jssert(
135-
!currentNode[httpMethod],
136-
`Route conflict: ${route} with ${httpMethod} method has already been registered. Trying to register the same route will override the previous one, and there might be unintended behaviors`,
137-
'/route',
138-
'warn'
139-
)
140-
141-
// after all above checks, register the route
142-
currentNode[httpMethod] = {
143-
handlers,
169+
// check if current node has the same route, if no create a new node with middlwares
170+
currentNode.children[node] = currentNode.children[node] ?? {
171+
// these middlwares will be used by all the children of this node
172+
globalMiddlewares: currentNode.globalMiddlewares ?? [],
173+
localMiddlewares: currentNode.localMiddlewares ?? [],
144174
};
145-
} catch (error: unknown) {
146-
handleError(error);
175+
currentNode = currentNode.children[node];
147176
}
177+
178+
// if same route with same method is already registered, show warning
179+
jssert(
180+
!currentNode[httpMethod],
181+
`Route conflict: ${route} with ${httpMethod} method has already been registered. Trying to register the same route will override the previous one, and there might be unintended behaviors`,
182+
'/route',
183+
'warn'
184+
)
185+
186+
// after all above checks, register the route
187+
currentNode[httpMethod] = {
188+
handlers,
189+
};
190+
} catch (error: unknown) {
191+
handleError(error);
148192
}
149193
}
194+
}
150195

151196
export default Router;

0 commit comments

Comments
 (0)