Node.js 后端开发实战:构建高性能 API

使用 Node.js + Express/Koa 构建企业级后端服务,掌握异步编程、数据库操作、身份认证等核心技术。

作者
后端工程师全栈开发工程师

🚀 Node.js 异步编程模型

Node.js 的核心优势是其非阻塞 I/O 模型。深入理解异步编程是掌握 Node.js 的关键。

💡 核心概念

Node.js 使用事件驱动、非阻塞 I/O 模型,使其轻量且高效。理解 Event Loop 是掌握 Node.js 的关键。

Event Loop 详解

1. Timers 阶段

执行 setTimeoutsetInterval 的回调

⬇️

2. Pending Callbacks 阶段

执行一些系统操作的回调(如 TCP 错误)

⬇️

3. Idle, Prepare 阶段

仅在内部使用

⬇️

4. Poll 阶段

获取新的 I/O 事件,执行 I/O 相关回调

⬇️

5. Check 阶段

执行 setImmediate 的回调

⬇️

6. Close Callbacks 阶段

执行关闭事件的回调(如 socket.on('close', ...)

异步编程演进

回调函数(Callback)

// 回调地狱
fs.readFile('file1.txt', (err, data1) => {
  if (err) return handleError(err)
  
  fs.readFile('file2.txt', (err, data2) => {
    if (err) return handleError(err)
    
    fs.writeFile('output.txt', data1 + data2, (err) => {
      if (err) return handleError(err)
      console.log('Done!')
    })
  })
})

Promise

// 使用 Promise 改进
const readFile = (path) => {
  return new Promise((resolve, reject) => {
    fs.readFile(path, (err, data) => {
      if (err) reject(err)
      else resolve(data)
    })
  })
}

readFile('file1.txt')
  .then(data1 => {
    return readFile('file2.txt').then(data2 => [data1, data2])
  })
  .then(([data1, data2]) => {
    return writeFile('output.txt', data1 + data2)
  })
  .catch(handleError)

Async/Await(推荐)

// 使用 Async/Await(最优雅)
async function mergeFiles() {
  try {
    const data1 = await readFile('file1.txt')
    const data2 = await readFile('file2.txt')
    await writeFile('output.txt', data1 + data2)
    console.log('Done!')
  } catch (err) {
    handleError(err)
  }
}

// 或使用工具函数包装
const fs = require('fs').promises

async function mergeFiles() {
  const data1 = await fs.readFile('file1.txt', 'utf8')
  const data2 = await fs.readFile('file2.txt', 'utf8')
  await fs.writeFile('output.txt', data1 + data2)
  console.log('Done!')
}

⚔️ Express vs Koa 对比

Express 和 Koa 都是 Node.js 主流框架,但设计理念不同。

特性ExpressKoa
异步处理Callback / PromiseAsync/Await(原生支持)
中间件模型线性洋葱模型(更优雅)
错误处理try-catch / 错误中间件try-catch(更直观)
内置功能多(路由、静态文件等)少(只提供核心功能)
社区生态成熟、丰富较新、增长中
学习曲线平缓中等

Express 示例

Express 中间件与路由

const express = require('express')
const app = express()

// 中间件
app.use(express.json())
app.use(logger())
app.use(authMiddleware())

// 路由
app.get('/api/users', async (req, res, next) => {
  try {
    const users = await User.findAll()
    res.json(users)
  } catch (err) {
    next(err)  // 传递错误到错误中间件
  }
})

// 错误中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
  console.error(err)
  res.status(500).json({ error: 'Internal Server Error' })
})

app.listen(3000)

Koa 示例

Koa 中间件(洋葱模型)

const Koa = require('koa')
const Router = require('@koa/router')
const bodyParser = require('koa-bodyparser')
const app = new Koa()
const router = new Router()

// 中间件
app.use(bodyParser())

// 自定义中间件(洋葱模型)
app.use(async (ctx, next) => {
  const start = Date.now()
  await next()  // 执行下一个中间件
  const duration = Date.now() - start
  console.log(`${ctx.method} ${ctx.url} - ${duration}ms`)
})

