本文将系统讲解Node.js后端开发的核心知识,包括Express/Koa框架、数据库操作、中间件开发、性能优化等,帮助前端工程师掌握全栈开发能力。
📋 目录
一、Node.js核心概念
1.1 事件循环机制
javascript
// Node.js事件循环阶段
/*
┌───────────────────────────┐
┌─>│ timers │ setTimeout/setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ I/O回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ 获取新I/O事件
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ setImmediate
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──┤ close callbacks │ socket.on('close')
└───────────────────────────┘
*/
// 执行顺序示例
console.log('1. 同步代码');
setTimeout(() => console.log('2. setTimeout'), 0);
setImmediate(() => console.log('3. setImmediate'));
process.nextTick(() => console.log('4. nextTick'));
Promise.resolve().then(() => console.log('5. Promise'));
// 输出顺序: 1 -> 4 -> 5 -> 2 -> 3
1.2 模块系统
javascript
// CommonJS模块
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = { add, subtract };
// 或
exports.add = add;
exports.subtract = subtract;
// 使用
const { add, subtract } = require('./math');
// ES Modules (package.json中设置 "type": "module")
// math.mjs
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export default { add, subtract };
// 使用
import { add, subtract } from './math.mjs';
import math from './math.mjs';
1.3 Stream流处理
javascript
const fs = require('fs');
const zlib = require('zlib');
// ❌ 错误:大文件一次性读取
fs.readFile('large-file.txt', (err, data) => {
// 内存可能溢出
fs.writeFile('output.txt', data, () => {});
});
// ✅ 正确:使用流处理
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');
const gzip = zlib.createGzip();
// 管道链式处理
readStream
.pipe(gzip)
.pipe(writeStream)
.on('finish', () => console.log('压缩完成'));
// 自定义Transform流
const { Transform } = require('stream');
const upperCaseTransform = new Transform({
transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
});
readStream
.pipe(upperCaseTransform)
.pipe(writeStream);
二、Express框架实战
2.1 项目结构
express-app/
├── src/
│ ├── config/ # 配置文件
│ │ ├── database.js
│ │ └── index.js
│ ├── controllers/ # 控制器
│ │ └── userController.js
│ ├── middlewares/ # 中间件
│ │ ├── auth.js
│ │ ├── errorHandler.js
│ │ └── validator.js
│ ├── models/ # 数据模型
│ │ └── User.js
│ ├── routes/ # 路由
│ │ ├── index.js
│ │ └── userRoutes.js
│ ├── services/ # 业务逻辑
│ │ └── userService.js
│ ├── utils/ # 工具函数
│ │ └── response.js
│ └── app.js # 应用入口
├── .env
└── package.json
2.2 应用初始化
javascript
// src/app.js
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');
const routes = require('./routes');
const errorHandler = require('./middlewares/errorHandler');
const app = express();
// 安全中间件
app.use(helmet());
// CORS配置
app.use(cors({
origin: process.env.CORS_ORIGIN || '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// 请求限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 最多100次请求
message: { error: '请求过于频繁,请稍后再试' }
});
app.use('/api/', limiter);
// 日志
app.use(morgan('combined'));
// 解析请求体
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// 路由
app.use('/api', routes);
// 健康检查
app.get('/health', (req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// 404处理
app.use((req, res) => {
res.status(404).json({ error: '接口不存在' });
});
// 全局错误处理
app.use(errorHandler);
module.exports = app;
2.3 路由与控制器
javascript
// src/routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { authenticate } = require('../middlewares/auth');
const { validateUser } = require('../middlewares/validator');
router.post('/register', validateUser, userController.register);
router.post('/login', userController.login);
router.get('/profile', authenticate, userController.getProfile);
router.put('/profile', authenticate, validateUser, userController.updateProfile);
router.delete('/:id', authenticate, userController.deleteUser);
module.exports = router;
javascript
// src/controllers/userController.js
const userService = require('../services/userService');
const { success, error } = require('../utils/response');
class UserController {
async register(req, res, next) {
try {
const { username, email, password } = req.body;
const user = await userService.createUser({ username, email, password });
res.status(201).json(success('注册成功', user));
} catch (err) {
next(err);
}
}
async login(req, res, next) {
try {
const { email, password } = req.body;
const result = await userService.login(email, password);
res.json(success('登录成功', result));
} catch (err) {
next(err);
}
}
async getProfile(req, res, next) {
try {
const user = await userService.getUserById(req.user.id);
res.json(success('获取成功', user));
} catch (err) {
next(err);
}
}
async updateProfile(req, res, next) {
try {
const user = await userService.updateUser(req.user.id, req.body);
res.json(success('更新成功', user));
} catch (err) {
next(err);
}
}
async deleteUser(req, res, next) {
try {
await userService.deleteUser(req.params.id);
res.json(success('删除成功'));
} catch (err) {
next(err);
}
}
}
module.exports = new UserController();
2.4 中间件开发
javascript
// src/middlewares/auth.js
const jwt = require('jsonwebtoken');
const { AppError } = require('../utils/errors');
const authenticate = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new AppError('未提供认证令牌', 401);
}
const token = authHeader.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
if (err.name === 'JsonWebTokenError') {
next(new AppError('无效的令牌', 401));
} else if (err.name === 'TokenExpiredError') {
next(new AppError('令牌已过期', 401));
} else {
next(err);
}
}
};
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return next(new AppError('无权限访问', 403));
}
next();
};
};
module.exports = { authenticate, authorize };
javascript
// src/middlewares/errorHandler.js
const errorHandler = (err, req, res, next) => {
console.error('Error:', err);
// 自定义错误
if (err.isOperational) {
return res.status(err.statusCode).json({
success: false,
message: err.message
});
}
// Mongoose验证错误
if (err.name === 'ValidationError') {
const messages = Object.values(err.errors).map(e => e.message);
return res.status(400).json({
success: false,
message: messages.join(', ')
});
}
// Mongoose重复键错误
if (err.code === 11000) {
return res.status(400).json({
success: false,
message: '数据已存在'
});
}
// 默认服务器错误
res.status(500).json({
success: false,
message: process.env.NODE_ENV === 'production'
? '服务器内部错误'
: err.message
});
};
module.exports = errorHandler;
三、Koa框架实战
3.1 Koa vs Express
| 特性 | Express | Koa |
|---|---|---|
| 中间件模型 | 回调函数 | async/await |
| 错误处理 | 回调传递 | try/catch |
| 内置功能 | 路由、模板等 | 极简,需插件 |
| 上下文 | req/res分离 | ctx统一 |
| 体积 | 较大 | 轻量 |
3.2 Koa应用示例
javascript
// src/app.js
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
const helmet = require('koa-helmet');
const app = new Koa();
const router = new Router();
// 错误处理中间件
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = {
success: false,
message: err.message
};
ctx.app.emit('error', err, ctx);
}
});
// 中间件
app.use(helmet());
app.use(cors());
app.use(bodyParser());
// 请求日志
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// 路由
router.get('/api/users', async (ctx) => {
const users = await User.find();
ctx.body = { success: true, data: users };
});
router.post('/api/users', async (ctx) => {
const user = await User.create(ctx.request.body);
ctx.status = 201;
ctx.body = { success: true, data: user };
});
router.get('/api/users/:id', async (ctx) => {
const user = await User.findById(ctx.params.id);
if (!user) {
ctx.throw(404, '用户不存在');
}
ctx.body = { success: true, data: user };
});
app.use(router.routes());
app.use(router.allowedMethods());
// 错误事件监听
app.on('error', (err, ctx) => {
console.error('server error', err);
});
module.exports = app;
3.3 Koa中间件洋葱模型
javascript
// 洋葱模型示例
app.use(async (ctx, next) => {
console.log('1. 进入第一层');
await next();
console.log('6. 离开第一层');
});
app.use(async (ctx, next) => {
console.log('2. 进入第二层');
await next();
console.log('5. 离开第二层');
});
app.use(async (ctx, next) => {
console.log('3. 进入第三层');
ctx.body = 'Hello';
console.log('4. 离开第三层');
});
// 输出顺序: 1 -> 2 -> 3 -> 4 -> 5 -> 6
四、数据库操作
4.1 MongoDB + Mongoose
javascript
// src/config/database.js
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
});
console.log('MongoDB连接成功');
} catch (err) {
console.error('MongoDB连接失败:', err.message);
process.exit(1);
}
};
module.exports = connectDB;
javascript
// src/models/User.js
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, '用户名不能为空'],
unique: true,
trim: true,
minlength: [3, '用户名至少3个字符']
},
email: {
type: String,
required: [true, '邮箱不能为空'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '邮箱格式不正确']
},
password: {
type: String,
required: [true, '密码不能为空'],
minlength: [6, '密码至少6个字符'],
select: false // 查询时默认不返回
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
avatar: String,
createdAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true,
toJSON: { virtuals: true },
toObject: { virtuals: true }
});
// 虚拟字段
userSchema.virtual('posts', {
ref: 'Post',
localField: '_id',
foreignField: 'author'
});
// 保存前加密密码
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// 实例方法:验证密码
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
// 静态方法
userSchema.statics.findByEmail = function(email) {
return this.findOne({ email });
};
module.exports = mongoose.model('User', userSchema);
4.2 MySQL + Sequelize
javascript
// src/config/database.js
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(
process.env.DB_NAME,
process.env.DB_USER,
process.env.DB_PASS,
{
host: process.env.DB_HOST,
dialect: 'mysql',
pool: {
max: 10,
min: 0,
acquire: 30000,
idle: 10000
},
logging: process.env.NODE_ENV === 'development' ? console.log : false
}
);
module.exports = sequelize;
javascript
// src/models/User.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const bcrypt = require('bcryptjs');
const User = sequelize.define('User', {
id: {
type: DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
username: {
type: DataTypes.STRING(50),
allowNull: false,
unique: true,
validate: {
len: [3, 50]
}
},
email: {
type: DataTypes.STRING(100),
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
password: {
type: DataTypes.STRING,
allowNull: false
},
role: {
type: DataTypes.ENUM('user', 'admin'),
defaultValue: 'user'
}
}, {
tableName: 'users',
timestamps: true,
hooks: {
beforeCreate: async (user) => {
user.password = await bcrypt.hash(user.password, 12);
}
}
});
// 实例方法
User.prototype.comparePassword = async function(password) {
return await bcrypt.compare(password, this.password);
};
module.exports = User;
4.3 Redis缓存
javascript
// src/config/redis.js
const Redis = require('ioredis');
const redis = new Redis({
host: process.env.REDIS_HOST || 'localhost',
port: process.env.REDIS_PORT || 6379,
password: process.env.REDIS_PASSWORD,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3
});
redis.on('connect', () => console.log('Redis连接成功'));
redis.on('error', (err) => console.error('Redis错误:', err));
module.exports = redis;
javascript
// src/middlewares/cache.js
const redis = require('../config/redis');
const cache = (duration = 60) => {
return async (req, res, next) => {
if (req.method !== 'GET') {
return next();
}
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
return res.json(JSON.parse(cached));
}
// 重写res.json方法
const originalJson = res.json.bind(res);
res.json = (data) => {
redis.setex(key, duration, JSON.stringify(data));
return originalJson(data);
};
next();
} catch (err) {
next();
}
};
};
module.exports = cache;
五、认证与授权
5.1 JWT认证
javascript
// src/services/authService.js
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const { AppError } = require('../utils/errors');
class AuthService {
generateToken(user) {
return jwt.sign(
{ id: user._id, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
);
}
generateRefreshToken(user) {
return jwt.sign(
{ id: user._id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: '30d' }
);
}
async login(email, password) {
const user = await User.findOne({ email }).select('+password');
if (!user || !(await user.comparePassword(password))) {
throw new AppError('邮箱或密码错误', 401);
}
const token = this.generateToken(user);
const refreshToken = this.generateRefreshToken(user);
return {
user: { id: user._id, username: user.username, email: user.email },
token,
refreshToken
};
}
async refreshToken(refreshToken) {
try {
const decoded = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
const user = await User.findById(decoded.id);
if (!user) {
throw new AppError('用户不存在', 401);
}
const newToken = this.generateToken(user);
return { token: newToken };
} catch (err) {
throw new AppError('刷新令牌无效', 401);
}
}
}
module.exports = new AuthService();
5.2 OAuth2.0第三方登录
javascript
// src/services/oauthService.js
const axios = require('axios');
const User = require('../models/User');
class OAuthService {
// GitHub OAuth
async githubAuth(code) {
// 获取access_token
const tokenRes = await axios.post(
'https://github.com/login/oauth/access_token',
{
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code
},
{ headers: { Accept: 'application/json' } }
);
const { access_token } = tokenRes.data;
// 获取用户信息
const userRes = await axios.get('https://api.github.com/user', {
headers: { Authorization: `Bearer ${access_token}` }
});
const { id, login, email, avatar_url } = userRes.data;
// 查找或创建用户
let user = await User.findOne({ githubId: id });
if (!user) {
user = await User.create({
username: login,
email: email || `${id}@github.com`,
githubId: id,
avatar: avatar_url
});
}
return user;
}
}
module.exports = new OAuthService();
六、性能优化与部署
6.1 性能优化
javascript
// 1. 使用集群模式
// cluster.js
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
console.log(`主进程 ${process.pid} 正在运行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工作进程 ${worker.process.pid} 已退出`);
cluster.fork(); // 重启进程
});
} else {
require('./src/app');
console.log(`工作进程 ${process.pid} 已启动`);
}
// 2. 使用PM2
// ecosystem.config.js
module.exports = {
apps: [{
name: 'api-server',
script: './src/server.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production'
}
}]
};
javascript
// 3. 压缩响应
const compression = require('compression');
app.use(compression());
// 4. 数据库查询优化
// 使用索引
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });
// 只查询需要的字段
const users = await User.find().select('username email');
// 分页查询
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
const users = await User.find()
.skip(skip)
.limit(limit)
.lean(); // 返回普通对象,性能更好
6.2 Docker部署
dockerfile
# Dockerfile
FROM node:18-alpine
WORKDIR /app
# 安装依赖
COPY package*.json ./
RUN npm ci --only=production
# 复制源码
COPY . .
# 设置环境变量
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000
# 使用非root用户
USER node
CMD ["node", "src/server.js"]
yaml
# docker-compose.yml
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- MONGODB_URI=mongodb://mongo:27017/myapp
- REDIS_HOST=redis
depends_on:
- mongo
- redis
restart: unless-stopped
mongo:
image: mongo:6
volumes:
- mongo_data:/data/db
restart: unless-stopped
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
restart: unless-stopped
volumes:
mongo_data:
redis_data:
6.3 日志与监控
javascript
// src/utils/logger.js
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
module.exports = logger;
📊 Node.js后端技术栈总结
| 类别 | 技术选型 | 说明 |
|---|---|---|
| 框架 | Express/Koa/Nest.js | 根据项目复杂度选择 |
| 数据库 | MongoDB/MySQL/PostgreSQL | 根据数据特点选择 |
| 缓存 | Redis | 高性能缓存 |
| 认证 | JWT/OAuth2.0 | 无状态认证 |
| 日志 | Winston/Pino | 结构化日志 |
| 部署 | PM2/Docker/K8s | 容器化部署 |
💡 总结
Node.js后端开发核心要点:
- ✅ 理解事件循环:异步编程的基础
- ✅ 合理使用中间件:职责分离,代码复用
- ✅ 数据库优化:索引、分页、缓存
- ✅ 安全防护:认证、授权、输入验证
- ✅ 性能优化:集群、压缩、缓存
- ✅ 容器化部署:Docker + PM2
掌握Node.js,让前端工程师具备全栈开发能力!
💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~