0%

Koa 与 Express 对比——Node.JS 框架选型分析

新年第一篇,就像 Koa 和 Express 的选择一样,都有各自的美好…

介绍

  Koa 和 Express 都是由 TJ Holowaychuk 创建的 Node.JS Web 框架,但它们的设计理念和使用方式有很大的不同。Express 是 Node.JS 生态中最早的 Web 框架之一,而 Koa 是 Express 的继任者,采用了更加现代化的设计理念。本文将深入对比这两个框架,帮助开发者根据项目需求选择合适的框架。

Koa 和 Express 的核心差异

架构设计理念

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Express - 回调函数风格 const express = require('express');
const app = express();

app.get('/', (req, res) => {
res.send('Hello Express!');
});

app.listen(3000, () => {
console.log('Express server running on port 3000');
});

// Koa - 中间件洋葱模型 const Koa = require('koa');
const app = new Koa();

app.use(ctx => {
ctx.body = 'Hello Koa!';
});

app.listen(3000, () => {
console.log('Koa server running on port 3000');
});

异步处理机制

1
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
// Express - 基于回调函数 const express = require('express');
const app = express();

app.get('/async', (req, res, next) => {
// 异步操作需要手动处理错误 someAsyncOperation((err, result) => {
if (err) {
return next(err); // 传递错误给下一个中间件
}
res.Json(result);
});
});

// 使用 Promise 的 Express(需要额外处理)
app.get('/async-promise', (req, res, next) => {
somePromiseOperation()
.then(result => res.Json(result))
.catch(next); // 传递错误
});

// Koa - 原生支持 async/await
const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
try {
const result = await someAsyncOperation();
ctx.body = result;
} catch (err) {
ctx.throw(err); // Koa 内置错误处理
}
});

中间件机制对比

Express 中间件

1
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
const express = require('express');
const app = express();

// 中间件执行顺序 app.use((req, res, next) => {
console.log('1. Middleware start');
req.startTime = Date.now();
next();
console.log('4. Middleware end (after route handler)');
});

app.use('/API', (req, res, next) => {
console.log('2. API middleware');
req.apiCall = true;
next();
});

// 路由处理器 app.get('/API/users', (req, res, next) => {
console.log('3. Route handler');
res.Json({
users: ['Alice', 'Bob'],
startTime: req.startTime,
apiCall: req.apiCall
});
});

// 错误处理中间件 app.use((err, req, res, next) => {
console.error('Error:', err);
res.status(500).Json({ error: 'Internal Server Error' });
});

Koa 中间件(洋葱模型)

1
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
const Koa = require('koa');
const app = new Koa();

// Koa 的洋葱模型 - 执行顺序不同 app.use(async (ctx, next) => {
console.log('1. Start middleware A');
await next(); // 暂停,执行后续中间件 console.log('6. End middleware A (after all downstream)');
});

app.use(async (ctx, next) => {
console.log('2. Start middleware B');
await next(); // 暂停,执行后续中间件 console.log('5. End middleware B');
});

app.use(async ctx => {
console.log('3. Route handler');
ctx.body = {
message: 'Hello from Koa',
timestamp: new Date().toISOString()
};
console.log('4. After setting response body');
});

// Koa 错误处理 app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = { error: err.message };
ctx.app.emit('error', err, ctx);
}
});

中间件编写最佳实践

1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// Express 中间件示例 function loggerMiddleware() {
return (req, res, next) => {
const start = Date.now();

// 监听响应结束事件 res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`);
});

next();
};
}

function rateLimitMiddleware(maxRequests = 100, windowMs = 60000) {
const clients = new Map();

return (req, res, next) => {
const clientIp = req.ip;
const now = Date.now();

if (!clients.has(clientIp)) {
clients.set(clientIp, []);
}

const requests = clients.get(clientIp);
// 清理过期请求记录 const validRequests = requests.filter(timestamp => now - timestamp < windowMs);

if (validRequests.length >= maxRequests) {
return res.status(429).Json({ error: 'Too Many Requests' });
}

validRequests.push(now);
clients.set(clientIp, validRequests);
next();
};
}