// 路由
router.get('/api/users', async (ctx) => {
  const users = await User.findAll()
  ctx.body = users
})

app.use(router.routes())
app.use(router.allowedMethods())
app.listen(3000)

💡 选择建议

新项目推荐使用 Koa(更现代、Async/Await 支持更好);旧项目或需要丰富中间件生态时选择 Express

🗄️ 数据库操作与 ORM

Node.js 支持多种数据库,包括关系型(MySQL、PostgreSQL)和 NoSQL(MongoDB、Redis)。

使用 Sequelize(ORM)

Sequelize 模型定义与查询

const { Sequelize, DataTypes } = require('sequelize')
const sequelize = new Sequelize('database', 'username', 'password', {
  host: 'localhost',
  dialect: 'mysql'
})

// 定义模型
const User = sequelize.define('User', {
  id: {
    type: DataTypes.INTEGER,
    autoIncrement: true,
    primaryKey: true
  },
  username: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true
  },
  email: {
    type: DataTypes.STRING,
    allowNull: false,
    unique: true,
    validate: {
      isEmail: true
    }
  },
  password: {
    type: DataTypes.STRING,
    allowNull: false
  }
}, {
  tableName: 'users',
  timestamps: true
})

// 查询
const users = await User.findAll({
  where: { status: 'active' },
  order: [['createdAt', 'DESC']],
  limit: 10
})

// 创建
const user = await User.create({
  username: 'john_doe',
  email: '[email protected]',
  password: hashPassword('password123')
})

使用 Mongoose(MongoDB ODM)

Mongoose Schema 与 Model

const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/mydb')

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
}, { timestamps: true })

// 实例方法
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password)
}

// 静态方法
userSchema.statics.findByEmail = function(email) {
  return this.findOne({ email })
}

const User = mongoose.model('User', userSchema)

// 使用
const user = await User.create({
  username: 'john_doe',
  email: '[email protected]',
  password: await bcrypt.hash('password123', 10)
})

⚠️ 性能提示

使用 ORM 方便但可能有性能损耗。对性能要求高的场景,考虑使用原生查询或查询构建器(如 knex)。

🔐 身份认证与授权

安全的身份认证是后端开发的核心。我们通常使用 JWT(JSON Web Token) 实现无状态认证。

JWT 认证实现

注册与登录

const jwt = require('jsonwebtoken')
const bcrypt = require('bcrypt')

// 注册
router.post('/register', async (ctx) => {
  const { username, email, password } = ctx.request.body
  
  // 验证输入
  if (!username || !email || !password) {
    ctx.status = 400
    ctx.body = { error: 'Missing required fields' }
    return
  }
  
  // 检查用户是否存在
  const existingUser = await User.findOne({ where: { email } })
  if (existingUser) {
    ctx.status = 409
    ctx.body = { error: 'Email already exists' }
    return
  }
  
  // 创建用户(密码自动哈希)
  const user = await User.create({
    username,
    email,
    password: await bcrypt.hash(password, 10)
  })
  
  // 生成 JWT
  const token = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn, '1h' }
  )
  
  ctx.status = 201
  ctx.body = { token }
})

// 登录
router.post('/login', async (ctx) => {
  const { email, password } = ctx.request.body
  
  const user = await User.findOne({ where: { email } })
  if (!user) {
    ctx.status = 401
    ctx.body = { error: 'Invalid credentials' }
    return
  }
  
  const isValidPassword = await bcrypt.compare(password, user.password)
  if (!isValidPassword) {
    ctx.status = 401
    ctx.body = { error: 'Invalid credentials' }
    return
  }
  
  const token = jwt.sign(
    { userId: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn, '1h' }
  )
  
  ctx.body = { token }
})

认证中间件

JWT 验证中间件

