从零到一搭建 Node.js 框架
搭建一个 Node.js 框架是理解 Web 应用架构的绝佳方式。本指南将带您完成创建一个轻量级但功能完善的 Node.js 框架的全过程,类似于 Express 或 Koa,但规模更小,便于理解。
目录
- 项目初始化
- 创建核心应用类
- 路由系统
- 中间件系统
- 请求和响应对象增强
- 错误处理
- 静态文件服务
- 模板引擎支持
- 配置和环境变量
- 完整项目结构
- 使用示例
- 下一步
1. 项目初始化
首先,创建项目目录并初始化 npm 项目:
mkdir my-nodejs-framework
cd my-nodejs-framework
npm init -y
安装必要的依赖:
npm install --save path-to-regexp http-errors
npm install --save-dev jest nodemon
更新 package.json
中的脚本:
"scripts": {"start": "node index.js","dev": "nodemon index.js","test": "jest"
}
2. 创建核心应用类
创建 lib
目录和核心应用文件:
mkdir -p lib
touch lib/application.js
实现核心应用类:
// lib/application.js
const http = require('http');
const EventEmitter = require('events');class Application extends EventEmitter {constructor() {super();this.middleware = [];this.settings = {};}listen(...args) {const server = http.createServer(this.callback());return server.listen(...args);}use(fn) {this.middleware.push(fn);return this;}callback() {return (req, res) => {let index = 0;const next = (err) => {// 错误处理逻辑将在后面实现if (err) return this.handleError(err, req, res);if (index >= this.middleware.length) return;const middleware = this.middleware[index++];try {middleware(req, res, next);} catch (err) {next(err);}};// 增强 req 和 res 对象this.enhanceReqRes(req, res);// 开始执行中间件链next();};}enhanceReqRes(req, res) {// 这里将添加对请求和响应对象的增强req.app = this;res.app = this;// 添加常用响应方法res.send = function(body) {if (typeof body === 'object') {this.setHeader('Content-Type', 'application/json');this.end(JSON.stringify(body));} else {this.setHeader('Content-Type', 'text/html');this.end(String(body));}};res.status = function(code) {this.statusCode = code;return this;};res.json = function(obj) {this.setHeader('Content-Type', 'application/json');this.end(JSON.stringify(obj));};}handleError(err, req, res) {// 默认错误处理console.error(err);res.statusCode = err.status || err.statusCode || 500;res.end(err.message || 'Internal Server Error');}set(setting, val) {this.settings[setting] = val;return this;}get(setting) {return this.settings[setting];}
}module.exports = Application;
3. 路由系统
创建路由文件:
touch lib/router.js
实现路由系统:
// lib/router.js
const { pathToRegexp } = require('path-to-regexp');class Router {constructor() {this.routes = {GET: [],POST: [],PUT: [],DELETE: [],PATCH: [],HEAD: [],OPTIONS: []};this.middleware = this.routes;}route(method, path, handlers) {const keys = [];const regexp = pathToRegexp(path, keys);this.routes[method].push({regexp,keys,handlers: Array.isArray(handlers) ? handlers : [handlers]});return this;}get(path, ...handlers) {return this.route('GET', path, handlers);}post(path, ...handlers) {return this.route('POST', path, handlers);}put(path, ...handlers) {return this.route('PUT', path, handlers);}delete(path, ...handlers) {return this.route('DELETE', path, handlers);}patch(path, ...handlers) {return this.route('PATCH', path, handlers);}head(path, ...handlers) {return this.route('HEAD', path, handlers);}options(path, ...handlers) {return this.route('OPTIONS', path, handlers);}all(path, ...handlers) {Object.keys(this.routes).forEach(method => {this.route(method, path, handlers);});return this;}match(req) {const { method, url } = req;const path = url.split('?')[0];const routes = this.routes[method] || [];for (const route of routes) {const match = route.regexp.exec(path);if (match) {req.params = {};const values = match.slice(1);for (let i = 0; i < route.keys.length; i++) {const key = route.keys[i];req.params[key.name] = values[i];}return route.handlers;}}return null;}middleware(req, res, next) {const handlers = this.match(req);if (!handlers) return next();let idx = 0;const nextHandler = (err) => {if (err) return next(err);const handler = handlers[idx++];if (!handler) return next();try {handler(req, res, nextHandler);} catch (err) {nextHandler(err);}};nextHandler();}
}module.exports = () => {const router = new Router();// 返回中间件函数return function routerMiddleware(req, res, next) {router.middleware(req, res, next);};
};
4. 中间件系统
中间件系统已经在核心应用类中实现了基本功能。现在添加一些常用的内置中间件:
mkdir -p lib/middleware
touch lib/middleware/body-parser.js
实现请求体解析中间件:
// lib/middleware/body-parser.js
function bodyParser() {return (req, res, next) => {if (req.method === 'GET' || req.method === 'HEAD') {return next();}const contentType = req.headers['content-type'] || '';let body = '';req.on('data', chunk => {body += chunk.toString();});req.on('end', () => {if (contentType.includes('application/json')) {try {req.body = JSON.parse(body);} catch (err) {req.body = {};return next(err);}} else if (contentType.includes('application/x-www-form-urlencoded')) {req.body = Object.fromEntries(new URLSearchParams(body));} else {req.body = body;}next();});};
}module.exports = bodyParser;
5. 请求和响应对象增强
扩展 enhanceReqRes
方法来提供更多功能:
// 在 lib/application.js 中更新 enhanceReqRes 方法enhanceReqRes(req, res) {req.app = this;res.app = this;// 解析查询参数const url = new URL(req.url, `http://${req.headers.host}`);req.query = Object.fromEntries(url.searchParams);// 请求扩展req.get = function(field) {return this.headers[field.toLowerCase()];};// 响应扩展res.set = function(field, value) {this.setHeader(field, value);return this;};res.send = function(body) {if (typeof body === 'object') {this.setHeader('Content-Type', 'application/json');this.end(JSON.stringify(body));} else {this.setHeader('Content-Type', 'text/html');this.end(String(body));}return this;};res.status = function(code) {this.statusCode = code;return this;};res.json = function(obj) {this.setHeader('Content-Type', 'application/json');this.end(JSON.stringify(obj));return this;};res.redirect = function(status, url) {if (typeof status === 'string') {url = status;status = 302;}this.statusCode = status;this.setHeader('Location', url);this.end();return this;};res.render = (view, locals) => {// 模板引擎支持将在后面实现if (!this.settings.views || !this.settings['view engine']) {throw new Error('No view engine configured');}// 将在模板引擎部分完成};
}
6. 错误处理
更新应用类中的错误处理方法:
// 在 lib/application.js 中更新 handleError 方法handleError(err, req, res) {// 检查是否有错误处理中间件const errorHandlers = this.middleware.filter(fn => fn.length === 4);if (errorHandlers.length > 0) {let idx = 0;const next = (error) => {if (idx >= errorHandlers.length) {// 如果所有错误处理中间件都不处理,则使用默认处理this.defaultErrorHandler(error || err, req, res);return;}const handler = errorHandlers[idx++];try {handler(error || err, req, res, next);} catch (e) {next(e);}};next(err);} else {// 如果没有错误处理中间件,则使用默认处理this.defaultErrorHandler(err, req, res);}
}defaultErrorHandler(err, req, res) {console.error(err);const statusCode = err.status || err.statusCode || 500;const message = this.get('env') === 'production' && statusCode === 500? 'Internal Server Error': err.message || 'Internal Server Error';res.statusCode = statusCode;if (req.headers.accept && req.headers.accept.includes('application/json')) {res.setHeader('Content-Type', 'application/json');res.end(JSON.stringify({ error: message }));} else {res.setHeader('Content-Type', 'text/html');res.end(`<h1>${statusCode} - ${message}</h1>`);}
}
7. 静态文件服务
创建静态文件服务中间件:
touch lib/middleware/static.js
实现静态文件服务:
// lib/middleware/static.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
const createReadStream = fs.createReadStream;function serveStatic(root, options = {}) {const { index = 'index.html', extensions = [] } = options;return async (req, res, next) => {if (req.method !== 'GET' && req.method !== 'HEAD') {return next();}// 获取请求路径const url = req.url.split('?')[0];let filePath = path.join(root, url);try {let stats = await stat(filePath);if (stats.isDirectory()) {// 尝试索引文件if (index) {filePath = path.join(filePath, index);stats = await stat(filePath);} else {// 如果不使用索引文件,则列出目录内容if (options.showDir) {const files = await readdir(filePath);return res.send(`<ul>${files.map(file => `<li><a href="${path.join(url, file)}">${file}</a></li>`).join('')}</ul>`);}return next();}}// 设置内容类型const ext = path.extname(filePath).toLowerCase();const contentType = {'.html': 'text/html','.css': 'text/css','.js': 'application/javascript','.json': 'application/json','.png': 'image/png','.jpg': 'image/jpeg','.jpeg': 'image/jpeg','.gif': 'image/gif','.svg': 'image/svg+xml'}[ext] || 'application/octet-stream';res.setHeader('Content-Type', contentType);res.setHeader('Content-Length', stats.size);const stream = createReadStream(filePath);stream.pipe(res);} catch (err) {if (err.code === 'ENOENT') {// 文件不存在,尝试扩展名if (extensions.length > 0) {for (const ext of extensions) {try {const newPath = `${filePath}.${ext}`;const stats = await stat(newPath);if (stats.isFile()) {const stream = createReadStream(newPath);const contentType = {'html': 'text/html','css': 'text/css','js': 'application/javascript','json': 'application/json'}[ext] || 'application/octet-stream';res.setHeader('Content-Type', contentType);res.setHeader('Content-Length', stats.size);stream.pipe(res);return;}} catch (e) {// 继续尝试下一个扩展名}}}// 所有尝试都失败,继续下一个中间件return next();}// 其他错误传递给错误处理return next(err);}};
}module.exports = serveStatic;
8. 模板引擎支持
创建视图渲染支持:
touch lib/view.js
实现视图引擎管理:
// lib/view.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');const readFile = promisify(fs.readFile);class View {constructor(options) {this.root = options.root;this.engines = options.engines || {};this.defaultEngine = options.defaultEngine;this.cache = options.cache || {};this.cacheEnabled = options.cacheEnabled !== false;}async render(name, options = {}) {const viewPath = this.lookup(name);if (!viewPath) {throw new Error(`Could not find view: ${name}`);}// 从缓存中获取编译后的模板let template;if (this.cacheEnabled && this.cache[viewPath]) {template = this.cache[viewPath];} else {const content = await readFile(viewPath, 'utf8');const engine = this.getEngine(viewPath);if (!engine) {throw new Error(`No engine registered for ${path.extname(viewPath)}`);}template = engine.compile(content, { filename: viewPath, ...options });if (this.cacheEnabled) {this.cache[viewPath] = template;}}// 渲染模板return template(options);}lookup(name) {// 如果有绝对路径,则直接使用if (path.isAbsolute(name)) {return this.exists(name) ? name : null;}// 如果没有扩展名,尝试添加默认引擎的扩展名let ext = path.extname(name);if (!ext && this.defaultEngine) {name = `${name}.${this.defaultEngine}`;ext = `.${this.defaultEngine}`;}// 在视图目录中查找文件const filePath = path.join(this.root, name);if (this.exists(filePath)) {return filePath;}return null;}exists(filePath) {try {fs.accessSync(filePath);return true;} catch (e) {return false;}}getEngine(filePath) {const ext = path.extname(filePath).slice(1);return this.engines[ext];}static registerEngine(ext, engine) {if (ext[0] === '.') ext = ext.slice(1);this.engines = this.engines || {};this.engines[ext] = engine;}
}module.exports = View;
更新应用类中的渲染方法:
// 在 lib/application.js 中添加const View = require('./view');// 在 Application 类中添加
constructor() {super();this.middleware = [];this.settings = {};this.engines = {};this.cache = {};
}// 添加模板引擎注册方法
engine(ext, fn) {if (ext[0] === '.') ext = ext.slice(1);this.engines[ext] = fn;return this;
}// 更新 enhanceReqRes 方法中的 render 功能
res.render = (view, locals, callback) => {const done = callback || ((err, html) => {if (err) return next(err);res.setHeader('Content-Type', 'text/html');res.end(html);});try {const viewOptions = {root: this.get('views') || process.cwd() + '/views',engines: this.engines,defaultEngine: this.get('view engine'),cacheEnabled: this.get('view cache') !== false,cache: this.cache};const viewInstance = new View(viewOptions);const context = {...this.locals,...res.locals,...locals,settings: this.settings};viewInstance.render(view, context).then(html => done(null, html)).catch(err => done(err));} catch (err) {done(err);}
};
9. 配置和环境变量
创建配置管理工具:
touch lib/config.js
实现配置管理:
// lib/config.js
const fs = require('fs');
const path = require('path');class Config {constructor(options = {}) {this.env = process.env.NODE_ENV || 'development';this.configPath = options.configPath || process.cwd();this.configObject = {};// 加载环境变量if (options.env !== false) {this.loadEnv();}// 加载配置文件if (options.files !== false) {this.loadConfigFiles();}}loadEnv() {// 加载 .env 文件try {const envFile = path.join(this.configPath, '.env');const envContent = fs.readFileSync(envFile, 'utf8');envContent.split('\n').forEach(line => {if (line.trim() && !line.startsWith('#')) {const [key, ...values] = line.split('=');const value = values.join('=').trim();process.env[key.trim()] = value;}});} catch (err) {// 如果 .env 文件不存在,则忽略if (err.code !== 'ENOENT') {console.error('Error loading .env file:', err);}}// 加载环境特定的 .env 文件,例如 .env.developmenttry {const envFile = path.join(this.configPath, `.env.${this.env}`);const envContent = fs.readFileSync(envFile, 'utf8');envContent.split('\n').forEach(line => {if (line.trim() && !line.startsWith('#')) {const [key, ...values] = line.split('=');const value = values.join('=').trim();process.env[key.trim()] = value;}});} catch (err) {// 忽略错误}}loadConfigFiles() {// 加载基本配置try {const configFile = path.join(this.configPath, 'config', 'config.json');const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));this.configObject = { ...this.configObject, ...config };} catch (err) {// 忽略错误}// 加载环境特定的配置try {const configFile = path.join(this.configPath, 'config', `${this.env}.json`);const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));this.configObject = { ...this.configObject, ...config };} catch (err) {// 忽略错误}}get(key, defaultValue) {// 首先检查环境变量if (process.env[key]) {return process.env[key];}// 然后检查配置对象const parts = key.split('.');let value = this.configObject;for (const part of parts) {if (value == null) {return defaultValue;}value = value[part];}return value !== undefined ? value : defaultValue;}set(key, value) {const parts = key.split('.');let obj = this.configObject;for (let i = 0; i < parts.length - 1; i++) {const part = parts[i];if (!obj[part] || typeof obj[part] !== 'object') {obj[part] = {};}obj = obj[part];}obj[parts[parts.length - 1]] = value;return this;}
}module.exports = Config;
将配置工具集成到应用中:
// 在 lib/application.js 中添加const Config = require('./config');// 在 Application 类的构造函数中添加
constructor() {super();this.middleware = [];this.settings = {};this.engines = {};this.cache = {};this.config = new Config();// 从配置中加载设置this.loadConfigSettings();
}loadConfigSettings() {// 可以从配置中加载默认设置this.set('env', this.config.get('NODE_ENV') || 'development');this.set('port', this.config.get('PORT') || 3000);this.set('views', this.config.get('views') || path.join(process.cwd(), 'views'));this.set('view engine', this.config.get('viewEngine'));this.set('view cache', this.config.get('viewCache', this.get('env') === 'production'));
}
10. 完整项目结构
创建完整的框架入口文件:
touch index.js lib/index.js
在 lib/index.js
中导出所有框架组件:
// lib/index.js
const Application = require('./application');
const Router = require('./router');
const bodyParser = require('./middleware/body-parser');
const serveStatic = require('./middleware/static');
const View = require('./view');
const Config = require('./config');function createApplication() {const app = new Application();return app;
}// 导出框架组件
module.exports = Object.assign(createApplication, {Application,Router,bodyParser,static: serveStatic,View,Config
});
在根目录的 index.js
中重新导出框架:
// index.js
module.exports = require('./lib');
最终的项目结构应该如下:
my-nodejs-framework/
├── index.js # 主入口文件
├── lib/ # 核心库
│ ├── application.js # 应用核心类
│ ├── config.js # 配置管理
│ ├── index.js # 库入口
│ ├── middleware/ # 中间件
│ │ ├── body-parser.js # 请求体解析
│ │ └── static.js # 静态文件服务
│ ├── router.js # 路由系统
│ └── view.js # 视图引擎支持
├── package.json # 项目配置
├── README.md # 文档
└── test/ # 测试文件
11. 使用示例
创建一个使用你的框架的示例应用:
mkdir -p examples/basic-app
cd examples/basic-app
mkdir -p public views
touch app.js
编写示例应用:
// examples/basic-app/app.js
const myFramework = require('../../index');
const path = require('path');const app = myFramework();
const router = myFramework.Router();// 配置设置
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');// 注册模板引擎
app.engine('ejs', require('ejs').__express);// 使用中间件
app.use(myFramework.bodyParser());
app.use(myFramework.static(path.join(__dirname, 'public')));// 路由处理
router.get('/', (req, res) => {res.render('index', { title: 'My Framework', message: 'Hello World!' });
});router.get('/json', (req, res) => {res.json({ message: 'This is JSON response' });
});router.get('/users/:id', (req, res) => {res.json({ userId: req.params.id });
});router.post('/users', (req, res) => {res.status(201).json({ message: 'User created', user: req.body });
});// 错误处理中间件
app.use((err, req, res, next) => {console.error(err.stack);res.status(500).send('Something broke!');
});// 使用路由
app.use(router);// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`Server running on port ${PORT}`);
});
创建一个简单的视图:
echo '<h1><%= title %></h1><p><%= message %></p>' > views/index.ejs
12. 下一步
恭喜!你已经从零开始构建了一个基本的 Node.js 框架。这个框架具备了路由、中间件、错误处理、静态文件服务和模板引擎等功能。下一步你可以考虑:
- 添加测试: 为框架的各个组件编写单元测试和集成测试。
- 添加更多中间件: 例如 CORS、压缩、安全头设置等。
- 支持 WebSockets: 添加实时通信功能。
- 日志系统: 实现一个可配置的日志记录系统。
- ORM 集成: 添加数据库访问层。
- 身份验证与授权: 实现用户认证和权限控制功能。
- 文档生成器: 为 API 路由自动生成文档。
- 集群支持: 添加对 Node.js 集群的支持,以提高性能。
- 性能优化: 进行代码性能分析和优化。
- 发布为 NPM 包: 准备并发布你的框架到 NPM。
基础知识详解
为了更好地理解我们构建的框架,下面是一些核心概念的详细解释:
中间件系统
中间件是 Node.js Web 框架的核心概念。在我们的框架中,中间件是一个接收 req
、res
和 next
参数的函数:
function myMiddleware(req, res, next) {// 对请求/响应进行处理next(); // 调用下一个中间件
}
中间件链通过 next()
函数连接起来,形成一个洋葱模型:
┌────────────────────────┐│ │┌──────┤ 中间件 1 (开始) ├──────┐│ │ │ ││ └────────────────────────┘ ││ ││ ┌────────────────────────┐ ││ │ │ ││ ┌────┤ 中间件 2 (开始) ├────┐ ││ │ │ │ │ ││ │ └────────────────────────┘ │ ││ │ │ ││ │ ┌────────────────────────┐ │ ││ │ │ │ │ ││ │ │ 路由处理 │ │ ││ │ │ │ │ ││ │ └────────────────────────┘ │ ││ │ │ ││ │ ┌────────────────────────┐ │ ││ │ │ │ │ ││ └────┤ 中间件 2 (结束) ├────┘ ││ │ │ ││ └────────────────────────┘ ││ ││ ┌────────────────────────┐ ││ │ │ │└──────┤ 中间件 1 (结束) ├──────┘│ │└────────────────────────┘
路由系统
路由系统负责将请求匹配到正确的处理程序。我们使用 path-to-regexp
库将路由路径转换为正则表达式,以支持参数化路由如 /users/:id
。
路由匹配流程:
- 获取请求的 HTTP 方法和 URL
- 在对应方法的路由列表中查找匹配的路由
- 如果找到匹配的路由,提取参数并执行处理函数
- 如果没有找到匹配的路由,继续下一个中间件
错误处理
错误处理是 Web 应用程序的重要部分。我们的框架支持两种错误处理方式:
- 同步错误: 通过 try/catch 捕获并传递给 next(err)
- 异步错误: 通过在异步操作的 catch 块中调用 next(err) 处理
错误处理中间件有四个参数:(err, req, res, next),框架会自动识别这种模式并在发生错误时调用它。
静态文件服务
静态文件服务器负责提供静态资源如 HTML、CSS、JavaScript、图片等。它的工作原理是:
- 检查请求的方法是否为 GET 或 HEAD
- 构造文件路径
- 检查文件是否存在
- 如果是目录,尝试提供索引文件
- 设置正确的 Content-Type 头
- 创建文件流并通过管道发送到响应
模板引擎
模板引擎允许生成动态 HTML。我们的框架实现了一个灵活的视图系统:
- 支持注册多种模板引擎
- 根据文件扩展名选择正确的引擎
- 支持模板缓存以提高性能
- 提供本地变量和全局变量传递给模板
示例扩展:RESTful API
下面是一个更完整的 RESTful API 示例:
const myFramework = require('../../index');
const path = require('path');const app = myFramework();
const router = myFramework.Router();// 中间件
app.use(myFramework.bodyParser());
app.use((req, res, next) => {console.log(`${req.method} ${req.url}`);next();
});// 模拟数据库
const users = [{ id: '1', name: 'Alice', email: 'alice@example.com' },{ id: '2', name: 'Bob', email: 'bob@example.com' }
];// RESTful 路由
router.get('/api/users', (req, res) => {res.json(users);
});router.get('/api/users/:id', (req, res, next) => {const user = users.find(u => u.id === req.params.id);if (!user) {const err = new Error('User not found');err.status = 404;return next(err);}res.json(user);
});router.post('/api/users', (req, res) => {const newUser = {id: String(users.length + 1),name: req.body.name,email: req.body.email};users.push(newUser);res.status(201).json(newUser);
});router.put('/api/users/:id', (req, res, next) => {const index = users.findIndex(u => u.id === req.params.id);if (index === -1) {const err = new Error('User not found');err.status = 404;return next(err);}users[index] = {id: req.params.id,name: req.body.name || users[index].name,email: req.body.email || users[index].email};res.json(users[index]);
});router.delete('/api/users/:id', (req, res, next) => {const index = users.findIndex(u => u.id === req.params.id);if (index === -1) {const err = new Error('User not found');err.status = 404;return next(err);}const deletedUser = users[index];users.splice(index, 1);res.json(deletedUser);
});// 错误处理
app.use((err, req, res, next) => {const statusCode = err.status || 500;const message = statusCode === 500 ? 'Internal Server Error' : err.message;console.error(err);res.status(statusCode).json({error: {message,status: statusCode}});
});// 未找到路由处理
app.use((req, res) => {res.status(404).json({error: {message: 'Not Found',status: 404}});
});// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {console.log(`API server running on port ${PORT}`);
});
高级优化方向
如果你想进一步扩展和优化你的框架,可以考虑以下方向:
1. 依赖注入系统
创建一个依赖注入容器,使控制器和服务更容易测试:
class Container {constructor() {this.services = new Map();}register(name, factory, singleton = true) {this.services.set(name, { factory, singleton, instance: null });return this;}get(name) {const service = this.services.get(name);if (!service) {throw new Error(`Service ${name} not found`);}if (service.singleton) {if (!service.instance) {service.instance = service.factory(this);}return service.instance;}return service.factory(this);}
}
2. 装饰器支持
添加对 TypeScript 装饰器的支持,使路由定义更加简洁:
@Controller('/api/users')
class UserController {@Get('/')index(req, res) {// 获取所有用户}@Get('/:id')show(req, res) {// 获取单个用户}@Post('/')create(req, res) {// 创建用户}
}
3. 插件系统
实现一个插件系统,允许轻松扩展框架功能:
class Application {// ... 其他方法plugin(pluginFunction) {pluginFunction(this);return this;}
}// 使用插件
app.plugin(require('my-framework-cache-plugin'));
4. 健康检查和监控
添加内置健康检查和监控功能:
app.use('/health', (req, res) => {const health = {uptime: process.uptime(),message: 'OK',timestamp: Date.now()};res.json(health);
});
5. GraphQL 支持
集成 GraphQL 作为 REST API 的替代方案:
const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');const schema = buildSchema(`type User {id: ID!name: String!email: String!}type Query {user(id: ID!): Userusers: [User]}
`);const rootValue = {user: ({ id }) => users.find(u => u.id === id),users: () => users
};app.use('/graphql', graphqlHTTP({schema,rootValue,graphiql: true
}));
性能优化技巧
为了让你的框架保持高性能,可以考虑以下优化:
1. 路由缓存
缓存编译后的路由正则表达式和参数:
class Router {constructor() {this.routes = { /* ... */ };this.compiledRoutes = new Map();}compileRoute(path) {if (this.compiledRoutes.has(path)) {return this.compiledRoutes.get(path);}const keys = [];const regexp = pathToRegexp(path, keys);const compiled = { regexp, keys };this.compiledRoutes.set(path, compiled);return compiled;}
}
2. 响应压缩
添加响应压缩以减少传输大小:
function compression(options = {}) {const { threshold = 1024 } = options;return (req, res, next) => {const originalEnd = res.end;const originalWrite = res.write;let body = [];// 检查客户端是否支持压缩const acceptEncoding = req.headers['accept-encoding'] || '';const supportsGzip = acceptEncoding.includes('gzip');if (!supportsGzip) {return next();}// 重写 write 和 end 方法res.write = function(chunk, encoding) {body.push(chunk);return true;};res.end = function(chunk, encoding) {if (chunk) {body.push(chunk);}const buffer = Buffer.concat(body);// 判断是否需要压缩if (buffer.length < threshold) {res.write = originalWrite;res.end = originalEnd;return originalEnd.call(this, buffer, encoding);}const zlib = require('zlib');zlib.gzip(buffer, (err, result) => {if (err) {res.write = originalWrite;res.end = originalEnd;return originalEnd.call(this, buffer, encoding);}res.setHeader('Content-Encoding', 'gzip');res.setHeader('Content-Length', result.length);originalEnd.call(this, result);});};next();};
}
3. HTTP/2 支持
添加对 HTTP/2 的支持以提高性能:
const http2 = require('http2');
const fs = require('fs');class Application {// ... 其他方法listenHttps(port, options) {const server = http2.createSecureServer({key: fs.readFileSync(options.key),cert: fs.readFileSync(options.cert),allowHTTP1: true}, this.callback());return server.listen(port);}
}
结语
通过本指南,你已经从零开始创建了一个功能完善的 Node.js Web 框架。这个框架虽然简单,但包含了所有核心功能:路由、中间件、错误处理、静态文件服务和模板引擎支持。
这个过程不仅帮助你理解了 Web 框架的内部工作原理,还为你提供了一个可以根据自己的需求定制和扩展的基础。无论是用于学习目的还是作为实际项目的基础,这个框架都是一个很好的起点。
随着你对框架的不断改进和扩展,它可能会发展成为一个适合特定用例的专门框架。祝你的 Node.js 开发之旅顺利!