// Koa 中间件示例 function koaLogger() {
return async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ctx.status} - ${ms}ms`);
};
}

function koaRateLimit(maxRequests = 100, windowMs = 60000) {
const clients = new Map();

return async (ctx, next) => {
const clientIp = ctx.ip;
const now = Date.now();

if (!clients.has(clientIp)) {
clients.set(clientIp, []);
}

const requests = clients.get(clientIp);
const validRequests = requests.filter(timestamp => now - timestamp < windowMs);

if (validRequests.length >= maxRequests) {
ctx.status = 429;
ctx.body = { error: 'Too Many Requests' };
return;
}

validRequests.push(now);
clients.set(clientIp, validRequests);

await next();
};
}

错误处理机制

Express 错误处理

1
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
45
const express = require('express');
const app = express();

// 同步错误处理 app.get('/sync-error', (req, res, next) => {
try {
throw new Error('Synchronous error');
} catch (err) {
next(err); // 传递给错误处理中间件
}
});

// 异步错误处理 app.get('/async-error', (req, res, next) => {
someAsyncOperation()
.then(result => {
if (!result) {
const error = new Error('Async operation failed');
error.status = 400;
next(error);
}
res.Json(result);
})
.catch(next); // 捕获并传递错误
});

// 全局错误处理中间件(必须有4个参数)
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);

res.status(err.status || 500).Json({
error: {
message: err.message,
status: err.status || 500
}
});
});

// 未捕获异常处理 process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err);
process.exit(1);
});

process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
process.exit(1);
});

Koa 错误处理

1
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
const Koa = require('koa');
const app = new Koa();

// Koa 的错误处理更简洁 app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
error: {
message: err.message,
status: ctx.status
}
};

// 记录错误 ctx.app.emit('error', err, ctx);
}
});

app.use(async ctx => {
// 同步错误 if (ctx.path === '/sync-error') {
throw new Error('Synchronous error in Koa');
}

// 异步错误 if (ctx.path === '/async-error') {
await someAsyncOperationThatFails();
}

ctx.body = 'Success';
});

// Koa 应用级别的错误处理 app.on('error', (err, ctx) => {
console.error('Application Error:', err);
console.error('Context:', {
url: ctx.url,
method: ctx.method,
status: ctx.status
});
});

路由系统对比

Express 路由

1
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
const express = require('express');
const app = express();

// 基本路由 app.get('/', (req, res) => res.send('Home'));
app.post('/users', (req, res) => res.Json(req.body));
app.put('/users/:id', (req, res) => res.Json({ id: req.params.id, ...req.body }));
app.delete('/users/:id', (req, res) => res.Json({ deleted: req.params.id }));

// 路由参数验证 app.get('/users/:id(\\d+)', (req, res) => {
// 只匹配数字 ID
res.Json({ userId: req.params.id });
});

// 中间件链 app.use('/API', (req, res, next) => {
console.log('API middleware');
next();
});

// 路由分组 const userRouter = express.Router();

userRouter.get('/', (req, res) => {
res.Json({ message: 'Get all users' });
});

userRouter.get('/:id', (req, res) => {
res.Json({ message: `Get user ${req.params.id}` });
});

userRouter.post('/', (req, res) => {
res.Json({ message: 'Create user', data: req.body });
});

app.use('/API/users', userRouter);

// 路由处理错误 app.get('/error-demo', (req, res, next) => {
// 错误会自动传递到错误处理中间件 const error = new Error('Route processing error');
error.status = 400;
next(error);
});

Koa 路由

1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
const Koa = require('koa');
const Router = require('@koa/router'); // 需要额外安装 @koa/router
const app = new Koa();

const router = new Router();

// 基本路由 router.get('/', async ctx => {
ctx.body = 'Home';
});

router.post('/users', async ctx => {
ctx.body = ctx.request.body;
});

router.put('/users/:id', async ctx => {
ctx.body = { id: ctx.params.id, ...ctx.request.body };
});

router.del('/users/:id', async ctx => {
ctx.body = { deleted: ctx.params.id };
});

// 路径参数验证 router.get('/users/:id', async ctx => {
const id = parseInt(ctx.params.id);
if (isNaN(id)) {
ctx.status = 400;
ctx.body = { error: 'Invalid user ID' };
return;
}
ctx.body = { userId: id };
});

// 路由前缀和分组 const userRoutes = new Router({ prefix: '/API/users' });

userRoutes.get('/', async ctx => {
ctx.body = { message: 'Get all users' };
});

userRoutes.get('/:id', async ctx => {
ctx.body = { message: `Get user ${ctx.params.id}` };
});

userRoutes.post('/', async ctx => {
ctx.body = { message: 'Create user', data: ctx.request.body };
});

app.use(userRoutes.routes());

// 中间件和路由组合 const authMiddleware = async (ctx, next) => {
const token = ctx.headers.authorization;
if (!token) {
ctx.status = 401;
ctx.body = { error: 'Unauthorized' };
return;
}
await next();
};

const protectedRouter = new Router();

protectedRouter.get('/profile', authMiddleware, async ctx => {
ctx.body = { message: 'Protected route' };
});

app.use(protectedRouter.routes());

性能对比与基准测试

1
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
45
46
47
48
49
50
51
52
53
54
55
// 性能测试示例 const Benchmark = require('benchmark');
const express = require('express');
const Koa = require('koa');
const Router = require('@koa/router');

// Express 应用 const expressApp = express();
expressApp.get('/hello', (req, res) => {
res.Json({ message: 'Hello Express!' });
});

// Koa 应用 const koaApp = new Koa();
const koaRouter = new Router();
koaRouter.get('/hello', async ctx => {
ctx.body = { message: 'Hello Koa!' };
});
koaApp.use(koaRouter.routes());

// 基准测试 const suite = new Benchmark.Suite();

suite
.add('Express', {
defer: true,
fn: function(deferred) {
// 模拟 HTTP 请求 require('http').request({
hostname: 'localhost',
port: 3001,
path: '/hello',
method: 'GET'
}, (res) => {
res.on('data', () => {});
res.on('end', () => deferred.resolve());
}).end();
}
})
.add('Koa', {
defer: true,
fn: function(deferred) {
require('http').request({
hostname: 'localhost',
port: 3002,
path: '/hello',
method: 'GET'
}, (res) => {
res.on('data', () => {});
res.on('end', () => deferred.resolve());
}).end();
}
})
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
.run({ async: true });

实际应用场景分析

什么时候选择 Express

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Express 适合的场景:成熟的生态系统 const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const compression = require('compression');

const app = express();

// 丰富的中间件生态 app.use(helmet()); // 安全头 app.use(cors()); // 跨域 app.use(compression()); // 压缩

// 速率限制 const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟 max: 100 // 限制每个 IP 100次请求
});
app.use('/API/', limiter);

// 熟悉的路由语法 app.get('/legacy-endpoint', (req, res) => {
res.Json({ status: 'This is an older endpoint' });
});

// 大量现有的 Express 中间件可用 const morgan = require('morgan');
app.use(morgan('combined'));

// 与现有 Express 应用集成 const legacyModule = require('./legacy-module');
app.use('/legacy', legacyModule);

什么时候选择 Koa

1
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
// Koa 适合的场景:现代异步处理 const Koa = require('koa');
const app = new Koa();

// 原生 async/await 支持 app.use(async (ctx, next) => {
// 清晰的异步错误处理 try {
await someAsyncOperation();
await next();
} catch (err) {
ctx.status = 500;
ctx.body = { error: err.message };
}
});

// 简洁的中间件语法 const responseTime = async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
ctx.set('X-Response-Time', `${ms}ms`);
};

app.use(responseTime);

// 更好的错误传播 app.use(async ctx => {
// 任何异步错误都会自动被捕获 const data = await fetchDataFromAPI();
ctx.body = data;
});

// 上下文对象提供了更丰富的信息 app.use(async ctx => {
ctx.body = {
url: ctx.url,
method: ctx.method,
headers: ctx.headers,
ip: ctx.ip,
query: ctx.query,
params: ctx.params,
requestBody: ctx.request.body
};
});

项目结构示例

Express 项目结构

1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// app.JS - Express 应用入口 const express = require('express');
const mongoose = require('mongoose');
const userRoutes = require('./routes/users');
const authRoutes = require('./routes/auth');
const errorHandler = require('./middleware/errorHandler');

const app = express();

// 中间件配置 app.use(express.Json());
app.use(express.urlencoded({ extended: true }));

// 路由配置 app.use('/API/users', userRoutes);
app.use('/API/auth', authRoutes);

// 错误处理 app.use(errorHandler);

// 连接数据库 mongoose.connect('mongodb://localhost:27017/myapp')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));

module.exports = app;

// routes/users.JS
const express = require('express');
const router = express.Router();
const { getAllUsers, getUserById, createUser } = require('../controllers/userController');

router.get('/', getAllUsers);
router.get('/:id', getUserById);
router.post('/', createUser);

module.exports = router;

// controllers/userController.JS
exports.getAllUsers = async (req, res, next) => {
try {
const users = await User.find();
res.Json(users);
} catch (err) {
next(err);
}
};

exports.getUserById = async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).Json({ error: 'User not found' });
}
res.Json(user);
} catch (err) {
next(err);
}
};

exports.createUser = async (req, res, next) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).Json(user);
} catch (err) {
next(err);
}
};

Koa 项目结构

1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// app.JS - Koa 应用入口 const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const mongoose = require('mongoose');
const userRoutes = require('./routes/users');
const authRoutes = require('./routes/auth');
const errorHandler = require('./middleware/errorHandler');

const app = new Koa();

// 中间件配置 app.use(bodyParser());

// 错误处理 app.use(errorHandler);

// 路由配置 app.use(userRoutes.routes());
app.use(authRoutes.routes());

// 连接数据库 mongoose.connect('mongodb://localhost:27017/myapp')
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('MongoDB connection error:', err));

module.exports = app;

// routes/users.JS
const Router = require('@koa/router');
const { getAllUsers, getUserById, createUser } = require('../controllers/userController');

const router = new Router({ prefix: '/API/users' });

router.get('/', getAllUsers);
router.get('/:id', getUserById);
router.post('/', createUser);

module.exports = router;

// controllers/userController.JS
exports.getAllUsers = async ctx => {
try {
const users = await User.find();
ctx.body = users;
} catch (err) {
ctx.throw(500, err.message);
}
};

exports.getUserById = async ctx => {
try {
const user = await User.findById(ctx.params.id);
if (!user) {
ctx.status = 404;
ctx.body = { error: 'User not found' };
return;
}
ctx.body = user;
} catch (err) {
ctx.throw(500, err.message);
}
};

exports.createUser = async ctx => {
try {
const user = new User(ctx.request.body);
await user.save();
ctx.status = 201;
ctx.body = user;
} catch (err) {
ctx.throw(500, err.message);
}
};

生态系统和社区支持

Express 生态系统

1
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
// Express 丰富的中间件生态 const express = require('express');
const app = express();

// 安全相关 const helmet = require('helmet');
const csurf = require('csurf');
const rateLimit = require('express-rate-limit');

// 认证相关 const passport = require('passport');
const session = require('express-session');

// 数据库相关 const mongoose = require('mongoose');
const sequelize = require('sequelize');

// 模板引擎 const ejs = require('ejs');
const handlebars = require('handlebars');

// 工具类 const compression = require('compression');
const morgan = require('morgan');
const cors = require('cors');

// 应用中间件 app.use(helmet());
app.use(csurf());
app.use(session({ secret: 'keyboard cat' }));
app.use(passport.initialize());
app.use(compression());
app.use(morgan('combined'));
app.use(cors());

Koa 生态系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Koa 现代化的中间件生态 const Koa = require('koa');
const app = new Koa();

// 需要对应的 Koa 中间件 const koaBody = require('koa-body');
const koaSession = require('koa-session');
const koaHelmet = require('koa-helmet');
const koaCors = require('@koa/cors');

// 认证 const koaPassport = require('koa-passport');

// 数据库(通用)
const mongoose = require('mongoose');

// 应用中间件 app.use(koaBody());
app.use(koaSession(app));
app.use(koaHelmet());
app.use(koaCors());
app.use(koaPassport.initialize());

// Koa 特有的中间件 const koaStatic = require('koa-static');
const koaSend = require('koa-send');

// 静态文件服务 app.use(koaStatic('./public'));

// 路由 const Router = require('@koa/router');

迁移和互操作

从 Express 迁移到 Koa

1
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
// 迁移示例:将 Express 中间件适配为 Koa 中间件 const expressToKoa = (expressMiddleware) => {
return async (ctx, next) => {
// 将 Koa 上下文转换为 Express 的 req/res
const req = ctx.request;
const res = ctx.response;

// 创建 next 函数来继续中间件链 const expressNext = (err) => {
if (err) {
ctx.throw(err);
} else {
return next();
}
};

// 调用 Express 中间件 await new Promise((resolve, reject) => {
const wrappedNext = (err) => {
if (err) reject(err);
else resolve();
};

expressMiddleware(req, res, wrappedNext);
});

await next();
};
};

// 反向:Koa 中间件适配为 Express 中间件 const koaToExpress = (koaMiddleware) => {
return (req, res, next) => {
// 创建 Koa 上下文 const ctx = {
request: req,
response: res,
req: req,
res: res,
// 简化的上下文对象
};

koaMiddleware(ctx, () => {})
.then(() => next())
.catch(next);
};
};

最佳实践总结

通用最佳实践

1
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
45
46
47
48
// 1. 环境配置 const config = {
development: {
port: 3000,
database: 'mongodb://localhost:27017/app_dev',
debug: true
},
production: {
port: process.env.PORT || 8080,
database: process.env.MONGODB_URI,
debug: false
}
};

// 2. 错误处理中间件 const createErrorHandler = (isProduction) => {
return (err, req, res, next) => {
console.error('Error:', err);

const errorResponse = {
error: isProduction ? 'Internal Server Error' : err.message,
...(isProduction ? {} : { stack: err.stack })
};

res.status(err.status || 500).Json(errorResponse);
};
};

// 3. 日志记录 const createLogger = () => {
return (req, res, next) => {
const start = Date.now();

res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} ${duration}ms`);
});