// 认证中间件
const authMiddleware = async (ctx, next) => {
  // 从 Header 获取 Token
  const authHeader = ctx.headers.authorization
  if (!authHeader) {
    ctx.status = 401
    ctx.body = { error: 'No token provided' }
    return
  }
  
  const token = authHeader.split(' ')[1]  // "Bearer <token>"
  
  try {
    // 验证 Token
    const decoded = jwt.verify(token, process.env.JWT_SECRET)
    ctx.state.user = decoded  // 将用户信息存储在 ctx.state
    await next()
  } catch (err) {
    ctx.status = 401
    ctx.body = { error: 'Invalid or expired token' }
  }
}

// 使用中间件
router.get('/profile', authMiddleware, async (ctx) => {
  const user = await User.findByPk(ctx.state.user.userId)
  ctx.body = { id: user.id, username: user.username, email: user.email }
})

📡️ RESTFUL API 设计

良好的 API 设计至关重要。遵循 REST 原则,使 API 直观、一致、易于使用。

1

使用正确的 HTTP 方法

GET - 获取资源;POST - 创建资源;PUT - 更新资源;DELETE - 删除资源

2

使用名词而非动词

/users/getUsers

3

使用复数名词

/users/user

4

版本化 API

/api/v1/users

5

使用正确的 HTTP 状态码

200 - 成功;201 - 创建成功;400 - 错误请求;401 - 未认证;404 - 未找到

完整的 RESTful API 示例

// 用户资源 API
router.get('/api/v1/users', async (ctx) => {
  // 获取用户列表(支持分页、过滤)
  const { page = 1, limit = 10, status } = ctx.query
  const offset = (page - 1) * limit
  
  const where = {}
  if (status) where.status = status
  
  const { count, rows } = await User.findAndCountAll({
    where,
    limit,
    offset,
    order: [['createdAt', 'DESC']]
  })
  
  ctx.body = {
    data: rows,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: count,
      totalPages: Math.ceil(count / limit)
    }
  }
})

router.get('/api/v1/users/:id', async (ctx) => {
  // 获取单个用户
  const user = await User.findByPk(ctx.params.id)
  if (!user) {
    ctx.status = 404
    ctx.body = { error: 'User not found' }
    return
  }
  ctx.body = user
})

router.post('/api/v1/users', async (ctx) => {
  // 创建用户
  const { username, email, password } = ctx.request.body
  const user = await User.create({ username, email, password })
  ctx.status = 201
  ctx.body = user
})

router.put('/api/v1/users/:id', authMiddleware, async (ctx) => {
  // 更新用户(需要认证 + 授权)
  const user = await User.findByPk(ctx.params.id)
  if (!user) {
    ctx.status = 404
    ctx.body = { error: 'User not found' }
    return
  }
  
  // 检查权限(只能修改自己的信息)
  if (user.id !== ctx.state.user.userId) {
    ctx.status = 403
    ctx.body = { error: 'Forbidden' }
    return
  }
  
  await user.update(ctx.request.body)
  ctx.body = user
})

router.delete('/api/v1/users/:id', authMiddleware, async (ctx) => {
  // 删除用户(需要认证 + 授权)
  const user = await User.findByPk(ctx.params.id)
  if (!user) {
    ctx.status = 404
    ctx.body = { error: 'User not found' }
    return
  }
  
  // 检查权限(只能删除自己的账号,或管理员)
  if (user.id !== ctx.state.user.userId && !ctx.state.user.isAdmin) {
    ctx.status = 403
    ctx.body = { error: 'Forbidden' }
    return
  }
  
  await user.destroy()
  ctx.status = 204
})

⚡ 性能优化

Node.js 性能优化涉及多个方面:代码优化、缓存策略、数据库优化、负载均衡等。

使用 Redis 缓存

Redis 缓存策略

const redis = require('redis')
const client = redis.createClient()

// 缓存中间件
const cacheMiddleware = (duration) => {
  return async (ctx, next) => {
    const key = ctx.url
    
    // 尝试从缓存获取
    client.get(key, async (err, data) => {
      if (data) {
        // 缓存命中
        ctx.body = JSON.parse(data)
      } else {
        // 缓存未命中,执行下一个中间件
        await next()
        
        // 将结果存入缓存
        client.setex(key, duration, JSON.stringify(ctx.body))
      }
    })
  }
}

