首页  |  学习总览  |  ← 返回专题总览 进阶专题 12 · Node.js

Node.js 实战指南 🔥 全栈必修

从 0 到 1 搭建服务、Express/Koa 框架、数据库、进程守护、Docker 部署、上线完整流程
学习时长:约 25 小时 | 前置:JavaScript 基础 | 产出:完整的后端服务项目

一、Node.js 基础

1.1 模块系统

// CommonJS 模块(Node.js 默认)
// 导出
module.exports = { add, subtract };
exports.hello = function() {};

// 导入
const { add } = require('./math');
const fs = require('fs');

// ES Module(需要 package.json 中设置 "type": "module")
export function add() {}
export default class MyClass {}

import { add } from './math.js';
import MyClass from './MyClass.js';

1.2 核心模块

模块用途常用 API
fs文件系统readFile, writeFile, mkdir, stat
path路径处理join, resolve, dirname, basename
httpHTTP 服务createServer, request, response
events事件触发on, emit, once, removeListener
stream流操作Readable, Writable, transform
crypto加密解密createHash, createCipher, randomBytes
os操作系统cpus, totalmem, hostname, platform
child_process子进程exec, spawn, fork

二、从 0 搭建 HTTP 服务

2.1 最简 HTTP 服务器

// server.js
const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000/');
});

// 运行: node server.js

2.2 路由处理

const http = require('http');
const url = require('url');

const routes = {
  '/': (req, res) => {
    res.end('Home Page');
  },
  '/api/users': (req, res) => {
    res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
  },
  '/api/posts': (req, res) => {
    res.end(JSON.stringify([{ id: 1, title: 'Hello' }]));
  }
};

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const handler = routes[parsedUrl.pathname];
  
  if (handler) {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    handler(req, res);
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(3000);

2.3 处理 POST 请求

const http = require('http');

const server = http.createServer((req, res) => {
  if (req.method === 'POST' && req.url === '/api/data') {
    let body = '';
    
    // 接收数据
    req.on('data', chunk => {
      body += chunk.toString();
    });
    
    // 数据接收完毕
    req.on('end', () => {
      const data = JSON.parse(body);
      console.log('Received:', data);
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ success: true, data }));
    });
  }
});

server.listen(3000);

三、Express 框架实战

3.1 项目初始化

// 初始化项目 mkdir my-express-app && cd my-express-app npm init -y npm install express // 目录结构 my-express-app/ ├── package.json ├── app.js // 入口文件 ├── routes/ // 路由 │ ├── index.js │ └── users.js ├── controllers/ // 控制器 │ └── userController.js ├── models/ // 数据模型 │ └── userModel.js ├── middleware/ // 中间件 │ └── auth.js └── config/ // 配置文件 └── db.js

3.2 创建 Express 应用

// app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// 内置中间件
app.use(express.json());                           // 解析 JSON 请求体
app.use(express.urlencoded({ extended: true })); // 解析表单数据
app.use(express.static('public'));               // 静态文件服务

// 日志中间件
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
  next();
});

// 路由
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to Express' });
});

// 模块化路由
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: 'Something went wrong!' });
});

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

module.exports = app;

3.3 路由与控制器

// routes/users.js
const router = require('express').Router();
const userController = require('../controllers/userController');

router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', userController.createUser);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);

module.exports = router;

// controllers/userController.js
const userModel = require('../models/userModel');

exports.getAllUsers = async (req, res) => {
  try {
    const users = await userModel.findAll();
    res.json({ success: true, data: users });
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  }
};

exports.getUserById = async (req, res) => {
  try {
    const user = await model.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }
    res.json({ success: true, data: user });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.createUser = async (req, res) => {
  try {
    const user = await model.create(req.body);
    res.status(201).json({ success: true, data: user });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

3.4 中间件开发

// middleware/auth.js
const jwt = require('jsonwebtoken');

// JWT 认证中间件
exports.authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'Access denied' });
  }
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

// 角色授权中间件
exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

// 使用
router.get('/admin', authenticate, authorize('admin'), adminController.getDashboard);

常用中间件

中间件用途
cors跨域资源共享
helmet安全头设置
morganHTTP 请求日志
compression响应压缩
express-rate-limit请求限流
multer文件上传
express-validator数据验证

四、数据库操作

4.1 MySQL 集成

