【Node.js后端开发实战指南:从入门到企业级应用】

本文将系统讲解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后端开发核心要点:

  1. 理解事件循环:异步编程的基础
  2. 合理使用中间件:职责分离,代码复用
  3. 数据库优化:索引、分页、缓存
  4. 安全防护:认证、授权、输入验证
  5. 性能优化:集群、压缩、缓存
  6. 容器化部署:Docker + PM2

掌握Node.js,让前端工程师具备全栈开发能力!


💬 如果这篇文章对你有帮助,欢迎点赞收藏!有问题欢迎在评论区讨论~

相关推荐
weixin_462446232 小时前
Node.js 纯 JS 生成 SVG 练字纸(米字格 / 田字格)完整实现解析
开发语言·javascript·node.js
cypking2 小时前
三、NestJS 开发实战文档-->集成 MySQL(TypeORM)
前端·数据库·mysql·adb·node.js
Misnearch2 小时前
npm包-serve包使用
前端·npm·node.js
千寻girling13 小时前
计算机组成原理-全通关源码-实验(通关版)---头歌平台
前端·面试·职场和发展·typescript·node.js
damo王21 小时前
how to install npm in ubuntu24.04?
前端·npm·node.js
weixin_5316518121 小时前
Node.js 流操作
node.js·node·stream
Lupino1 天前
Node.js 与 Haskell 混合网络编程踩坑记:TCP 粘包与状态不一致引发的“死锁”
javascript·node.js
神秘的猪头1 天前
LangChain Tool 实战:让大模型“长出双手”,通过 Tool 调用连接真实世界
langchain·node.js·aigc
小北方城市网1 天前
第 10 课:Node.js 后端企业级进阶 —— 任务管理系统后端优化与功能增强(续)
大数据·前端·vue.js·ai·性能优化·node.js