当前位置: 首页 > article >正文

nodejs构建项目

从零到一搭建 Node.js 框架

搭建一个 Node.js 框架是理解 Web 应用架构的绝佳方式。本指南将带您完成创建一个轻量级但功能完善的 Node.js 框架的全过程,类似于 Express 或 Koa,但规模更小,便于理解。

目录

  1. 项目初始化
  2. 创建核心应用类
  3. 路由系统
  4. 中间件系统
  5. 请求和响应对象增强
  6. 错误处理
  7. 静态文件服务
  8. 模板引擎支持
  9. 配置和环境变量
  10. 完整项目结构
  11. 使用示例
  12. 下一步

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 框架。这个框架具备了路由、中间件、错误处理、静态文件服务和模板引擎等功能。下一步你可以考虑:

  1. 添加测试: 为框架的各个组件编写单元测试和集成测试。
  2. 添加更多中间件: 例如 CORS、压缩、安全头设置等。
  3. 支持 WebSockets: 添加实时通信功能。
  4. 日志系统: 实现一个可配置的日志记录系统。
  5. ORM 集成: 添加数据库访问层。
  6. 身份验证与授权: 实现用户认证和权限控制功能。
  7. 文档生成器: 为 API 路由自动生成文档。
  8. 集群支持: 添加对 Node.js 集群的支持,以提高性能。
  9. 性能优化: 进行代码性能分析和优化。
  10. 发布为 NPM 包: 准备并发布你的框架到 NPM。

基础知识详解

为了更好地理解我们构建的框架,下面是一些核心概念的详细解释:

中间件系统

中间件是 Node.js Web 框架的核心概念。在我们的框架中,中间件是一个接收 reqresnext 参数的函数:

function myMiddleware(req, res, next) {// 对请求/响应进行处理next(); // 调用下一个中间件
}

中间件链通过 next() 函数连接起来,形成一个洋葱模型:

        ┌────────────────────────┐│                        │┌──────┤  中间件 1 (开始)       ├──────┐│      │                        │      ││      └────────────────────────┘      ││                                      ││      ┌────────────────────────┐      ││      │                        │      ││ ┌────┤  中间件 2 (开始)       ├────┐ ││ │    │                        │    │ ││ │    └────────────────────────┘    │ ││ │                                  │ ││ │    ┌────────────────────────┐    │ ││ │    │                        │    │ ││ │    │      路由处理          │    │ ││ │    │                        │    │ ││ │    └────────────────────────┘    │ ││ │                                  │ ││ │    ┌────────────────────────┐    │ ││ │    │                        │    │ ││ └────┤  中间件 2 (结束)       ├────┘ ││      │                        │      ││      └────────────────────────┘      ││                                      ││      ┌────────────────────────┐      ││      │                        │      │└──────┤  中间件 1 (结束)       ├──────┘│                        │└────────────────────────┘

路由系统

路由系统负责将请求匹配到正确的处理程序。我们使用 path-to-regexp 库将路由路径转换为正则表达式,以支持参数化路由如 /users/:id

路由匹配流程:

  1. 获取请求的 HTTP 方法和 URL
  2. 在对应方法的路由列表中查找匹配的路由
  3. 如果找到匹配的路由,提取参数并执行处理函数
  4. 如果没有找到匹配的路由,继续下一个中间件

错误处理

错误处理是 Web 应用程序的重要部分。我们的框架支持两种错误处理方式:

  1. 同步错误: 通过 try/catch 捕获并传递给 next(err)
  2. 异步错误: 通过在异步操作的 catch 块中调用 next(err) 处理

错误处理中间件有四个参数:(err, req, res, next),框架会自动识别这种模式并在发生错误时调用它。

静态文件服务

静态文件服务器负责提供静态资源如 HTML、CSS、JavaScript、图片等。它的工作原理是:

  1. 检查请求的方法是否为 GET 或 HEAD
  2. 构造文件路径
  3. 检查文件是否存在
  4. 如果是目录,尝试提供索引文件
  5. 设置正确的 Content-Type 头
  6. 创建文件流并通过管道发送到响应

模板引擎