// config/db.js
const mysql = require('mysql2/promise');

const pool = mysql.createPool({
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'root',
  password: process.env.DB_PASSWORD || '',
  database: process.env.DB_NAME || 'myapp',
  waitForConnections: true,
  connectionLimit: 10,
  queueLimit: 0
});

// 测试连接
pool.getConnection()
  .then(conn => {
    console.log('Database connected');
    conn.release();
  })
  .catch(err => {
    console.error('Database connection failed:', err);
  });

module.exports = pool;

// models/userModel.js
const pool = require('../config/db');

class UserModel {
  static async findAll() {
    const [rows] = await pool.query('SELECT * FROM users');
    return rows;
  }
  
  static async findById(id) {
    const [rows] = await pool.query('SELECT * FROM users WHERE id = ?', [id]);
    return rows[0];
  }
  
  static async create(data) {
    const [result] = await pool.query('INSERT INTO users SET ?', data);
    return { id: result.insertId, ...data };
  }
  
  static async update(id, data) {
    await pool.query('UPDATE users SET ? WHERE id = ?', [data, id]);
    return this.findById(id);
  }
  
  static async delete(id) {
    await pool.query('DELETE FROM users WHERE id = ?', [id]);
  }
}

module.exports = UserModel;

4.2 MongoDB 集成(Mongoose)

// config/db.js
const mongoose = require('mongoose');

mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp')
  .then(() => console.log('MongoDB connected'))
  .catch(err => console.error('MongoDB connection error:', err));

// models/userModel.js
const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  name: { type: String, required: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' },
  createdAt: { type: Date, default: Date.now }
});

// 索引
userSchema.index({ email: 1 });

// 虚拟字段
userSchema.virtual('profile').get(function() {
  return { name: this.name, email: this.email };
});

// 中间件(钩子)
userSchema.pre('save', async function(next) {
  if (this.isModified('password')) {
    this.password = await bcrypt.hash(this.password, 12);
  }
  next();
});

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

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

module.exports = mongoose.model('User', userSchema);

4.3 Redis 缓存

// config/redis.js
const Redis = require('ioredis');
const redis = new Redis({
  host: process.env.REDIS_HOST || '127.0.0.1',
  port: process.env.REDIS_PORT || 6379,
  password: process.env.REDIS_PASSWORD || undefined
});

redis.on('connect', () => console.log('Redis connected'));
redis.on('error', (err) => console.error('Redis error:', err));

module.exports = redis;

// 使用示例:缓存用户数据
const redis = require('../config/redis');

async function getUserWithCache(userId) {
  const cacheKey = `user:${userId}`;
  
  // 1. 先从缓存读取
  const cached = await redis.get(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  
  // 2. 缓存未命中,从数据库读取
  const user = await UserModel.findById(userId);
  if (user) {
    // 3. 写入缓存,设置过期时间
    await redis.setex(cacheKey, 3600, JSON.stringify(user)); // 1小时
  }
  
  return user;
}

// 清除缓存(更新用户时)
async function updateUser(userId, data) {
  await UserModel.update(userId, data);
  await redis.del(`user:${userId}`); // 删除缓存
}

五、用户认证实战

5.1 JWT 认证完整流程

// utils/jwt.js
const jwt = require('jsonwebtoken');

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const JWT_EXPIRES = '7d';

exports.generateToken = (payload) => {
  return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES });
};

exports.verifyToken = (token) => {
  return jwt.verify(token, JWT_SECRET);
};

// controllers/authController.js
const bcrypt = require('bcryptjs');
const User = require('../models/userModel');
const { generateToken } = require('../utils/jwt');

