Node.js 搭建后台框架
·
一、框架选型与基础搭建
- 核心框架选择
- 优先选成熟框架:Express(轻量灵活,适合中小项目)、NestJS(企业级,模块化 / TypeScript 友好)、Koa2(更现代的中间件机制),避免从零手写 HTTP 服务。
- 配套生态:选框架适配的中间件(如 Express 用
express.json()解析 JSON 请求体,而非自己写解析逻辑)。
- 项目结构规范
- 避免 “面条代码”,按功能 / 分层拆分目录,示例(Express):
plaintext
project/ ├── config/ // 配置文件(数据库、JWT、端口等) ├── controllers/ // 控制器(处理请求、返回响应) ├── models/ // 数据模型(数据库交互) ├── routes/ // 路由定义(接口路径映射) ├── middleware/ // 自定义中间件(鉴权、日志、异常处理) ├── utils/ // 工具函数(加密、格式化、通用方法) ├── app.js // 应用入口(挂载中间件、路由) └── server.js // 启动服务器 - 配置文件抽离:敏感配置(数据库密码、JWT 密钥)不要硬编码,用
dotenv加载.env文件,示例:javascript
运行
// .env 文件(添加到 .gitignore,不提交到仓库) PORT=3000 DB_PASSWORD=123456 JWT_SECRET=your-secret-key // config/index.js require('dotenv').config(); module.exports = { port: process.env.PORT, db: { password: process.env.DB_PASSWORD }, jwt: { secret: process.env.JWT_SECRET } };
- 避免 “面条代码”,按功能 / 分层拆分目录,示例(Express):
二、安全防护(重中之重)
- 密码与身份认证
- 密码不可逆存储:必须用
bcryptjs/bcrypt哈希加密,禁止明文 / MD5(易破解)存储。 - JWT 规范:
- 设置合理过期时间(如 2h),避免永久有效;
- 密钥复杂度足够(至少 16 位随机字符串),定期更换;
- 避免在 JWT Payload 中存敏感信息(如密码),仅存用户 ID、角色等非敏感数据。
- 密码不可逆存储:必须用
- 防常见攻击
- 跨域:用
cors中间件,指定允许的 origin(生产环境禁止*),示例:javascript
运行
app.use(cors({ origin: ['https://your-frontend.com'], // 仅允许指定前端域名 credentials: true // 如需跨域携带 Cookie })); - XSS 攻击:返回响应时过滤 HTML 特殊字符(用
xss模块),禁止前端输入的内容直接渲染。 - SQL 注入:用 ORM 框架(Sequelize、Prisma)或参数化查询,禁止拼接 SQL 字符串,示例:
javascript
运行
// 错误(易注入): db.query(`SELECT * FROM users WHERE name = '${req.query.name}'`); // 正确(参数化): db.query('SELECT * FROM users WHERE name = ?', [req.query.name]); - CSRF 攻击:接口鉴权优先用 JWT(放在请求头
Authorization: Bearer <token>),而非 Cookie;如需 Cookie,加csurf中间件。 - 接口限流:用
express-rate-limit限制单 IP 请求频率(如 1 分钟最多 100 次),防止暴力攻击,示例:javascript
运行
const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 60 * 1000, // 1分钟 max: 100, // 单IP最多100次请求 message: '请求过于频繁,请稍后再试' }); app.use('/api/', limiter); // 对所有/api接口限流
- 跨域:用
三、异常处理与日志
- 全局异常捕获
- 避免未捕获的异常导致服务器崩溃,示例(Express):
javascript
运行
// 自定义异常处理中间件(放在所有路由之后) app.use((err, req, res, next) => { console.error('异常信息:', err.stack); res.status(err.statusCode || 500).json({ code: err.code || 500, msg: process.env.NODE_ENV === 'production' ? '服务器内部错误' : err.message }); }); // 异步函数异常捕获(Express 需手动传递给 next) app.get('/api/user', async (req, res, next) => { try { const data = await getUserData(); res.json(data); } catch (err) { next(err); // 交给全局异常中间件处理 } });
- 避免未捕获的异常导致服务器崩溃,示例(Express):
- 日志记录
- 不要只用
console.log,用winston/morgan记录日志:morgan:记录 HTTP 请求日志(方法、路径、状态码、响应时间);winston:分级记录日志(info/error/warn),输出到文件 + 控制台,示例:javascript
运行
const winston = require('winston'); const logger = winston.createLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: winston.format.json(), transports: [ new winston.transports.Console(), // 控制台输出 new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), // 错误日志 new winston.transports.File({ filename: 'logs/combined.log' }) // 所有日志 ] }); // 使用:logger.error('数据库连接失败');
- 不要只用
四、数据库操作
- 连接与复用
- 数据库连接池:MySQL/MongoDB 等都要配置连接池,避免每次请求新建连接(性能损耗),示例(MySQL2):
javascript
运行
const mysql = require('mysql2/promise'); const pool = mysql.createPool({ host: 'localhost', user: 'root', password: '123456', database: 'test', waitForConnections: true, connectionLimit: 10, // 最大连接数 queueLimit: 0 }); // 复用连接池,而非每次创建连接 module.exports = pool;
- 数据库连接池:MySQL/MongoDB 等都要配置连接池,避免每次请求新建连接(性能损耗),示例(MySQL2):
- 数据校验
- 前端传参必须校验:用
joi/express-validator验证参数类型、长度、格式,避免无效数据入库,示例:javascript
运行
const { body, validationResult } = require('express-validator'); // 注册接口参数校验 app.post('/api/register', [ body('name').notEmpty().withMessage('用户名不能为空'), body('password').isLength({ min: 6 }).withMessage('密码至少6位') ], (req, res) => { // 校验结果 const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // 校验通过,处理业务 });
- 前端传参必须校验:用
- 事务处理
- 涉及多表操作(如转账、订单创建)必须用事务,避免数据不一致,示例(MySQL2):
javascript
运行
const pool = require('./config/db'); async function createOrder() { const connection = await pool.getConnection(); try { await connection.beginTransaction(); // 开启事务 // 执行多个SQL操作 await connection.execute('INSERT INTO orders (...) VALUES (...)'); await connection.execute('UPDATE goods SET stock = stock - 1 WHERE id = ?', [1]); await connection.commit(); // 提交事务 } catch (err) { await connection.rollback(); // 回滚事务 throw err; } finally { connection.release(); // 释放连接 } }
- 涉及多表操作(如转账、订单创建)必须用事务,避免数据不一致,示例(MySQL2):
五、性能与部署
- 性能优化
- 接口缓存:高频读取接口(如首页数据、商品列表)用
redis缓存,减少数据库查询,示例:javascript
运行
const redis = require('redis'); const client = redis.createClient({ url: 'redis://localhost:6379' }); client.connect(); // 获取商品列表(先查缓存,再查数据库) app.get('/api/goods', async (req, res) => { const cacheKey = 'goods:list'; const cacheData = await client.get(cacheKey); if (cacheData) { return res.json(JSON.parse(cacheData)); } // 缓存未命中,查数据库 const goods = await db.query('SELECT * FROM goods'); // 存入缓存(设置过期时间,避免数据不一致) await client.setEx(cacheKey, 3600, JSON.stringify(goods)); res.json(goods); }); - 避免同步阻塞:Node.js 是单线程,禁止用
fs.readFileSync/bcryptjs.hashSync等同步方法处理高并发请求,改用异步版本(fs.readFile/bcryptjs.hash)。
- 接口缓存:高频读取接口(如首页数据、商品列表)用
- 部署注意事项
- 环境区分:开发 / 测试 / 生产环境配置分离,生产环境关闭
debug日志、禁用cors: *; - 进程守护:用
pm2启动服务,防止进程崩溃,示例:bash
运行
# 安装 pm2 npm install pm2 -g # 启动服务(配置文件 ecosystem.config.js 更佳) pm2 start server.js --name my-api # 查看进程状态 pm2 status # 日志查看 pm2 logs - 端口与权限:生产环境避免用 80/443 等低端口(需 root 权限),用 Nginx 反向代理到 Node.js 服务(如 Nginx 监听 80,转发到 3000 端口)。
- 环境区分:开发 / 测试 / 生产环境配置分离,生产环境关闭
六、代码质量与维护
- 代码规范
- 用 ESLint + Prettier 统一代码风格,避免团队协作时格式混乱;
- 优先用 TypeScript:强类型检查能提前发现类型错误,尤其适合中大型项目。
- 接口文档
- 自动生成接口文档:用 Swagger/OpenAPI(如
swagger-jsdoc + swagger-ui-express),避免手动维护文档,示例:javascript
运行
const swaggerJsdoc = require('swagger-jsdoc'); const swaggerUi = require('swagger-ui-express'); const options = { definition: { openapi: '3.0.0', info: { title: 'API 文档', version: '1.0.0' }, }, apis: ['./routes/*.js'], // 从路由文件读取注释生成文档 }; const specs = swaggerJsdoc(options); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
- 自动生成接口文档:用 Swagger/OpenAPI(如
- 单元测试
- 核心逻辑写单元测试:用 Jest/Mocha 测试控制器、工具函数,示例(Jest):
javascript
运行
// utils/encrypt.test.js const { encryptPwd } = require('./encrypt'); test('密码加密后能验证通过', async () => { const pwd = '123456'; const hash = await encryptPwd(pwd); const isMatch = await bcryptjs.compare(pwd, hash); expect(isMatch).toBe(true); });
- 核心逻辑写单元测试:用 Jest/Mocha 测试控制器、工具函数,示例(Jest):
总结
- 基础层:规范项目结构、抽离配置、选成熟框架,避免重复造轮子;
- 安全层:密码哈希、JWT 规范、防注入 / XSS / 限流,敏感配置不硬编码;
- 稳定性层:全局异常捕获、日志记录、数据库事务、连接池复用;
- 性能与维护:缓存高频接口、用 PM2 守护进程、自动生成接口文档、写单元测试。
更多推荐
所有评论(0)