UNPKG

@ah_naf/noderoute

Version:

A lightweight, flexible HTTP server framework for Node.js.

153 lines (130 loc) 4.39 kB
const http = require("http"); const fs = require("fs").promises; const path = require("path"); const { serve404, mimeTypes, serveStaticFile } = require("./utils"); const Route = require("./Route"); class NodeRoute { #server; #routes; #globalMiddlewares; constructor(options = {}) { this.#server = http.createServer(); this.#routes = []; this.#globalMiddlewares = []; this.options = options; if (this.options.timeout) { this.#server.setTimeout(this.options.timeout); } this.#server.on("request", async (req, res) => { const startTime = process.hrtime(); res.sendFile = async (filePath) => { try { const fileHandle = await fs.open(filePath, "r"); const fileReadStream = fileHandle.createReadStream(); const ext = path.extname(filePath); const mimeType = mimeTypes[ext] || "application/octet-stream"; res.setHeader("Content-Type", mimeType); fileReadStream.pipe(res); } catch (error) { console.error(error); res.writeHead(500, { "Content-Type": "text/plain" }); res.end("Internal Server Error"); } }; res.status = (code) => { res.statusCode = code; return res; }; res.json = (data) => { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(data)); }; res.send = (data) => { res.setHeader("Content-Type", "text/plain"); res.end(data); }; const logRequest = () => { const diff = process.hrtime(startTime); const timeTaken = (diff[0] * 1e9 + diff[1]) / 1e9; if (this.options.enableLogging) { console.log( `${req.method} ${req.url} ${res.statusCode} ${timeTaken.toFixed( 3 )}s` ); } }; const handleRouteRequest = async () => { let staticRoute = this.#routes.find( (route) => route.staticRoutes.has(req.url) && req.method === "GET" ); if (staticRoute) { const filePath = staticRoute.staticRoutes.get(req.url); await serveStaticFile(filePath, res); } else { staticRoute = this.#routes.find((route) => { const htmlFile = route.options.index ? route.options.index : "index.html"; return ( route.path === req.url && req.method === "GET" && route.staticRoutes.has( (req.url === "/" ? "/" : req.url + "/") + htmlFile ) ); }); if (staticRoute) { const htmlFile = staticRoute.options.index ? staticRoute.options.index : "index.html"; const filePath = staticRoute.staticRoutes.get( (req.url === "/" ? "/" : req.url + "/") + htmlFile ); await serveStaticFile(filePath, res); } else { const route = this.#routes.find((route) => { const parsedUrl = new URL(req.url, `http://${req.headers.host}`); return route.extractParams(route.path, parsedUrl.pathname); }); if (route) { await route.handleRequest(req, res); } else { serve404(res, this.options.custom404Path); } } } // logRequest(); }; const executeGlobalMiddlewares = (index) => { if (index >= this.#globalMiddlewares.length) { handleRouteRequest(); } else { this.#globalMiddlewares[index](req, res, () => executeGlobalMiddlewares(index + 1) ); } }; res.on("finish", logRequest); executeGlobalMiddlewares(0); }); } use(middleware) { if (typeof middleware !== "function") { throw new Error("Middleware must be a function"); } this.#globalMiddlewares.push(middleware); } route(path, options = null) { if (this.#routes.some((route) => route.path === path)) { throw new Error(`Route for path "${path}" is already defined`); } const route = new Route(path, options ? options : this.options); this.#routes.push(route); return route; } listen(port, cb) { this.#server.listen(port, cb); } } module.exports = NodeRoute;