exports.register = async (req, res) => {
  try {
    const { email, password, name } = req.body;
    
    // 检查用户是否已存在
    const existingUser = await User.findByEmail(email);
    if (existingUser) {
      return res.status(400).json({ error: 'Email already registered' });
    }
    
    // 加密密码
    const hashedPassword = await bcrypt.hash(password, 12);
    
    // 创建用户
    const user = await User.create({ email, password: hashedPassword, name });
    
    // 生成 Token
    const token = generateToken({ id: user.id, email: user.email });
    
    res.status(201).json({ success: true, token, user: { id: user.id, email, name } });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.login = async (req, res) => {
  try {
    const { email, password } = req.body;
    
    // 查找用户
    const user = await User.findByEmail(email);
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    // 验证密码
    const isValid = await bcrypt.compare(password, user.password);
    if (!isValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    
    // 生成 Token
    const token = generateToken({ id: user.id, email: user.email, role: user.role });
    
    res.json({ success: true, token, user: { id: user.id, email, name: user.name } });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

六、WebSocket 实时通信

// server.js
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');

const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } });

// 在线用户
const onlineUsers = new Map();

io.on('connection', (socket) => {
  console.log('User connected:', socket.id);
  
  // 用户加入
  socket.on('join', (userId) => {
    onlineUsers.set(userId, socket.id);
    socket.join('room:' + userId);
    io.emit('onlineUsers', Array.from(onlineUsers.keys()));
  });
  
  // 私聊消息
  socket.on('privateMessage', ({ to, message }) => {
    const toSocketId = onlineUsers.get(to);
    if (toSocketId) {
      io.to(toSocketId).emit('newMessage', {
        from: socket.id,
        message,
        timestamp: new Date()
      });
    }
  });
  
  // 群聊
  socket.on('joinRoom', (room) => {
    socket.join(room);
    io.to(room).emit('userJoined', { user: socket.id, room });
  });
  
  socket.on('roomMessage', ({ room, message }) => {
    io.to(room).emit('newRoomMessage', {
      user: socket.id,
      message,
      timestamp: new Date()
    });
  });
  
  // 断开连接
  socket.on('disconnect', () => {
    for (const [userId, socketId] of onlineUsers.entries()) {
      if (socketId === socket.id) {
        onlineUsers.delete(userId);
        break;
      }
    }
    io.emit('onlineUsers', Array.from(onlineUsers.keys()));
  });
});

server.listen(3000);

七、文件上传与处理

// middleware/upload.js
const multer = require('multer');
const path = require('path');

// 存储配置
const storage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'uploads/');
  },
  filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
    cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
  }
});

// 文件过滤
const fileFilter = (req, file, cb) => {
  const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'application/pdf'];
  if (allowedTypes.includes(file.mimetype)) {
    cb(null, true);
  } else {
    cb(new Error('Invalid file type'), false);
  }
};

const upload = multer({
  storage,
  fileFilter,
  limits: { fileSize: 5 * 1024 * 1024 } // 5MB
});

module.exports = upload;

// 使用
app.post('/api/upload', upload.single('file'), (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'No file uploaded' });
  }
  res.json({
    success: true,
    file: {
      filename: req.file.filename,
      path: req.file.path,
      size: req.file.size
    }
  });
});

// 多文件上传
app.post('/api/upload/multiple', upload.array('files', 10), (req, res) => {
  res.json({ files: req.files });
});

八、进程守护与部署

8.1 PM2 进程守护

// 安装 PM2
npm install -g pm2

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

// 常用命令
pm2 list                    // 查看所有进程
pm2 logs my-api             // 查看日志
pm2 monit                   // 监控 CPU/内存
pm2 restart my-api          // 重启
pm2 stop my-api             // 停止
pm2 delete my-api           // 删除

// ecosystem.config.js(PM2 配置文件)
module.exports = {
  apps: [{
    name: 'my-api',
    script: './app.js',
    instances: 'max',       // 集群模式,CPU 核心数
    exec_mode: 'cluster',
    max_memory_restart: '500M',
    env: {
      NODE_ENV: 'development',
      PORT: 3000
    },
    env_production: {
      NODE_ENV: 'production',
      PORT: 8080
    },
    error_file: './logs/error.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    merge_logs: true
  }]
};

// 使用配置文件启动
pm2 start ecosystem.config.js --env production

// 开机自启
pm2 startup
pm2 save

8.2 Nginx 反向代理

// /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name example.com;

    // 静态文件
    location /static/ {
        root /var/www/myapp/public;
        expires 30d;
    }

    // API 反向代理
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    // WebSocket 支持
    location /socket.io/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

// 启用站点
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

8.3 Docker 部署

// Dockerfile
FROM node:18-alpine

WORKDIR /app

// 复制依赖文件
COPY package*.json ./

// 安装依赖
RUN npm ci --only=production

// 复制源代码
COPY . .

// 暴露端口
EXPOSE 8080

// 健康检查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD node healthcheck.js || exit 1

