Node.js和Express后端开发完全指南
Node.js已经成为现代后端开发的重要工具,它允许我们使用JavaScript编写服务器端代码,实现全栈JavaScript开发。Express是Node.js生态系统中最流行的Web框架,它提供了简洁、灵活的API,帮助我们快速构建Web应用。本文将深入探讨Node.js和Express的使用技巧和最佳实践,帮助你构建高性能、可维护的后端应用。
1. Node.js基础
1.1 什么是Node.js?
Node.js是一个基于Chrome V8引擎的JavaScript运行时,它允许我们在服务器端运行JavaScript代码。Node.js采用事件驱动、非阻塞I/O模型,使其轻量且高效。
1.2 安装Node.js
Windows和macOS: 从Node.js官网下载安装包。
Linux: 使用包管理器安装。
# Ubuntu/Debiancurl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -sudo apt-get install -y nodejs
# CentOS/RHELcurl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -sudo yum install -y nodejs1.3 NPM和Yarn
NPM: Node.js自带的包管理器。
Yarn: Facebook开发的包管理器,提供更快的安装速度和更可靠的依赖管理。
# 安装Yarnnpm install -g yarn1.4 基本模块
内置模块:
fs:文件系统操作path:路径处理http:HTTP服务器和客户端https:HTTPS服务器和客户端events:事件处理stream:流处理buffer:缓冲区处理util:实用工具
使用内置模块:
const fs = require('fs');const path = require('path');const http = require('http');2. Express基础
2.1 什么是Express?
Express是一个基于Node.js的Web应用框架,它提供了简洁、灵活的API,帮助我们快速构建Web应用。Express是Node.js生态系统中最流行的Web框架,被广泛用于构建RESTful API、Web应用和单页应用的后端。
2.2 安装Express
# 使用NPMnpm init -ynpm install express
# 使用Yarnyarn init -yyarn add express2.3 基本用法
创建简单的Express应用:
const express = require('express');const app = express();const port = 3000;
// 中间件app.use(express.json());app.use(express.urlencoded({ extended: true }));
// 路由app.get('/', (req, res) => { res.send('Hello World!');});
// 启动服务器app.listen(port, () => { console.log(`Server running at http://localhost:${port}`);});2.4 路由
基本路由:
// GET请求app.get('/api/users', (req, res) => { res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);});
// POST请求app.post('/api/users', (req, res) => { const user = req.body; res.status(201).json(user);});
// PUT请求app.put('/api/users/:id', (req, res) => { const { id } = req.params; const user = req.body; res.json({ id, ...user });});
// DELETE请求app.delete('/api/users/:id', (req, res) => { const { id } = req.params; res.status(204).send();});路由参数:
app.get('/api/users/:id', (req, res) => { const { id } = req.params; res.json({ id, name: 'John' });});查询参数:
app.get('/api/users', (req, res) => { const { page, limit } = req.query; res.json({ page, limit, users: [] });});2.5 中间件
内置中间件:
// 解析JSON请求体app.use(express.json());
// 解析URL编码的请求体app.use(express.urlencoded({ extended: true }));
// 提供静态文件app.use(express.static('public'));自定义中间件:
// 日志中间件const logger = (req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next();};
app.use(logger);
// 认证中间件const auth = (req, res, next) => { const token = req.headers.authorization; if (!token) { return res.status(401).json({ message: 'Unauthorized' }); } // 验证token next();};
app.use('/api', auth);错误处理中间件:
app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ message: 'Internal Server Error' });});3. 高级Express功能
3.1 路由模块化
创建路由模块:
const express = require('express');const router = express.Router();
router.get('/', (req, res) => { res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);});
router.post('/', (req, res) => { const user = req.body; res.status(201).json(user);});
module.exports = router;使用路由模块:
const express = require('express');const app = express();const usersRouter = require('./routes/users');
app.use('/api/users', usersRouter);3.2 中间件链
const express = require('express');const app = express();
// 中间件1const middleware1 = (req, res, next) => { console.log('Middleware 1'); next();};
// 中间件2const middleware2 = (req, res, next) => { console.log('Middleware 2'); next();};
// 应用中间件链app.get('/api/users', middleware1, middleware2, (req, res) => { res.json([{ id: 1, name: 'John' }]);});3.3 错误处理
同步错误:
app.get('/api/error', (req, res) => { throw new Error('Sync error');});异步错误:
// 方法1:使用try-catchapp.get('/api/async-error', async (req, res, next) => { try { const result = await someAsyncOperation(); res.json(result); } catch (error) { next(error); }});
// 方法2:直接传递错误app.get('/api/async-error', async (req, res, next) => { const result = await someAsyncOperation().catch(next); res.json(result);});自定义错误类:
class AppError extends Error { constructor(message, statusCode) { super(message); this.statusCode = statusCode; this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error'; this.isOperational = true;
Error.captureStackTrace(this, this.constructor); }}
// 使用自定义错误app.get('/api/error', (req, res, next) => { next(new AppError('Not found', 404));});
// 错误处理中间件app.use((err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error';
res.status(err.statusCode).json({ status: err.status, message: err.message });});3.4 环境变量
使用dotenv:
npm install dotenv创建.env文件:
NODE_ENV=developmentPORT=3000DATABASE_URL=mongodb://localhost:27017/myappJWT_SECRET=your-secret-key使用环境变量:
require('dotenv').config();
const express = require('express');const app = express();
const port = process.env.PORT || 3000;const databaseUrl = process.env.DATABASE_URL;
app.listen(port, () => { console.log(`Server running at http://localhost:${port}`);});4. 数据库集成
4.1 MongoDB
安装Mongoose:
npm install mongoose连接MongoDB:
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true, useUnifiedTopology: true}).then(() => { console.log('Connected to MongoDB');}).catch((error) => { console.error('Error connecting to MongoDB:', error);});创建模型:
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 }});
const User = mongoose.model('User', userSchema);
module.exports = User;使用模型:
const User = require('./models/User');
// 创建用户app.post('/api/users', async (req, res) => { try { const user = new User(req.body); await user.save(); res.status(201).json(user); } catch (error) { res.status(400).json({ message: error.message }); }});
// 获取所有用户app.get('/api/users', async (req, res) => { try { const users = await User.find(); res.json(users); } catch (error) { res.status(500).json({ message: error.message }); }});
// 获取单个用户app.get('/api/users/:id', async (req, res) => { try { const user = await User.findById(req.params.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } res.json(user); } catch (error) { res.status(500).json({ message: error.message }); }});
// 更新用户app.put('/api/users/:id', async (req, res) => { try { const user = await User.findByIdAndUpdate(req.params.id, req.body, { new: true }); if (!user) { return res.status(404).json({ message: 'User not found' }); } res.json(user); } catch (error) { res.status(400).json({ message: error.message }); }});
// 删除用户app.delete('/api/users/:id', async (req, res) => { try { const user = await User.findByIdAndDelete(req.params.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } res.status(204).send(); } catch (error) { res.status(500).json({ message: error.message }); }});4.2 PostgreSQL
安装Sequelize:
npm install sequelize pg pg-hstore连接PostgreSQL:
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(process.env.DATABASE_URL, { dialect: 'postgres', logging: false});
sequelize.authenticate().then(() => { console.log('Connected to PostgreSQL');}).catch((error) => { console.error('Error connecting to PostgreSQL:', error);});创建模型:
const { DataTypes } = require('sequelize');const sequelize = require('./sequelize');
const User = sequelize.define('User', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: { type: DataTypes.STRING, allowNull: false }, email: { type: DataTypes.STRING, allowNull: false, unique: true }, password: { type: DataTypes.STRING, allowNull: false }});
module.exports = User;使用模型:
const User = require('./models/User');
// 创建用户app.post('/api/users', async (req, res) => { try { const user = await User.create(req.body); res.status(201).json(user); } catch (error) { res.status(400).json({ message: error.message }); }});
// 获取所有用户app.get('/api/users', async (req, res) => { try { const users = await User.findAll(); res.json(users); } catch (error) { res.status(500).json({ message: error.message }); }});
// 获取单个用户app.get('/api/users/:id', async (req, res) => { try { const user = await User.findByPk(req.params.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } res.json(user); } catch (error) { res.status(500).json({ message: error.message }); }});
// 更新用户app.put('/api/users/:id', async (req, res) => { try { const user = await User.findByPk(req.params.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } await user.update(req.body); res.json(user); } catch (error) { res.status(400).json({ message: error.message }); }});
// 删除用户app.delete('/api/users/:id', async (req, res) => { try { const user = await User.findByPk(req.params.id); if (!user) { return res.status(404).json({ message: 'User not found' }); } await user.destroy(); res.status(204).send(); } catch (error) { res.status(500).json({ message: error.message }); }});4.3 MySQL
安装Sequelize:
npm install sequelize mysql2连接MySQL:
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize(process.env.DATABASE_URL, { dialect: 'mysql', logging: false});
sequelize.authenticate().then(() => { console.log('Connected to MySQL');}).catch((error) => { console.error('Error connecting to MySQL:', error);});创建模型和使用方法与PostgreSQL类似。
5. 认证和授权
5.1 JWT认证
安装jsonwebtoken:
npm install jsonwebtoken bcryptjs生成和验证JWT:
const jwt = require('jsonwebtoken');const bcrypt = require('bcryptjs');
// 生成tokenconst generateToken = (userId) => { return jwt.sign({ id: userId }, process.env.JWT_SECRET, { expiresIn: '1h' });};
// 验证tokenconst verifyToken = (token) => { return jwt.verify(token, process.env.JWT_SECRET);};
// 密码加密const hashPassword = async (password) => { const salt = await bcrypt.genSalt(10); return await bcrypt.hash(password, salt);};
// 密码验证const comparePassword = async (password, hashedPassword) => { return await bcrypt.compare(password, hashedPassword);};认证流程:
const User = require('./models/User');const { generateToken, hashPassword, comparePassword } = require('./utils/auth');
// 注册app.post('/api/auth/register', async (req, res) => { try { const { name, email, password } = req.body;
// 检查用户是否存在 const existingUser = await User.findOne({ email }); if (existingUser) { return res.status(400).json({ message: 'User already exists' }); }
// 密码加密 const hashedPassword = await hashPassword(password);
// 创建用户 const user = await User.create({ name, email, password: hashedPassword });
// 生成token const token = generateToken(user.id);
res.status(201).json({ user, token }); } catch (error) { res.status(400).json({ message: error.message }); }});
// 登录app.post('/api/auth/login', async (req, res) => { try { const { email, password } = req.body;
// 检查用户是否存在 const user = await User.findOne({ email }); if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); }
// 验证密码 const isPasswordValid = await comparePassword(password, user.password); if (!isPasswordValid) { return res.status(401).json({ message: 'Invalid email or password' }); }
// 生成token const token = generateToken(user.id);
res.json({ user, token }); } catch (error) { res.status(400).json({ message: error.message }); }});
// 认证中间件const auth = async (req, res, next) => { try { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ message: 'Access token required' }); }
const decoded = verifyToken(token); const user = await User.findById(decoded.id); if (!user) { return res.status(401).json({ message: 'Invalid token' }); }
req.user = user; next(); } catch (error) { res.status(401).json({ message: 'Invalid token' }); }};
// 保护路由app.get('/api/profile', auth, (req, res) => { res.json({ user: req.user });});5.2 OAuth认证
安装passport:
npm install passport passport-google-oauth20配置Google OAuth:
const passport = require('passport');const GoogleStrategy = require('passport-google-oauth20').Strategy;const User = require('./models/User');
passport.use(new GoogleStrategy({ clientID: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, callbackURL: '/api/auth/google/callback'}, async (accessToken, refreshToken, profile, done) => { try { // 检查用户是否存在 let user = await User.findOne({ googleId: profile.id }); if (!user) { // 创建新用户 user = await User.create({ googleId: profile.id, name: profile.displayName, email: profile.emails[0].value }); } done(null, user); } catch (error) { done(error, null); }}));
passport.serializeUser((user, done) => { done(null, user.id);});
passport.deserializeUser(async (id, done) => { try { const user = await User.findById(id); done(null, user); } catch (error) { done(error, null); }});OAuth路由:
const passport = require('passport');
// 发起OAuth认证app.get('/api/auth/google', passport.authenticate('google', { scope: ['profile', 'email']}));
// OAuth回调app.get('/api/auth/google/callback', passport.authenticate('google', { failureRedirect: '/api/auth/failure'}), (req, res) => { // 生成token const token = generateToken(req.user.id); res.json({ user: req.user, token });});
// 认证失败app.get('/api/auth/failure', (req, res) => { res.status(401).json({ message: 'Authentication failed' });});6. 性能优化
6.1 数据库优化
索引: 为频繁查询的字段添加索引。
查询优化:
- 只查询需要的字段
- 使用分页
- 避免N+1查询
连接池: 使用连接池管理数据库连接。
6.2 缓存
安装redis:
npm install redis使用Redis缓存:
const redis = require('redis');
const redisClient = redis.createClient({ url: process.env.REDIS_URL});
redisClient.connect().then(() => { console.log('Connected to Redis');}).catch((error) => { console.error('Error connecting to Redis:', error);});
// 缓存中间件const cache = (duration) => { return async (req, res, next) => { const key = `cache:${req.originalUrl}`;
try { const cachedData = await redisClient.get(key); if (cachedData) { return res.json(JSON.parse(cachedData)); }
const originalSend = res.json; res.json = async (data) => { await redisClient.set(key, JSON.stringify(data), { EX: duration }); originalSend.call(res, data); };
next(); } catch (error) { next(); } };};
// 应用缓存app.get('/api/users', cache(3600), async (req, res) => { const users = await User.find(); res.json(users);});6.3 负载均衡
使用PM2:
npm install -g pm2启动应用:
pm run buildpm run start:prod配置PM2:
module.exports = { apps: [ { name: 'myapp', script: 'index.js', instances: 'max', exec_mode: 'cluster', env: { NODE_ENV: 'development' }, env_production: { NODE_ENV: 'production' } } ]};启动PM2:
pm run buildpm run start:prod6.4 代码优化
使用async/await: 异步代码更简洁,更易读。
使用Promise.all: 并行执行多个异步操作。
app.get('/api/dashboard', async (req, res) => { try { const [users, posts, comments] = await Promise.all([ User.countDocuments(), Post.countDocuments(), Comment.countDocuments() ]);
res.json({ users, posts, comments }); } catch (error) { res.status(500).json({ message: error.message }); }});避免阻塞操作: 避免在主线程中执行阻塞操作,如文件I/O、网络请求等。
使用流: 对于大文件,使用流处理。
const fs = require('fs');const path = require('path');
app.get('/api/download', (req, res) => { const filePath = path.join(__dirname, 'large-file.txt'); const stream = fs.createReadStream(filePath); stream.pipe(res);});7. 测试
7.1 单元测试
安装Jest:
npm install --save-dev jest编写单元测试:
const { hashPassword, comparePassword } = require('./auth');
describe('Auth utils', () => { test('hashPassword should return hashed password', async () => { const password = 'password123'; const hashedPassword = await hashPassword(password); expect(hashedPassword).not.toBe(password); });
test('comparePassword should return true for correct password', async () => { const password = 'password123'; const hashedPassword = await hashPassword(password); const result = await comparePassword(password, hashedPassword); expect(result).toBe(true); });
test('comparePassword should return false for incorrect password', async () => { const password = 'password123'; const wrongPassword = 'wrongpassword'; const hashedPassword = await hashPassword(password); const result = await comparePassword(wrongPassword, hashedPassword); expect(result).toBe(false); });});运行测试:
npm test7.2 集成测试
安装supertest:
npm install --save-dev supertest编写集成测试:
const request = require('supertest');const app = require('./app');
describe('API endpoints', () => { test('GET /api/users should return 200', async () => { const response = await request(app).get('/api/users'); expect(response.statusCode).toBe(200); expect(Array.isArray(response.body)).toBe(true); });
test('POST /api/users should return 201', async () => { const user = { name: 'John', email: 'john@example.com', password: 'password123' }; const response = await request(app).post('/api/users').send(user); expect(response.statusCode).toBe(201); expect(response.body.name).toBe(user.name); });});运行测试:
npm test8. 部署
8.1 部署到Heroku
创建Heroku应用:
heroku create myapp配置环境变量:
heroku config:set NODE_ENV=productionheroku config:set DATABASE_URL=mongodb://localhost:27017/myappheroku config:set JWT_SECRET=your-secret-key部署应用:
git push heroku main8.2 部署到AWS
使用Elastic Beanstalk:
- 安装AWS CLI:
npm install -g aws-cli - 配置AWS CLI:
aws configure - 创建Elastic Beanstalk应用:
eb init - 部署应用:
eb deploy
8.3 部署到Docker
创建Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]创建docker-compose.yml:
version: '3'services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=mongodb://mongo:27017/myapp - JWT_SECRET=your-secret-key depends_on: - mongo mongo: image: mongo:4.4 ports: - "27017:27017" volumes: - mongo-data:/data/dbvolumes: mongo-data:构建和运行容器:
docker-compose up -d9. 最佳实践
9.1 项目结构
myapp/├── src/│ ├── config/│ │ └── database.js│ ├── controllers/│ │ └── userController.js│ ├── middlewares/│ │ ├── auth.js│ │ └── errorHandler.js│ ├── models/│ │ └── User.js│ ├── routes/│ │ └── userRoutes.js│ ├── utils/│ │ └── auth.js│ └── app.js├── index.js├── package.json└── .env9.2 代码风格
使用ESLint和Prettier:
npm install --save-dev eslint prettier eslint-config-prettier eslint-plugin-prettier配置ESLint:
module.exports = { env: { node: true, commonjs: true, es2021: true, jest: true }, extends: [ 'eslint:recommended', 'prettier' ], parserOptions: { ecmaVersion: 12 }, plugins: ['prettier'], rules: { 'prettier/prettier': 'error' }};配置Prettier:
module.exports = { singleQuote: true, trailingComma: 'es5', tabWidth: 2, semi: true};9.3 错误处理
统一错误处理:
const errorHandler = (err, req, res, next) => { err.statusCode = err.statusCode || 500; err.status = err.status || 'error';
res.status(err.statusCode).json({ status: err.status, message: err.message, stack: process.env.NODE_ENV === 'development' ? err.stack : undefined });};
module.exports = errorHandler;
// app.jsconst errorHandler = require('./middlewares/errorHandler');
app.use(errorHandler);9.4 日志
安装winston:
npm install winston配置日志:
const winston = require('winston');
const logger = winston.createLogger({ level: process.env.NODE_ENV === 'production' ? 'info' : 'debug', format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ]});
if (process.env.NODE_ENV !== 'production') { logger.add(new winston.transports.Console({ format: winston.format.simple() }));}
module.exports = logger;
// 使用日志const logger = require('./logger');
app.get('/api/users', async (req, res) => { try { const users = await User.find(); logger.info('Fetched users'); res.json(users); } catch (error) { logger.error('Error fetching users:', error); res.status(500).json({ message: 'Internal server error' }); }});9.5 安全
安装helmet和cors:
npm install helmet cors使用helmet和cors:
const helmet = require('helmet');const cors = require('cors');
app.use(helmet());app.use(cors());
// 防止XSS攻击app.use((req, res, next) => { res.setHeader('X-XSS-Protection', '1; mode=block'); next();});
// 防止CSRF攻击const csrf = require('csurf');const csrfProtection = csrf({ cookie: true });app.use(csrfProtection);
app.get('/api/csrf-token', (req, res) => { res.json({ csrfToken: req.csrfToken() });});10. 总结
Node.js和Express为我们提供了一种强大、灵活的方式来构建后端应用。通过本文介绍的各种技术和最佳实践,你可以:
- 构建高性能、可维护的后端应用
- 集成各种数据库
- 实现认证和授权
- 优化应用性能
- 编写测试
- 部署应用到各种平台
希望本文对你有所帮助,祝你编码愉快!
11. 参考资料
部分信息可能已经过时









