mirror of
https://github.com/sergeyyarkov/node-docker-example.git
synced 2026-10-11 02:09:22 +03:00
feat: server static files
This commit is contained in:
parent
c00ec6779a
commit
9fa90d956b
14
handlers.js
14
handlers.js
@ -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
26
helpers/file.js
Normal 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
13
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
3
public/css/style.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
BIN
public/images/docker-logo.png
Normal file
BIN
public/images/docker-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
public/images/nodejs-logo.png
Normal file
BIN
public/images/nodejs-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
41
response.js
41
response.js
@ -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;
|
||||||
|
|||||||
15
router.js
15
router.js
@ -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.');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
19
server.js
19
server.js
@ -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
11
views/404.hbs
Normal 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
12
views/index.hbs
Normal 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>
|
||||||
Loading…
x
Reference in New Issue
Block a user