解释为什么 Express ‘app’ 和 ‘server’ 必须分开的原因?
参考回答
在使用 Express 开发应用时,建议将 app(Express 实例)和 server(HTTP 服务器实例)分开管理。这主要是为了提高代码的灵活性和模块化,使得应用程序更容易进行测试、扩展和维护。
具体原因包括:
1. 单元测试的需求:通过分离,可以直接测试 app,而无需依赖 HTTP 服务器。
2. 部署灵活性:允许在不同的服务器配置中复用 app 实例,而无需直接启动 HTTP 服务器。
3. 更好的控制:分离后,可以更精确地控制服务器的行为,例如跨服务共享 app。
详细讲解与拓展
1. app 和 server 是什么?
app是由express()创建的 Express 应用实例,负责定义路由、处理中间件和业务逻辑。server是由 Node.js 的http.createServer()或app.listen()创建的 HTTP 服务器实例,负责监听指定端口并处理请求。
通常,开发中我们会用以下代码来启动 Express 应用:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
const server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
尽管这段代码工作良好,但将 app 和 server 紧密绑定在一起可能会引发一些问题。
2. 分离 app 和 server 的好处
(1)单元测试
如果 app 和 server 耦合在一起,就无法单独测试 Express 的路由或中间件。例如:
const request = require('supertest');
const app = require('./app');
describe('GET /', () => {
it('should return "Hello World"', async () => {
const res = await request(app).get('/');
expect(res.text).toBe('Hello World');
});
});
上面示例中,测试只针对 app,而无需启动服务器实例。如果 app 和 server 耦合,则测试需要启动和关闭服务器,这会增加复杂性和运行时间。
(2)更灵活的部署
分离后,可以将 app 挂载到不同的 HTTP 服务器上(如 HTTPS、Socket 服务器)。例如:
const http = require('http');
const https = require('https');
const app = require('./app');
const httpServer = http.createServer(app);
const httpsServer = https.createServer({ key, cert }, app);
httpServer.listen(3000);
httpsServer.listen(443);
如果 app 和 server 不分离,上述代码会变得更加复杂,且不易扩展。
(3)中间件和路由的复用
分离 app 和 server 后,可以更方便地将 app 作为模块复用。例如:
const app = require('./app');
module.exports = app;
在其他服务中:
const app = require('./app');
const server = app.listen(4000);
(4)优雅的错误处理
分离后,可以在测试或实际运行环境中对服务器实例进行更好的控制。例如,优雅地关闭服务器:
const server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
process.on('SIGINT', () => {
server.close(() => {
console.log('Server closed');
process.exit(0);
});
});
3. 最佳实践:分离实现方式
创建 app 模块
单独创建一个 app.js 文件,只负责配置 Express 应用:
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World');
});
module.exports = app;
启动服务器
在 server.js 中加载 app 并启动 HTTP 服务器:
const app = require('./app');
const server = app.listen(3000, () => {
console.log('Server is running on port 3000');
});
测试代码
测试只需引入 app 而不依赖 server:
const request = require('supertest');
const app = require('./app');
describe('GET /', () => {
it('should return "Hello World"', async () => {
const res = await request(app).get('/');
expect(res.statusCode).toBe(200);
expect(res.text).toBe('Hello World');
});
});
总结
将 app 和 server 分开是现代 Node.js 开发中的一种推荐实践。它不仅提高了代码的灵活性和模块化,还方便测试和部署。例如,测试中可以单独测试 app 实例,生产环境中可以挂载 app 到不同的服务器实例上。这种分离结构能够让代码更清晰、更易维护,同时适应更复杂的应用场景。