模板引擎允许生成动态 HTML。我们的框架实现了一个灵活的视图系统:

  1. 支持注册多种模板引擎
  2. 根据文件扩展名选择正确的引擎
  3. 支持模板缓存以提高性能
  4. 提供本地变量和全局变量传递给模板

示例扩展: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 开发之旅顺利!

相关文章:

nodejs构建项目

从零到一搭建 Node.js 框架 搭建一个 Node.js 框架是理解 Web 应用架构的绝佳方式。本指南将带您完成创建一个轻量级但功能完善的 Node.js 框架的全过程&#xff0c;类似于 Express 或 Koa&#xff0c;但规模更小&#xff0c;便于理解。 目录 项目初始化创建核心应用类路由系…...

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义菜单项。​掘金…...

Cygwin编译安装Acise

本文记录Windows下使用Cygwin编译安装Acise的流程。 零、环境 操作系统Windows11Visual Studio CodeVisual Studio Code 1.92.0Cygwin 一、工具及依赖 1.1 Visual Studio Code 下载并安装Visual Studio Code, 同时安装以下插件&#xff0c; Task Explorer Output Colorizer …...

基于STM32、HAL库的IP6525S快充协议芯片简介及驱动程序设计

一、简介: IP6525S是一款高性能的同步降压DC-DC转换器芯片,具有以下特点: 输入电压范围:4.5V至32V 输出电压范围:0.8V至30V 最大输出电流:5A 效率高达95% 可编程开关频率(100kHz-1MHz) 支持PWM和PFM模式 内置过流保护、过温保护等功能 该芯片常用于工业控制、通信设备…...

RabbitMQ惰性队列的工作原理、消息持久化机制、同步刷盘的概念、延迟插件的使用方法

惰性队列工作原理 惰性队列通过尽可能多地将消息存储到磁盘上来减少内存的使用。与传统队列相比&#xff0c;惰性队列不会主动将消息加载到内存中&#xff0c;而是尽量让消息停留在磁盘上&#xff0c;从而降低内存占用。尽管如此&#xff0c;它并不保证所有操作都是同步写入磁…...

MySQL与Oracle深度对比

MySQL与Oracle深度对比&#xff1a;数据类型与SQL差异 一、数据类型差异 1. 数值类型对比 数据类型MySQLOracle整数TINYINT, SMALLINT, MEDIUMINT, INT, BIGINTNUMBER(精度) 或直接INT(内部仍为NUMBER)小数DECIMAL(p,s), FLOAT, DOUBLENUMBER(p,s), FLOAT, BINARY_FLOAT, BI…...

【Leetcode 每日一题】1922. 统计好数字的数目

问题背景 我们称一个数字字符串是 好数字 当它满足&#xff08;下标从 0 0 0 开始&#xff09;偶数 下标处的数字为 偶数 且 奇数 下标处的数字为 质数 ( 2 , 3 , 5 (2, \ 3, \ 5 (2, 3, 5 或 7 ) 7) 7)。 比方说&#xff0c;“2582” 是好数字&#xff0c;因为偶数下标处…...

pyqtgraph.opengl.items.GLSurfacePlotItem.GLSurfacePlotItem 报了一个错

1. 需求是这个样子的 有一个 pyqtgraph.opengl.GLViewWidget &#xff0c;在应用启动时存在QMainWindow中&#xff0c;即父对象是QMainWindow&#xff0c;当业务需要时&#xff0c;修改它的父对象变为一个QDialog&#xff0c;可以让它从QMainWindow中弹出显示在QDialog里&#…...

【C++初学】课后作业汇总复习(六) 函数模板

1、函数模板 思考&#xff1a;如果重载的函数&#xff0c;其解决问题的逻辑是一致的、函数体语句相同&#xff0c;只是处理的数据类型不同&#xff0c;那么写多个相同的函数体&#xff0c;是重复劳动&#xff0c;而且还可能因为代码的冗余造成不一致性。 解决&#xff1a;使用…...

【第16届蓝桥杯C++C组】--- 数位倍数

Hello呀&#xff0c;小伙伴们&#xff0c;第16届蓝桥杯也完美结束了&#xff0c;无论大家考的如何&#xff0c;都要放平心态&#xff0c;今年我刚上大一&#xff0c;也第一次参加蓝桥杯&#xff0c;刷的算法题也只有200来道&#xff0c;但是还是考的不咋滴&#xff0c;但是拿不…...