// 使用缓存
router.get('/api/users', cacheMiddleware(60), async (ctx) => {
  const users = await User.findAll()
  ctx.body = users
})

数据库查询优化

🎯 常见性能问题

  • N+1 查询问题:使用 include(Sequelize)或 populate(Mongoose)预加载关联数据
  • 缺少索引:为经常查询的字段添加索引
  • 返回过多数据:使用分页、字段选择(SELECT)限制返回数据
  • 慢查询:使用 EXPLAIN 分析查询计划,优化 SQL

解决 N+1 问题

// ❌ 错误示例(N+1 查询)
const users = await User.findAll()
for (const user of users) {
  const posts = await Post.findAll({ where: { userId: user.id } })
  console.log(user.username, posts.length)
}

// ✅ 正确示例(使用 include 预加载)
const users = await User.findAll({
  include: [{
    model: Post,
    as: 'posts'
  }]
})

// ✅ 正确示例(使用 DataLoader 批量加载)
import DataLoader from 'dataloader'

const batchUsers = async (ids) => {
  const users = await User.findAll({ where: { id: ids } })
  return ids.map(id => users.find(user => user.id === id))
}

const userLoader = new DataLoader(batchUsers)

const posts = await Post.findAll()
const authors = await Promise.all(
  posts.map(post => userLoader.load(post.userId))
)

使用集群模式

Cluster 模块利用多核 CPU

const cluster = require('cluster')
const numCPUs = require('os').cpus().length

if (cluster.isMaster) {
  // 主进程:fork 工作进程
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork()
  }
  
  cluster.on('exit', (worker) => {
    console.log(`Worker ${worker.process.pid} died`)
    cluster.fork()  // 重启工作进程
  })
} else {
  // 工作进程:启动服务器
  const app = require('./app.js')
  app.listen(3000, () => {
    console.log(`Worker ${process.pid} started`)
  })
}

🚀 部署与监控

Node.js 应用部署需要考虑进程管理、日志记录、性能监控等。

使用 PM2 管理进程

PM2 配置与使用

# 安装 PM2
npm install pm2 -g

# 启动应用
pm2 start app.js --name "my-api"

# 查看进程状态
pm2 status

# 查看日志
pm2 logs my-api

# 重启应用
pm2 restart my-api

# 停止应用
pm2 stop my-api

# 删除应用
pm2 delete my-api

# 生成启动脚本(系统重启后自动启动)
pm2 startup
pm2 save

PM2 配置文件(ecosystem.config.js)

module.exports = {
  apps: [{
    name: 'my-api',
    script: 'app.js',
    instances: 'max',  // 使用所有 CPU 核心
    exec_mode: 'cluster',  // 集群模式
    env: {
      NODE_ENV: 'development'
    },
    env_production: {
      NODE_ENV: 'production',
      NODE_OPTIONS: '--max-old-space-size=4096'  // 增加内存限制
    },
    error_file: 'logs/error.log',
    out_file: 'logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    merge_logs: true,
    autorestart: true,
    watch: false
  }]
}

性能监控

📊 PM2 Monitoring

内置监控:pm2 monit

📈 New Relic

商业 APM 工具,功能强大

🔍 Elasticsearch + Logstash + Kibana (ELK)

日志收集、存储、可视化

📝 总结

Node.js 后端开发涉及的知识面很广,需要持续学习和实践。

核心要点

  • 异步编程:掌握 Callback、Promise、Async/Await
  • 框架选择:Express(成熟)vs Koa(现代)
  • 数据库:使用 ORM(Sequelize、Mongoose)提高开发效率
  • 认证:使用 JWT 实现无状态认证
  • API 设计:遵循 RESTful 原则
  • 性能优化:缓存、数据库优化、集群模式
  • 部署:使用 PM2 管理进程,监控应用性能

希望这篇文章能帮助你构建高性能、可扩展的 Node.js 后端服务!🚀