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 |
| http | HTTP 服务 | 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 | 安全头设置 |
| morgan | HTTP 请求日志 |
| 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>`
});