ASP.NET Core 性能优化:客户端响应缓存

文章目录 前言一、什么是缓存二、客户端缓存核心机制&#xff1a;HTTP缓存头1&#xff09;使用[ResponseCache]属性&#xff08;推荐&#xff09;2&#xff09;预定义缓存配置&#xff08;CacheProfile&#xff09;3&#xff09;手动设置HTTP头4&#xff09;缓存验证机制&#…...

Numpy和OpenCV库匹配查询,安装OpenCV ABI错误

文章目录 地址opencv-python&#xff1a;4.x版本的对应numpyopencv-python&#xff1a;5.x版本的对应numpy方法2 ps&#xff1a;装个opencv遇到ABI错误无语了&#xff0c;翻了官网&#xff0c;github文档啥都没&#xff0c;记录下 地址 opencv-python&#xff1a;4.x版本的对应…...

全球变暖(蓝桥杯 2018 年第九届省赛)

题目描述 你有一张某海域 NN 像素的照片&#xff0c;. 表示海洋、 # 表示陆地&#xff0c;如下所示&#xff1a; ....... .##.... .##.... ....##. ..####. ...###. .......其中 "上下左右" 四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。 由…...

ubuntu18.04安装miniforge3

1.下载安装文件 略&#xff08;注&#xff1a;从同事哪里拖来的安装包&#xff09; 2.修改安装文件权限 chmod x Miniforge3-Linux-x86_64.sh 3.将它安装到指定位置 micromamba activate /home/xxx/fxp/fromDukto/miniforge3 4.激活 /home/xxx/fxp/fromDukto/miniforge3…...

高并发短信系统设计:基于SharingJDBC的分库分表、大数据同步与实时计算方案

高并发短信系统设计&#xff1a;基于SharingJDBC的分库分表、大数据同步与实时计算方案 一、概述 在当今互联网应用中&#xff0c;短信服务是极为重要的一环。面对每天发送2000万条短信的需求&#xff0c;我们需要一个能够处理海量数据&#xff08;一年下来达到数千万亿级别&…...

OceanBase企业版集群部署:oatcli命令行方式

OceanBase企业版集群部署&#xff1a;oatcli命令行方式 安装包准备服务器准备最低资源配置是否部署ODP组件&#xff1f;仲裁服务器 服务器配置操作系统内核参数BIOS设置磁盘挂载网卡设置 安装OAT部署工具初始化OBServer服务器使用oatcli部署三副本集群安装OceanBase软件初始化O…...

SQL 查询中涉及的表及其作用说明

SQL 查询中涉及的表及其作用说明&#xff1a; 涉及的数据库表 表名别名/用途关联关系dbo.s_orderSO&#xff08;主表&#xff09;存储订单主信息&#xff08;订单号、日期、客户等&#xff09;dbo.s_orderdetailSoD&#xff08;订单明细&#xff09;通过 billid SO.billid 关…...

智能手机功耗测试

随着智能手机发展,用户体验对手机的续航功耗要求越来越高。需要对手机进行功耗测试及分解优化,将手机的性能与功耗平衡。低功耗技术推动了手机的用户体验。手机功耗测试可以采用powermonitor或者NI仪表在功耗版上进行测试与优化。作为一个多功能的智能终端,手机的功耗组成极…...

UNIX域套接字(Unix Domain Sockets, UDS) 的两种接口

目录 1. 流式套接字&#xff08;SOCK_STREAM&#xff09;特点类比典型使用场景代码示例&#xff08;伪代码&#xff09; 2. 数据报套接字&#xff08;SOCK_DGRAM&#xff09;特点类比典型使用场景代码示例&#xff08;伪代码&#xff09; 3. 两者的核心区别对比4. 为什么 UNIX …...

使用U盘安装 ubuntu 系统

1. 准备U 盘制作镜像 1.1 下载 ubuntu iso https://ubuntu.com/download/ 这里有多个版本以供下载&#xff0c;本文选择桌面版。 1.2 下载rufus https://rufus.ie/downloads/ 1.3 以管理员身份运行 rufus 设备选择你用来制作启动项的U盘&#xff0c;不能选错了&#xff1b;点…...