next();
};
};

// 4. 安全配置 const createSecurityMiddleware = () => {
return (req, res, next) => {
// 设置安全头 res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');

next();
};
};

总结

  • Express 是 Node.JS 生态中最成熟、生态最丰富的框架,适合需要大量第三方中间件的项目
  • Koa 采用更现代化的设计理念,原生支持 async/await,代码更简洁
  • Express 的学习曲线相对平缓,社区资源丰富
  • Koa 的洋葱模型提供了更灵活的中间件控制
  • 在选择框架时应考虑项目需求、团队熟悉度和生态系统需求
  • 两者都是优秀的选择,关键在于适合具体的应用场景

新年的第一篇文章,就像 Koa 和 Express 的选择一样,各有其美好的地方。Express 成熟稳重,Koa 轻盈灵活,选择哪一个都是一种美好。

选择指南

选择标准推荐框架理由
快速原型开发Express生态丰富,上手快
现代异步处理Koa原生 async/await 支持
学习成本Express文档和教程丰富
代码简洁性Koa更少的回调,更清晰的错误处理
生态系统Express中间件数量更多
性能要求两者相近实际差别不大

扩展阅读

  1. Express 官方文档
  2. Koa 官方文档
  3. Node.JS 框架比较
  4. 中间件模式详解

练习建议

  1. 用两个框架分别实现相同的 API
  2. 比较两个框架的中间件执行效率
  3. 尝试将 Express 项目迁移到 Koa
  4. 研究两个框架的生态系统和中间件
bulb