NodeJS常用框架Express/Koa/NestJS
Node.js 作为一个高性能的 JavaScript 运行时环境,广泛应用于构建服务器端应用程序。为了提高开发效率,开发者通常会使用一些框架来简化工作。在众多框架中,Express、Koa 和 NestJS 是最常用的三种。本文将对这三种框架进行介绍和比较
小贴士
文章中涉及到的代码示例你都可以从 这里查看 ,若对你有用还望点赞支持
原生开发劣势
虽然原生 Node.js 提供了一个功能强大的底层 API,能够支持高性能的服务器端开发,但在实际开发中,直接使用原生 Node.js 通常会面临以下劣势。这些劣势不仅影响开发效率,也对代码维护和扩展性提出了更高的挑战
开发效率低下
:原生 Node.js 提供的是较底层的 API,例如 http.createServer() 方法来创建 HTTP 服务器。但是这些 API 通常只处理非常基本的功能,例如处理请求和响应,而对于实际项目中经常遇到的需求(如路由管理、解析请求体、静态文件托管等),需要开发者自己实现
const http = require("http");
const server = http.createServer((req, res) => {
// 逻辑混乱 etc..
if (req.url === "/" && req.method === "GET") {
res.end("Home Page");
} else if (req.url === "/about" && req.method === "GET") {
res.end("About Page");
} else {
res.statusCode = 404;
res.end("Not Found");
}
});
server.listen(3000);
2
3
4
5
6
7
8
9
10
11
12
13
对于复杂业务而言上面会变得非常难维护
代码冗长且难以维护
:原生 Node.js 缺乏结构化的代码组织方式,项目代码容易变得混乱、重复,随着需求的增加,维护成本也会迅速上升缺乏标准化和约束
:原生 Node.js 提供了极大的灵活性,但缺乏统一的规范。这种“无约束”的开发方式容易导致团队协作问题扩展性差
:构建通用的扩展功能(如认证、权限管理、跨域处理等)变得非常繁琐
NodeJS框架之Express
Express 是最流行的 Node.js 框架之一,由 TJ Holowaychuk 创建。它是一个简洁且灵活的框架,提供了一组强大的功能,用于构建单页、多页和混合的 Web 应用
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3001, () => {
console.log('Example app listening on port 3001!');
});
2
3
4
5
6
7
8
9
10
11
中间件
中间件是express的最重要特点,相信所有使用过它的开发者都会感受到它强大的优势
试想一下如果没有中间件机制,我们的业务代码必定会有一大坨if/else
模块或者重复冗余的代码块。而中间件将复杂的请求处理逻辑分层,使代码清晰、易于管理;独立实现为功能模块,可以被多个路由和应用复用,将功能模块解耦,使代码易于维护
在会开发使用的同时,搞懂中间件原理机制也会提升我们的编程功底,其实它的实现原理很简单,可以总结为:中间件就是一个按注册顺序执行的流水线,每个作业控制着下一个环节
中间件分类
按照角色的意义可以划分为3大类:应用级
、路由级
、错误级
- 应用级别的中间件
app.get("/", (req, res, next) => {
console.log("router middleware1");
next();
});
2
3
4
- 路由级别的中间件
app.get('/', (req, res, next) => {
console.log("router middleware1");
next();
});
app.get('/', (req, res) => {
console.log("router middleware2");
res.send('Hello World!');
});
2
3
4
5
6
7
8
9
- 错误级别的中间件
app.use((err, req, res, next) => {
console.error('错误捕获:', err);
})
2
3
访问以上的服务查看打印的日志:
中间件原理
Express 的核心代码主要依赖于以下几个关键概念:
- 中间件队列:存储所有中间件的队列,
通过队列形式执行
- 请求-响应对象共享:中间件通过 req 和 res 对象共享请求状态
- next() 方法:控制中间件的执行顺序
- 中间件注册:
app.use
:当调用app.use()
时,Express 会将中间件函数存储到一个队列中,队列中按注册顺序依次存储中间件
app.use = function use(fn) {
this._router.stack.push({
route: undefined, // 表示全局中间件
handle: fn // 中间件函数
});
};
2
3
4
5
6
使用use方法时内部实际走的是
router.use
方法,将当前中间件函数转换成一个Layer
实例,最后将其放入一个队列中
- 中间件执行:
next
:中间件的核心是next()
函数,它允许当前中间件执行完成后调用下一个中间件。如果没有调用next()
,则中间件链会中断。下面是一段伪代码
function processMiddleware(req, res, out) {
let idx = 0; // 当前中间件索引
function next(err) {
if (idx >= stack.length) return out(err); // 执行结束或出错
const layer = stack[idx++];
if (err) {
// 如果有错误,跳过普通中间件,找到错误处理中间件
if (layer.handle.length === 4) {
layer.handle(err, req, res, next);
} else {
next(err);
}
} else {
layer.handle(req, res, next); // 执行当前中间件
}
}
next(); // 启动中间件链
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
以下是调试express部分截图
常见中间件
express.json()
:解析 JSON 格式的请求体,常用于处理 API 请求
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/data', (req, res) => {
console.log(req.body); // 解析后的 JSON 对象
res.send('Data received');
});
2
3
4
5
6
7
8
9
express.static()
:提供静态资源服务
app.use(express.static('public')); // 访问 public 文件夹中的静态资源
- cors:解决跨域资源共享问题,允许客户端从不同源访问资源
// 使用前先下载依赖 npm install cors
const cors = require('cors');
app.use(cors()); // 默认允许所有源
app.use(cors({ origin: 'https://example.com' })); // 仅允许特定源
2
3
4
跨域请求解决的思路非常简单,可查看我的往期文章 什么是跨域
- cookie-parser:解析 HTTP 请求中的 Cookie,将其封装到 req.cookies 对象中
// 使用前先安装 npm install cookie-parser
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/', (req, res) => {
console.log(req.cookies); // 访问解析后的 cookies
res.send('Cookies received');
});
2
3
4
5
6
7
8
- helmet:用于安全响应头设置的中间件,这种中间件偏向于一种封装,你可以查看我的往期文章 HTTP协议安全规范 中手把手教你如何做安全防范
// 使用前安装 npm install helmet
const helmet = require('helmet');
app.use(helmet());
2
3
NodeJS框架之Koa
Koa 是由 Express 的原班人马开发的新一代框架。它旨在成为一个更小、更富有表现力且更健壮的基础框架,帮助开发者快速构建 Web 应用和 API
const Koa = require("koa");
const app = new Koa();
// 定义中间件
app.use(async (ctx) => {
ctx.body = "Hello, Koa!";
});
// 启动服务器
app.listen(3000, () => {
console.log("Server is running on http://localhost:3000");
});
2
3
4
5
6
7
8
9
10
11
12
Koa 不像 Express 那样内置大量功能,如路由、模板引擎或请求体解析器。它只是一个核心库,所有功能都需要通过中间件扩展
洋葱模型
Koa 的中间件机制基于“洋葱模型”,即中间件是环绕型的,允许开发者在请求和响应的不同阶段做不同的处理
koa的洋葱模型是基于Promise来做的,将所有的中间件通过Promise链式调用,整体源码非常简洁,可以在 koa-compose 包中查看到
// 源码 koa-compose/index.js
function compose (middleware) {
return function (context, next) {
let index = -1
return dispatch(0)
function dispatch (i) {
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i]
if (i === middleware.length) fn = next
if (!fn) return Promise.resolve()
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)))
} catch (err) {
return Promise.reject(err)
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
从上面可以看出洋葱模型确实很简单,但你真的知道它背后的巧妙实现吗❓总结一句就是:巧妙利用async/await
特性将await后的代码变成同步执行,这样就是实现了深度优先执行,也就是说嵌套的越深会等待深处的执行完了后才会执行当前next后的代码块
小贴士
洋葱模型依赖于async/await
或者迭代器等等,如果不使用将会导致洋葱模型执行错误
我们简单模拟下它的执行流程:
const middleware1 = async (ctx, next) => {
console.log("middleware1");
await next();
console.log("middleware1 end");
return "最终的返回值";
};
const middleware2 = async (ctx, next) => {
console.log("middleware2");
const res = await next();
console.log("middleware2 end", res);
};
const middleware3 = async (ctx, next) => {
console.log("middleware3");
return "middleware3 返回值";
};
const middlewares = [middleware1, middleware2, middleware3];
function compose(middlewares) {
return dispatch(0);
function dispatch(idx) {
let fn = middlewares[idx];
if (idx === middlewares.length) {
return Promise.resolve();
}
return Promise.resolve(
fn({} /* ctx */, function next() {
return dispatch(idx + 1);
})
);
}
}
compose(middlewares).then((res) => {
console.log(res);
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
打断点调试一看究竟:
打印结果:
middleware1
middleware2
middleware3
middleware2 end middleware3 返回值
middleware1 end
最终的返回值
2
3
4
5
6
常用中间件
- koa-router:提供路由功能,类似 Express 的路由
// 使用前安装 npm install @koa/router
const Router = require('koa-router');
const router = new Router();
router.get('/', (ctx) => {
ctx.body = 'Welcome to the home page';
});
app.use(router.routes());
2
3
4
5
6
7
8
9
- koa-static:提供静态文件服务,类似于 Express 的 express.static()
// 使用前安装 npm install koa-static
const serve = require('koa-static');
app.use(serve('./public')); // 提供 public 文件夹中
2
3
NodeJS框架之Egg.js
Egg.js 是阿里开源的一个nodejs框架,可以说它对于前面的koa等框架提供了mvc模型典范,这里不做过多介绍,大家可以查看官方文档
如果你还没有想好用哪一款框架,推荐下方的NestJS👇
NodeJS框架之fastify
Fastify 是一个高性能、灵活、开发友好的 Node.js Web 框架,以轻量、速度快为核心特色,专为现代 Web 应用和 API 开发设计;Fastify 的性能比大多数常见框架(如 Express 和 Koa)更优,在某些基准测试中甚至接近于原生 Node.js 的处理速度
这里暂不做过多介绍,感兴趣的可以阅读官方文档
NodeJS框架之NestJS
NestJS 是一个基于 TypeScript 构建的企业级框架,灵感来自 Angular,提供模块化、可测试、可扩展的解决方案,非常适合构建复杂的后端系统
import { Controller, Get } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { Module } from '@nestjs/common';
@Controller()
class AppController {
@Get()
getHello(): string {
return 'Hello, NestJS!';
}
}
@Module({
controllers: [AppController],
})
class AppModule {}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
console.log('Server is running on http://localhost:3000');
}
bootstrap();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
由于NestJS的重要性,本人已将NestJS抽取成单独系列,您可以在 这里阅读
框架对比
特性 | Express | Koa | NestJS |
---|---|---|---|
轻量程度 | 非常轻量 | 更轻量 | 较重 |
中间件机制 | 丰富的第三方中间件 | 灵活且现代化的中间件机制 | 自带大量功能模块 |
异步支持 | 使用回调或 Promise | 使用 async/await | 原生支持 async/await |
社区生态 | 庞大且成熟 | 逐渐增长 | 新兴但发展迅速 |
学习曲线 | 简单快速 | 中等 | 陡峭(类似 Angular) |
适用场景 | 小型到中型项目 | 中型到大型项目 | 大型复杂项目 |
如何选择框架
选择框架时,需要根据项目需求、团队技术栈以及未来可扩展性综合考虑:
- 如果项目简单且需要快速上线:选择 Express。它简单易用,插件生态丰富,是开发 RESTful API 的绝佳选择
- 如果需要更现代的异步支持和灵活性:选择 Koa。它更轻量、更现代化,适合需要高度自定义的项目
- 如果是复杂的企业项目或微服务架构:选择 NestJS。它的模块化和 TypeScript 支持,使得开发和维护大型项目更加高效
总结
Node.js 的发展带动了众多优秀框架的诞生,Express、Koa 和 NestJS 是最常用的三款框架,分别适合不同类型的项目需求。Express 以简单高效著称,Koa 则更现代化和灵活,而 NestJS 则是企业级项目的利器
无论选择哪种框架,理解其核心思想并结合项目需求进行实践,才是高效开发的关键。对于初学者来说,可以从 Express 开始入门,逐步探索 Koa 和 NestJS 的更多可能性