feat: server static files

This commit is contained in:
Sergey Yarkov 2022-03-16 22:39:18 +03:00
parent c00ec6779a
commit 9fa90d956b
13 changed files with 136 additions and 27 deletions

View File

@ -1,7 +1,21 @@
import http from 'node:http'; import http from 'node:http';
import fs from 'node:fs';
import Response from './response.js'; import Response from './response.js';
import db from './db.js'; import db from './db.js';
/**
* Render index page
*
* @param {http.ClientRequest} req
* @param {Response} res
*/
export const indexPageHandler = async (req, res) => {
const info = fs.readFileSync('./package.json', 'utf8');
const data = JSON.parse(info.toString());
res.render('./views/index.hbs', { app: { name: data.name } });
};
/** /**
* GET - Get list of all artcles from database * GET - Get list of all artcles from database
* *

26
helpers/file.js Normal file
View File

@ -0,0 +1,26 @@
import fs from 'node:fs';
class FileHelper {
static readDirRecursive(dir, files = []) {
const entries = fs.readdirSync(dir);
const result = files;
for (const entry of entries) {
const path = dir + '/' + entry;
const isDir = fs.statSync(path).isDirectory();
const isFile = fs.statSync(path).isFile();
if (isDir) {
files = this.readDirRecursive(path, files);
}
if (isFile) {
result.push(path);
}
}
return result;
}
}
export default FileHelper;

13
package-lock.json generated
View File

@ -588,6 +588,19 @@
} }
} }
}, },
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"mimic-response": { "mimic-response": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",

View File

@ -22,6 +22,7 @@
"dependencies": { "dependencies": {
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"mime-types": "^2.1.35",
"path-to-regexp": "^6.2.0", "path-to-regexp": "^6.2.0",
"pg": "^8.7.3" "pg": "^8.7.3"
} }

View File

@ -7,6 +7,7 @@ import {
createArticleHandler, createArticleHandler,
getArticleHandler, getArticleHandler,
getArticlesHandler, getArticlesHandler,
indexPageHandler,
} from '../handlers.js'; } from '../handlers.js';
/** /**
@ -14,6 +15,7 @@ import {
*/ */
import { waitMiddleware } from '../middlewares.js'; import { waitMiddleware } from '../middlewares.js';
router.get('/api/articles', getArticlesHandler).middleware([waitMiddleware]); router.get('/', indexPageHandler);
router.get('/api/articles/:id', getArticleHandler).middleware([waitMiddleware]); router.get('/articles', getArticlesHandler).middleware([waitMiddleware]);
router.post('/api/article', createArticleHandler); router.get('/articles/:id', getArticleHandler).middleware([waitMiddleware]);
router.post('/articles', createArticleHandler);

3
public/css/style.css Normal file
View File

@ -0,0 +1,3 @@
body {
font-family: monospace;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,4 +1,9 @@
import http from 'node:http'; import http from 'node:http';
import fs from 'node:fs';
import hbs from 'handlebars';
import mime from 'mime-types';
import router from './router.js';
import FileHelper from './helpers/file.js';
class Response { class Response {
/** /**
@ -24,6 +29,42 @@ class Response {
notFound(text) { notFound(text) {
this.send(text, 404); this.send(text, 404);
} }
/**
* Response with rendered page
*
* @param {string} src Path to static template file
* @param {object} data Data for the page
*/
render(src, data, status = 200) {
try {
const file = fs.readFileSync(src);
const template = hbs.compile(file.toString());
const out = template(data);
this.res.writeHead(status, { 'Content-Type': 'text/html' });
this.res.end(out);
} catch (error) {
throw error;
}
}
/**
* Response with static files
*
* @param {string} path Path to static files
*/
serve(path) {
const files = FileHelper.readDirRecursive(path);
for (const file of files) {
router.define('GET', `/${file}`, (req, { res }) => {
const data = fs.readFileSync(file);
const contentType = mime.lookup(file);
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
}
}
} }
export default Response; export default Response;

View File

@ -1,5 +1,5 @@
import http from 'node:http'; import http from 'node:http';
import util from 'node:util'; import Response from './response.js';
import { pathToRegexp } from 'path-to-regexp'; import { pathToRegexp } from 'path-to-regexp';
import StringHelper from './helpers/string.js'; import StringHelper from './helpers/string.js';
@ -22,11 +22,12 @@ class Router {
} }
/** /**
* This function checks for the existence of a route and calls its handler * This function checks for the existence of a route
* and calls its middlewares and handler
* *
* @param {http.ClientRequest} req * @param {http.ClientRequest} req
* @param {http.ServerResponse} res * @param {Response} res
* @returns {boolean} Is route has been handled * @returns
*/ */
call(req, res) { call(req, res) {
const mask = `${req.method}:${StringHelper.delTrailingSlash(req.url)}`; const mask = `${req.method}:${StringHelper.delTrailingSlash(req.url)}`;
@ -41,7 +42,7 @@ class Router {
*/ */
if (this.routes.hasOwnProperty(mask)) { if (this.routes.hasOwnProperty(mask)) {
this.#middlewares(route, req, res).then(() => route.handler(req, res)); this.#middlewares(route, req, res).then(() => route.handler(req, res));
return true; return;
} }
/* Route url is not exact */ /* Route url is not exact */
@ -62,12 +63,12 @@ class Router {
this.#middlewares(route, req, res).then(() => this.#middlewares(route, req, res).then(() =>
route.handler(req, res) route.handler(req, res)
); );
return true; return;
} }
} }
} }
return false; res.notFound('Page not found.');
} }
/** /**

View File

@ -38,23 +38,8 @@ class Application {
this.server.on('request', (req, res) => { this.server.on('request', (req, res) => {
const response = new Response(req, res); const response = new Response(req, res);
/** response.serve('public');
* Reponse with API data this.router.call(req, response);
*/
if (req.url.includes('api')) {
const handled = this.router.call(req, response);
/**
* Undefined route
*/
if (!handled) response.notFound('Route not found!');
return;
}
/**
* Reponse with static files
*/
response.send('Static Page.');
}); });
this.server.listen(env.app.port, () => this.server.listen(env.app.port, () =>
console.log(`[LOG]: Server is running on port "${env.app.port}"`) console.log(`[LOG]: Server is running on port "${env.app.port}"`)

11
views/404.hbs Normal file
View File

@ -0,0 +1,11 @@
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>404 | Not found</title>
</head>
<body>
Page not found.
</body>
</html>

12
views/index.hbs Normal file
View File

@ -0,0 +1,12 @@
<html lang='en'>
<head>
<meta charset='UTF-8' />
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<title>{{app.name}}</title>
<link rel='stylesheet' href='public/css/style.css' />
</head>
<body>
Index Page.
</body>
</html>