一、自研Mini-Express框架:开发动机与定位
1.1 为什么要手写复刻Express?
-
破除黑盒依赖:日常业务直接引入Express,不懂请求分发、洋葱中间件、路由匹配底层源码,线上报错无法溯源底层问题
-
补齐面试核心考点:Node高频面试:Express中间件执行顺序、Koa洋葱模型、动态路由params原理、req.body解析原理,手写一遍即可吃透全部考点
-
拔高简历含金量:区别于普通业务CRUD,自研开源npm包、配套文档、单元测试、CI自动化,体现架构封装、工程化能力
-
业务定制能力:可剔除Express冗余API,内置CORS、日志、错误捕获、数据库挂载,适配本系列大屏、认证、博客专属业务
1.2 自研框架定位(Mini-Express)
-
兼容Express基础API:app.use()、app.get()、app.post()、res.json()、res.send()完全对齐写法
-
核心能力全覆盖:异步洋葱中间件、动态路由、body/query/params解析、静态资源托管、全局错误中间件
-
轻量化体积:无第三方依赖,原生Node API开发,打包体积<20kb
-
兼容生态:无缝对接Mongoose、Socket.io、JWT鉴权,复用本系列往期所有业务代码
1.3 技术前置基础
原生http模块、Stream流、URL解析、正则表达式、异步Promise、洋葱模型执行机制,无需底层高深算法。
二、核心模块从零手写实现(全量可运行源码)
项目目录结构(npm包标准目录)
mini-express/
├── lib/
│ ├──application.js # 框架核心入口类
│ ├──route.js # 路由封装类
│ ├──request.js # 扩展原生req对象
│ ├──response.js # 扩展原生res对象
│ └──static.js # 静态文件服务模块
├── test/ # Jest单元测试
├── index.js # 包入口文件
├── package.json
└── README.md
2.1 扩展原生Request、Response对象
lib/request.js 拓展请求参数
// 拓展原生http.IncomingMessage,挂载query、params、body
const { URL } = require('url')
class MiniRequest {
constructor(req) {
this.req = req
// 挂载原生url、method
this.url = req.url
this.method = req.method.toLowerCase()
// 解析query参数
const urlObj = new URL(req.url, `http://${req.headers.host}`)
this.query = Object.fromEntries(urlObj.searchParams)
this.path = urlObj.pathname
// 路由动态参数,后续路由匹配赋值
this.params = {}
}
}
module.exports = MiniRequest
lib/response.js 拓展响应方法
// 拓展原生http.ServerResponse,封装常用响应API
class MiniResponse {
constructor(res) {
this.res = res
this.statusCode = 200
}
// 设置状态码
status(code) {
this.statusCode = code
return this
}
// 文本响应
send(data) {
if (typeof data === 'object') return this.json(data)
this.res.writeHead(this.statusCode, {
'Content-Type': 'text/html;charset=utf-8'
})
this.res.end(data)
}
// JSON响应(业务最常用)
json(data) {
this.res.writeHead(this.statusCode, {
'Content-Type': 'application/json;charset=utf-8'
})
this.res.end(JSON.stringify(data))
}
}
module.exports = MiniResponse
2.2 路由核心模块:动态路由+RESTful方法
核心能力:存储路由表、正则匹配动态路由/user/:id、提取params参数、分发路由handler
// lib/route.js 路由管理类
const pathToRegexp = require('path-to-regexp') // 路由正则匹配,轻量化工具
class Route {
constructor() {
// 路由表:{method: [{path,regexp,keys,handler}]}
this.routes = {
get: [], post: [], put: [], delete: [], patch: []
}
}
// 注册RESTful方法
addMethod(method, path, handler) {
const keys = []
// 将动态路由转为正则:/user/:id => /^\/user\/([^\/]+)/
const regexp = pathToRegexp(path, keys)
this.routes[method].push({
path, regexp, keys, handler
})
}
// 快捷绑定get/post/put/delete
get(path, handler) { this.addMethod('get', path, handler) }
post(path, handler) { this.addMethod('post', path, handler) }
put(path, handler) { this.addMethod('put', path, handler) }
delete(path, handler) { this.addMethod('delete', path, handler) }
// 匹配路由,赋值req.params
match(method, path) {
const targetRoutes = this.routes[method]
for (const item of targetRoutes) {
const result = item.regexp.exec(path)
if (result) {
// 提取动态参数
const params = {}
item.keys.forEach((key, index) => {
params[key.name] = result[index + 1]
})
return { handler: item.handler, params }
}
}
return null
}
}
module.exports = Route
2.3 核心Application类:洋葱异步中间件机制(重点)
实现核心:app.use()、中间件数组、异步next()洋葱执行、body解析、请求分发、全局错误捕获,复刻Koa+Express混合洋葱模型
// lib/application.js 框架核心入口
const http = require('http')
const querystring = require('querystring')
const MiniRequest = require('./request')
const MiniResponse = require('./response')
const Route = require('./route')
const fs = require('fs')
const path = require('path')
class MiniExpress {
constructor() {
// 全局中间件数组
this.middlewares = []
// 路由实例
this.router = new Route()
// 内置body解析中间件开关
this.isParseJson = false
this.isParseForm = false
}
// 1. 注册中间件 use方法
use(path, fn) {
// 兼容不传路径,全局中间件
if (typeof path === 'function') {
fn = path
path = '/'
}
this.middlewares.push({ path, handler: fn })
}
// 快捷开启body解析中间件
json() {
this.isParseJson = true
}
urlencoded() {
this.isParseForm = true
}
// 挂载路由方法代理
get(...args) { this.router.get(...args) }
post(...args) { this.router.post(...args) }
put(...args) { this.router.put(...args) }
delete(...args) { this.router.delete(...args) }
// 2. 洋葱模型:递归执行中间件 + next异步调度
async composeMiddlewares(req, res, index = 0) {
try {
// 遍历匹配路径中间件
const middleware = this.middlewares[index]
if (!middleware) return this.handleRouter(req, res)
// 路径匹配才执行中间件
if (req.path.startsWith(middleware.path)) {
await middleware.handler(req, res, () => {
return this.composeMiddlewares(req, res, index + 1)
})
} else {
return this.composeMiddlewares(req, res, index + 1)
}
} catch (err) {
// 全局统一错误捕获
res.status(500).json({ code: 500, msg: '服务异常', error: err.message })
}
}
// 3. 解析req.body 表单/JSON请求体
parseBody(req) {
return new Promise((resolve) => {
let chunks = []
req.req.on('data', buf => chunks.push(buf))
req.req.on('end', () => {
const data = Buffer.concat(chunks).toString()
const contentType = req.req.headers['content-type']
if (this.isParseJson && contentType.includes('application/json')) {
req.body = data ? JSON.parse(data) : {}
}
if (this.isParseForm && contentType.includes('application/x-www-form-urlencoded')) {
req.body = querystring.parse(data)
}
resolve()
})
})
}
// 4. 路由分发处理
handleRouter(req, res) {
const matchResult = this.router.match(req.method, req.path)
if (matchResult) {
req.params = matchResult.params
return matchResult.handler(req, res)
}
// 路由404兜底
res.status(404).json({ code: 404, msg: '接口不存在' })
}
// 5. 静态文件托管服务
static(dir) {
this.use(async (req, res, next) => {
// 仅匹配静态资源后缀
const staticReg = /\.(png|jpg|css|js|html|ico)$/
if (!staticReg.test(req.path)) return next()
const filePath = path.join(dir, req.path)
fs.readFile(filePath, (err, data) => {
if (err) return next()
res.send(data)
})
})
}
// 6. 服务监听启动
listen(port, callback) {
const server = http.createServer(async (rawReq, rawRes) => {
// 包装自定义req/res
const req = new MiniRequest(rawReq)
const res = new MiniResponse(rawRes)
// 解析请求体
await this.parseBody(req)
// 执行洋葱中间件链路
await this.composeMiddlewares(req, res)
})
server.listen(port, callback)
}
}
// 工厂函数,对齐express()调用方式
function createMiniExpress() {
return new MiniExpress()
}
module.exports = createMiniExpress
2.4 包入口文件 index.js
module.exports = require('./lib/application')
三、实战:自研框架搭建全栈博客CRUD API
完全复刻Express业务写法,零学习成本,接入Mongoose数据库,实现完整博客接口,适配前文鉴权体系
3.1 业务项目初始化
# 业务项目依赖
npm install ../mini-express mongoose cors
3.2 数据库Schema设计
// db/index.js
const mongoose = require('mongoose')
mongoose.connect('mongodb://127.0.0.1:27017/mini_blog')
const PostSchema = new mongoose.Schema({
title: String,
content: String,
author: String,
createTime: { type: Date, default: Date.now }
})
module.exports = mongoose.model('Post', PostSchema)
3.3 业务入口app.js(和Express写法1:1一致)
// app.js 自研框架业务实战
const miniExpress = require('mini-express')
const app = miniExpress()
const Post = require('./db')
// 1. 内置中间件:解析JSON、表单body
app.json()
app.urlencoded()
// 2. 自定义全局日志中间件
app.use((req, res, next) => {
console.log(`[${new Date()}] ${req.method} ${req.path}`)
next()
})
// 3. 全局CORS跨域中间件
app.use((req, res, next) => {
res.res.setHeader('Access-Control-Allow-Origin', '*')
res.res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE')
next()
})
// 4. 博客RESTful CRUD路由
// 新增文章
app.post('/posts', async (req, res) => {
const post = await Post.create(req.body)
res.json({ code: 200, data: post, msg: '发布成功' })
})
// 获取全部文章
app.get('/posts', async (req, res) => {
const list = await Post.find()
res.json({ code: 200, data: list })
})
// 获取单篇动态文章 :id
app.get('/posts/:id', async (req, res) => {
// 直接获取路由动态参数 params.id
const detail = await Post.findById(req.params.id)
res.json({ code: 200, data: detail })
})
// 托管静态博客首页资源
app.static('./public')
// 启动服务
app.listen(3000, () => {
console.log('MiniExpress博客服务启动:http://localhost:3000')
})
3.4 核心能力验证
-
洋葱中间件顺序:日志→跨域→路由,next()逐层放行,异常统一500捕获
-
自动解析:req.body、req.query、req.params全自动挂载,无需额外中间件
-
容错兜底:无路由404、代码报错500,统一格式返回
四、工程化:单元测试 + npm发布 + GitHub CI自动化
4.1 Jest单元测试编写
测试路由、中间件、参数解析三大核心能力,保障框架迭代稳定性
// test/app.test.js
const request = require('supertest')
const miniExpress = require('../index')
const app = miniExpress()
app.get('/test/:id', (req, res) => {
res.json({ id: req.params.id })
})
test('动态路由参数解析', async () => {
const res = await request(app).get('/test/1001')
expect(res.body.id).toBe('1001')
})
4.2 package.json 发包配置
{
"name": "mini-express-dev",
"version": "1.0.0",
"main": "index.js",
"description": "从零手写轻量化Express复刻框架",
"author": "全栈开发者",
"license": "MIT",
"scripts": {
"test": "jest",
"build": "npm run test"
},
"dependencies": {
"path-to-regexp": "^6.2.1"
},
"devDependencies": {
"jest": "^29.7.0",
"supertest": "^6.3.4"
}
}
4.3 npm发包完整流程
-
注册npm官网账号,本地登录:
npm login -
校验包名唯一性:
npm view mini-express-dev -
执行打包发布:
npm publish --access=public -
业务项目直接安装使用:
npm install mini-express-dev
4.4 GitHub Actions自动CI配置
推送代码自动执行单元测试、版本检测、自动发包,新建.github/workflows/deploy.yml
name: Npm Publish
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18
registry-url: https://registry.npmjs.org
- run: npm install
- run: npm test
- name: Publish Npm
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: npm publish
五、框架对比复盘 + 能力收获总结
5.1 MiniExpress VS Express VS Koa 差距对比
|
能力项 |
自研MiniExpress |
Express |
Koa2 |
|---|---|---|---|
|
异步洋葱中间件 |
✅ 完整实现 |
❌ 线性中间件 |
✅ 原生洋葱模型 |
|
动态路由params |
✅ 支持 |
✅ 支持 |
✅ 支持 |
|
原生容错边界 |
简易兜底,待优化 |
生产级完善 |
生产级完善 |
|
路由分层拆分 |
暂不支持子路由 |
支持Router子路由 |
支持子路由 |
|
性能限流、缓存 |
无内置能力 |
需第三方中间件 |
需第三方中间件 |
5.2 生产落地补充优化方向
-
新增Router子路由拆分,适配大型项目路由模块化
-
新增请求限流、超时销毁、大文件流解析,规避内存溢出
-
完善错误分级:业务错误、服务错误、客户端错误分类码值
-
内置JWT、Redis全局挂载,无缝对接本系列认证、大屏项目
5.3 本次实战核心收获
-
彻底弄懂:洋葱模型执行顺序、next()异步递归调度底层原理,面试可手写中间件执行流程
-
彻底弄懂:动态路由正则匹配、url参数拆分、http请求体二进制解析原理
-
掌握npm包开发、版本管理、单元测试、GitHub CI自动化完整工程化流程
-
打通全栈闭环:自定义框架→数据库CRUD→接口业务→发包上线,完整全栈链路
412



被折叠的 条评论
为什么被折叠?



