feat: added new article handler & route handling by regexp

This commit is contained in:
Sergey Yarkov 2022-03-15 22:52:30 +03:00
parent 69717ae409
commit e17386a782
7 changed files with 131 additions and 16 deletions

View File

@ -1,5 +1,6 @@
import http from 'node:http'; import http from 'node:http';
import Response from './response.js'; import Response from './response.js';
import db from './db.js';
/** /**
* Get list of all artcles from database * Get list of all artcles from database
@ -8,8 +9,34 @@ import Response from './response.js';
* @param {Response} res * @param {Response} res
*/ */
export const getArticlesHandler = async (req, res) => { export const getArticlesHandler = async (req, res) => {
res.json([ const data = await db.client.query('SELECT * FROM "articles"');
{ id: 1, title: 'First artcile' }, res.json({ data: data.rows });
{ id: 2, title: 'Second artcile' }, };
]);
/**
* Get article by ID
*
* @param {http.ClientRequest} req
* @param {Response} res
*/
export const getArticleHandler = async (req, res) => {
/**
* Validate url `id` param
*/
if (!/^\d+$/.test(req.params.id)) {
res.json({ data: {}, error: 'Parameters is not valid!' }, 422);
return;
}
const data = await db.client.query(
'SELECT * from "articles" WHERE "id" = $1',
[req.params.id]
);
if (data.rows.length === 0) {
res.json({ data: {}, error: 'Article not found.' }, 404);
return;
}
res.json({ data: data.rows });
}; };

41
package-lock.json generated
View File

@ -393,6 +393,18 @@
"integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==",
"dev": true "dev": true
}, },
"handlebars": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
"requires": {
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
"uglify-js": "^3.1.4",
"wordwrap": "^1.0.0"
}
},
"has-flag": { "has-flag": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@ -594,8 +606,7 @@
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
"dev": true
}, },
"ms": { "ms": {
"version": "2.1.3", "version": "2.1.3",
@ -603,6 +614,11 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true "dev": true
}, },
"neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"nodemon": { "nodemon": {
"version": "2.0.15", "version": "2.0.15",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz",
@ -682,6 +698,11 @@
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
}, },
"path-to-regexp": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.0.tgz",
"integrity": "sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg=="
},
"pg": { "pg": {
"version": "8.7.3", "version": "8.7.3",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz", "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.3.tgz",
@ -881,6 +902,11 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true "dev": true
}, },
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"split2": { "split2": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz", "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
@ -960,6 +986,12 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"uglify-js": {
"version": "3.15.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.3.tgz",
"integrity": "sha512-6iCVm2omGJbsu3JWac+p6kUiOpg3wFO2f8lIXjfEb8RrmLjzog1wTPMmwKB7swfzzqxj9YM+sGUM++u1qN4qJg==",
"optional": true
},
"undefsafe": { "undefsafe": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@ -1026,6 +1058,11 @@
"string-width": "^4.0.0" "string-width": "^4.0.0"
} }
}, },
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"wrap-ansi": { "wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",

View File

@ -21,6 +21,8 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"handlebars": "^4.7.7",
"path-to-regexp": "^6.2.0",
"pg": "^8.7.3" "pg": "^8.7.3"
} }
} }

View File

@ -3,6 +3,7 @@ import router from '../router.js';
/** /**
* Handlers * Handlers
*/ */
import { getArticlesHandler } from '../handlers.js'; import { getArticleHandler, getArticlesHandler } from '../handlers.js';
router.define('/api/articles', getArticlesHandler); router.define('/api/articles', getArticlesHandler);
router.define('/api/article/:id', getArticleHandler);

View File

@ -11,8 +11,8 @@ class Response {
this.res = res; this.res = res;
} }
send(text) { send(text, status = 200) {
this.res.writeHead(200, { 'Content-Type': 'text/plain' }); this.res.writeHead(status, { 'Content-Type': 'text/plain' });
this.res.end(text); this.res.end(text);
} }
@ -21,8 +21,8 @@ class Response {
this.res.end(JSON.stringify(data)); this.res.end(JSON.stringify(data));
} }
notFound(data) { notFound(text) {
this.json(data, 404); this.send(text, 404);
} }
} }

View File

@ -1,29 +1,77 @@
import http from 'node:http'; import http from 'node:http';
import { pathToRegexp } from 'path-to-regexp';
class Router { class Router {
constructor() { constructor() {
/** /**
* Store the current list of routes for out API * Store the current list of routes
*/ */
this.routes = {}; this.routes = {};
} }
/** /**
* This function checks for the existence of a route and calls its handler
* *
* @param {http.ClientRequest} req * @param {http.ClientRequest} req
* @param {http.ServerResponse} res * @param {http.ServerResponse} res
* @returns * @returns
*/ */
call(req, res) { call(req, res) {
/**
* Find the exact route
*/
if (this.routes.hasOwnProperty(req.url)) { if (this.routes.hasOwnProperty(req.url)) {
this.routes[req.url](req, res); this.routes[req.url].handler(req, res);
return true; return true;
} }
/**
* Find route by regular expression
*/
const keys = Object.keys(this.routes);
let i = keys.length;
while (i--) {
const route = this.routes[keys[i]];
if (!route.options.exact && route.options.regexp !== undefined) {
const match = req.url.match(route.options.regexp);
if (match) {
if (route.options.params !== undefined) {
req.params = {};
route.options.params.forEach((param) => {
req.params[param.name] = match[1];
});
}
route.handler(req, res);
return true;
}
}
}
return false; return false;
} }
define(url, handler) { /**
this.routes[url] = handler; * Add more routes to router store
*
* @param {string} url
* @param {function} handler
* @param {boolean} exact
*/
define(
url,
handler,
options = { exact: true, regexp: undefined, params: undefined }
) {
if (url.includes(':')) {
options.params = [];
options.exact = false;
options.regexp = pathToRegexp(url, options.params);
}
this.routes[url] = {
handler,
options,
};
} }
} }

View File

@ -47,7 +47,7 @@ class Application {
/** /**
* Undefined route * Undefined route
*/ */
if (!handled) response.notFound({ data: 'Route not found!' }); if (!handled) response.notFound('Route not found!');
return; return;
} }