// 启动命令
CMD ["node", "app.js"]

// .dockerignore
node_modules
npm-debug.log
.git
.env
uploads/*
!uploads/.gitkeep

// docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
    restart: unless-stopped
    volumes:
      - ./uploads:/app/uploads

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: myapp
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

volumes:
  mysql_data:
  redis_data:

// 构建并启动
docker-compose up -d --build

// 查看日志
docker-compose logs -f app

九、CI/CD 自动化部署

// .github/workflows/deploy.yml
name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Run tests
        run: npm test
      
      - name: Build Docker image
        run: docker build -t myapp:${{ github.sha }} .
      
      - name: Deploy to server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /var/www/myapp
            docker-compose pull
            docker-compose up -d
            docker system prune -f

十、性能优化与监控

10.1 性能优化

常见优化手段

优化点方法
数据库索引优化、连接池、查询优化、读写分离
缓存Redis 缓存热点数据、HTTP 缓存头
异步耗时操作异步处理、消息队列
压缩Gzip/Brotli 压缩、图片优化
集群PM2 集群模式、负载均衡
CDN静态资源 CDN 加速

10.2 日志与监控

// 使用 Winston 日志
const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// 使用
logger.info('Server started');
logger.error('Database connection failed', { error: err });

// 性能监控(使用 prom-client)
const client = require('prom-client');
const httpRequestDuration = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Duration of HTTP requests in seconds',
  labelNames: ['method', 'route', 'status']
});

app.use((req, res, next) => {
  const end = httpRequestDuration.startTimer();
  res.on('finish', () => end({ method: req.method, route: req.path, status: res.statusCode }));
  next();
});

app.get('/metrics', async (req, res) => {
  res.set('Content-Type', client.register.contentType);
  res.end(await client.register.metrics());
});

十一、实战案例

11.1 RESTful API 完整示例

// 完整的博客 API
const express = require('express');
const router = express.Router();
const { authenticate, authorize } = require('../middleware/auth');
const { body, validationResult } = require('express-validator');

// 获取文章列表(支持分页、搜索、排序)
router.get('/', async (req, res) => {
  const { page = 1, limit = 10, search, sort = '-createdAt' } = req.query;
  const query = search ? { title: new RegExp(search, 'i') } : {};
  
  const [posts, total] = await Promise.all([
    Post.find(query).sort(sort).skip((page - 1) * limit).limit(Number(limit)),
    Post.countDocuments(query)
  ]);
  
  res.json({
    success: true,
    data: posts,
    pagination: { page: Number(page), limit: Number(limit), total, pages: Math.ceil(total / limit) }
  });
});

// 创建文章
router.post('/'span>,
  authenticate,
  [
    body('title').notEmpty().withMessage('Title is required'),
    body('content').notEmpty().withMessage('Content is required')
  ],
  async (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }
    
    const post = await Post.create({ ...req.body, author: req.user.id });
    res.status(201).json({ success: true, data: post });
  }
);

11.2 定时任务

// 使用 node-cron
const cron = require('node-cron');

// 每天凌晨清理过期 token
cron.schedule('0 0 * * *', async () => {
  console.log('Cleaning expired tokens...');
  await Token.deleteMany({ expiresAt: { $lt: new Date() } });
});

// 每小时生成报表
cron.schedule('0 * * * *', async () => {
  const stats = await generateHourlyReport();
  await sendReportEmail(stats);
});

// 每 5 分钟检查服务状态
cron.schedule('*/5 * * * *', async () => {
  const health = await checkServicesHealth();
  if (!health.healthy) {
    await sendAlert(health);
  }
});

11.3 邮件发送

// utils/email.js
const nodemailer = require('nodemailer');

const transporter = nodemailer.createTransport({
  host: process.env.SMTP_HOST,
  port: process.env.SMTP_PORT,
  secure: true,
  auth: {
    user: process.env.SMTP_USER,
    pass: process.env.SMTP_PASS
  }
});

exports.sendEmail = async ({ to, subject, html }) => {
  await transporter.sendMail({
    from: `"MyApp" <${process.env.SMTP_USER}>`,
    to,
    subject,
    html
  });
};

// 使用
await sendEmail({
  to: user.email,
  subject: 'Welcome to MyApp',
  html: `<h1>Welcome, ${user.name}!</h1>`
});