从零到一搭建 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.development
try {
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!): User
users: [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 开发之旅顺利!