安全厂商安全理念分析

奇安信&#xff08;toB企业安全&#xff09; 安全理念&#xff1a;率先提出 “内生安全” 理念。即把安全能力内置到信息化环境中&#xff0c;通过信息化系统和安全系统的聚合、业务数据和安全数据的聚合、IT 人才和安全人才的聚合&#xff0c;让安全系统像人的免疫系统一样&a…...

Redis如何判断哨兵模式下节点之间数据是否一致

在哨兵模式下判断两个Redis节点的数据一致性&#xff0c;可以通过以下几种方法实现&#xff1a; 一、检查主从复制偏移量 使用INFO replication命令 分别在主节点和从节点执行该命令&#xff0c;比较两者的master_repl_offset&#xff08;主节点&#xff09;和slave_repl_offs…...

HarmonyOS-ArkUI V2装饰器: @Provider和@Consumer装饰器:跨组件层级双向同步

作用 我们在之前学习的那些控件中,各有特点,也各有缺陷,至今没有痛痛快快的出现过真正能跨组件的双向绑定的装饰器。 比如 @Local装饰器,不能跨组件@Param装饰器呢,能跨组件传递,但是仅仅就是下一层组件接收参数。另外,它是单向传递,不可被重新赋值。如果您非要改值则…...

oracle 并行度(Parallel Degree)

在Oracle数据库中&#xff0c;并行度&#xff08;Parallel Degree&#xff09; 是用于控制并行处理任务的关键配置&#xff0c;旨在通过多进程协作加速大规模数据处 一、并行度的核心概念 并行度&#xff08;DOP, Degree of Parallelism&#xff09; 表示一个操作同时使用的并…...

Redis-场景缓存+秒杀+管道+消息队列

缓存一致性 1.两次更新 先更新数据库&#xff0c;再更新缓存&#xff1b;先更新缓存&#xff0c;再更新数据库&#xff1b; 出现不一致问题场景&#xff1a; 先更新数据库&#xff0c;再更新缓存&#xff1b; 先更新缓存&#xff0c;再更新数据库&#xff1b; 两次更新的适…...

系统的安全及应用

仓库做了哪些优化 仓库源换成国内源不使用root用户登录将不必要的开机启动项关闭内核的调优 系统做了哪些安全加固 禁止使用root禁止使用弱命令将常见的 远程连接端口换掉 系统安全及应用 Cpu负载高 java程序 运行异常中病毒&#xff1f; ps aux - - sort %cpu %mem Cpu …...

PostgreSQL内幕探索—基础知识

PostgreSQL内幕探索—基础知识 PostgreSQL&#xff08;以下简称PG&#xff09; 起源于 1986 年加州大学伯克利分校的 ‌POSTGRES 项目‌&#xff0c;最初以对象关系模型为核心&#xff0c;支持高级数据类型和复杂查询功能‌。 1996 年更名为 PostgreSQL 并开源&#xff0c;逐…...

基于redis 实现我的收藏功能优化详细设计方案

基于redis 实现我的收藏功能优化详细设计方案 一、架构设计 +---------------------+ +---------------------+ | 客户端请求 | | 数据存储层 | | (收藏列表查询) | | (Redis Cluster) | +-------------------…...

WPS复制粘贴错误 ,文件未找到 mathpage.wll

文章目录 1.错误提示图片2.解决方案1.找到MathType.wll文件和MathType Commands 2016.dotm文件并复制2.找到wps安装地址并拷贝上述两个文件到指定目录 3.重启WPS 1.错误提示图片 2.解决方案 1.找到MathType.wll文件和MathType Commands 2016.dotm文件并复制 MathType.wll地址如…...

驱动开发硬核特训 · Day 6 : 深入解析设备模型的数据流与匹配机制 —— 以 i.MX8M 与树莓派为例的实战对比

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 从静态绑定驱动模型到现代设备模型 主题&#xff1a;深入解析设备模型的数据流与匹配机制 —— 以 i.MX8M 与树莓派为例的实战对比 在上一节中&#xff0c;我们从驱动框架的历史演进出…...