Appearance
Node.js + Express 开发指南
🚀 项目初始化
1. 创建项目结构
bash
mkdir my-express-app
cd my-express-app
npm init -y
2. 安装依赖
bash
# 核心依赖
npm install express cors helmet morgan dotenv
# 开发依赖
npm install -D nodemon eslint prettier
# 数据库相关(可选)
npm install mongoose # MongoDB
# 或者
npm install mysql2 sequelize # MySQL
3. 推荐的项目结构
my-express-app/
├── src/
│ ├── controllers/ # 控制器
│ ├── models/ # 数据模型
│ ├── routes/ # 路由
│ ├── middleware/ # 中间件
│ ├── utils/ # 工具函数
│ ├── config/ # 配置文件
│ └── app.js # 应用入口
├── tests/ # 测试文件
├── .env # 环境变量
├── .gitignore
└── package.json
🏗️ 基础应用搭建
1. 创建基础应用 (src/app.js)
javascript
const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const morgan = require('morgan')
require('dotenv').config()
const app = express()
const PORT = process.env.PORT || 3000
// 中间件
app.use(helmet()) // 安全头
app.use(cors()) // 跨域
app.use(morgan('combined')) // 日志
app.use(express.json({ limit: '10mb' })) // JSON解析
app.use(express.urlencoded({ extended: true })) // URL编码解析
// 静态文件
app.use('/public', express.static('public'))
// 路由
app.get('/', (req, res) => {
res.json({
message: '欢迎使用我的API',
version: '1.0.0',
timestamp: new Date().toISOString()
})
})
// 404处理
app.use('*', (req, res) => {
res.status(404).json({
error: '路由不存在',
path: req.originalUrl
})
})
// 全局错误处理
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(500).json({
error: '服务器内部错误',
message: process.env.NODE_ENV === 'development' ? err.message : '请稍后重试'
})
})
app.listen(PORT, () => {
console.log(`🚀 服务器运行在 http://localhost:${PORT}`)
})
module.exports = app
2. 环境配置 (.env)
env
# 服务器配置
PORT=3000
NODE_ENV=development
# 数据库配置
DB_HOST=localhost
DB_PORT=27017
DB_NAME=myapp
DB_USER=
DB_PASSWORD=
# JWT配置
JWT_SECRET=your-super-secret-jwt-key
JWT_EXPIRES_IN=7d
# 其他配置
API_VERSION=v1
UPLOAD_PATH=./uploads
🛣️ 路由设计
1. 用户路由 (src/routes/users.js)
javascript
const express = require('express')
const router = express.Router()
const userController = require('../controllers/userController')
const authMiddleware = require('../middleware/auth')
const validationMiddleware = require('../middleware/validation')
// 用户注册
router.post('/register',
validationMiddleware.validateUserRegistration,
userController.register
)
// 用户登录
router.post('/login',
validationMiddleware.validateUserLogin,
userController.login
)
// 获取用户信息(需要认证)
router.get('/profile',
authMiddleware.requireAuth,
userController.getProfile
)
// 更新用户信息
router.put('/profile',
authMiddleware.requireAuth,
validationMiddleware.validateUserUpdate,
userController.updateProfile
)
// 获取所有用户(管理员权限)
router.get('/',
authMiddleware.requireAuth,
authMiddleware.requireAdmin,
userController.getAllUsers
)
// 删除用户
router.delete('/:id',
authMiddleware.requireAuth,
authMiddleware.requireAdmin,
userController.deleteUser
)
module.exports = router
2. 控制器 (src/controllers/userController.js)
javascript
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const User = require('../models/User')
const { validationResult } = require('express-validator')
class UserController {
// 用户注册
async register(req, res) {
try {
// 验证输入
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({
error: '输入验证失败',
details: errors.array()
})
}
const { username, email, password } = req.body
// 检查用户是否已存在
const existingUser = await User.findOne({
$or: [{ email }, { username }]
})
if (existingUser) {
return res.status(409).json({
error: '用户已存在',
message: '邮箱或用户名已被使用'
})
}
// 加密密码
const saltRounds = 12
const hashedPassword = await bcrypt.hash(password, saltRounds)
// 创建用户
const user = new User({
username,
email,
password: hashedPassword
})
await user.save()
// 生成JWT
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
)
res.status(201).json({
message: '注册成功',
user: {
id: user._id,
username: user.username,
email: user.email,
createdAt: user.createdAt
},
token
})
} catch (error) {
console.error('注册错误:', error)
res.status(500).json({
error: '注册失败',
message: '服务器内部错误'
})
}
}
// 用户登录
async login(req, res) {
try {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({
error: '输入验证失败',
details: errors.array()
})
}
const { email, password } = req.body
// 查找用户
const user = await User.findOne({ email }).select('+password')
if (!user) {
return res.status(401).json({
error: '登录失败',
message: '邮箱或密码错误'
})
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password)
if (!isPasswordValid) {
return res.status(401).json({
error: '登录失败',
message: '邮箱或密码错误'
})
}
// 生成JWT
const token = jwt.sign(
{ userId: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN }
)
// 更新最后登录时间
user.lastLoginAt = new Date()
await user.save()
res.json({
message: '登录成功',
user: {
id: user._id,
username: user.username,
email: user.email,
lastLoginAt: user.lastLoginAt
},
token
})
} catch (error) {
console.error('登录错误:', error)
res.status(500).json({
error: '登录失败',
message: '服务器内部错误'
})
}
}
// 获取用户信息
async getProfile(req, res) {
try {
const user = await User.findById(req.user.userId)
if (!user) {
return res.status(404).json({
error: '用户不存在'
})
}
res.json({
user: {
id: user._id,
username: user.username,
email: user.email,
createdAt: user.createdAt,
lastLoginAt: user.lastLoginAt
}
})
} catch (error) {
console.error('获取用户信息错误:', error)
res.status(500).json({
error: '获取用户信息失败'
})
}
}
// 更新用户信息
async updateProfile(req, res) {
try {
const errors = validationResult(req)
if (!errors.isEmpty()) {
return res.status(400).json({
error: '输入验证失败',
details: errors.array()
})
}
const { username } = req.body
const userId = req.user.userId
// 检查用户名是否已被使用
if (username) {
const existingUser = await User.findOne({
username,
_id: { $ne: userId }
})
if (existingUser) {
return res.status(409).json({
error: '用户名已被使用'
})
}
}
// 更新用户信息
const user = await User.findByIdAndUpdate(
userId,
{ username, updatedAt: new Date() },
{ new: true }
)
res.json({
message: '更新成功',
user: {
id: user._id,
username: user.username,
email: user.email,
updatedAt: user.updatedAt
}
})
} catch (error) {
console.error('更新用户信息错误:', error)
res.status(500).json({
error: '更新失败'
})
}
}
}
module.exports = new UserController()
🛡️ 中间件
1. 认证中间件 (src/middleware/auth.js)
javascript
const jwt = require('jsonwebtoken')
const User = require('../models/User')
class AuthMiddleware {
// 验证JWT token
async requireAuth(req, res, next) {
try {
const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: '未授权',
message: '请提供有效的访问令牌'
})
}
const token = authHeader.substring(7) // 移除 'Bearer ' 前缀
// 验证token
const decoded = jwt.verify(token, process.env.JWT_SECRET)
// 检查用户是否存在
const user = await User.findById(decoded.userId)
if (!user) {
return res.status(401).json({
error: '未授权',
message: '用户不存在'
})
}
// 将用户信息添加到请求对象
req.user = {
userId: user._id,
email: user.email,
role: user.role
}
next()
} catch (error) {
if (error.name === 'JsonWebTokenError') {
return res.status(401).json({
error: '未授权',
message: '无效的访问令牌'
})
}
if (error.name === 'TokenExpiredError') {
return res.status(401).json({
error: '未授权',
message: '访问令牌已过期'
})
}
console.error('认证错误:', error)
res.status(500).json({
error: '认证失败'
})
}
}
// 验证管理员权限
requireAdmin(req, res, next) {
if (req.user.role !== 'admin') {
return res.status(403).json({
error: '权限不足',
message: '需要管理员权限'
})
}
next()
}
// 可选认证(不强制要求登录)
async optionalAuth(req, res, next) {
try {
const authHeader = req.headers.authorization
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7)
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findById(decoded.userId)
if (user) {
req.user = {
userId: user._id,
email: user.email,
role: user.role
}
}
}
next()
} catch (error) {
// 可选认证失败时不返回错误,继续执行
next()
}
}
}
module.exports = new AuthMiddleware()
2. 验证中间件 (src/middleware/validation.js)
javascript
const { body } = require('express-validator')
class ValidationMiddleware {
// 用户注册验证
validateUserRegistration = [
body('username')
.isLength({ min: 3, max: 20 })
.withMessage('用户名长度必须在3-20个字符之间')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('用户名只能包含字母、数字和下划线'),
body('email')
.isEmail()
.withMessage('请提供有效的邮箱地址')
.normalizeEmail(),
body('password')
.isLength({ min: 8 })
.withMessage('密码长度至少8个字符')
.matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
.withMessage('密码必须包含至少一个小写字母、一个大写字母和一个数字')
]
// 用户登录验证
validateUserLogin = [
body('email')
.isEmail()
.withMessage('请提供有效的邮箱地址')
.normalizeEmail(),
body('password')
.notEmpty()
.withMessage('密码不能为空')
]
// 用户信息更新验证
validateUserUpdate = [
body('username')
.optional()
.isLength({ min: 3, max: 20 })
.withMessage('用户名长度必须在3-20个字符之间')
.matches(/^[a-zA-Z0-9_]+$/)
.withMessage('用户名只能包含字母、数字和下划线')
]
}
module.exports = new ValidationMiddleware()
🗄️ 数据模型
MongoDB + Mongoose 示例
javascript
// src/models/User.js
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, '用户名是必需的'],
unique: true,
trim: true,
minlength: [3, '用户名至少3个字符'],
maxlength: [20, '用户名最多20个字符']
},
email: {
type: String,
required: [true, '邮箱是必需的'],
unique: true,
lowercase: true,
trim: true,
match: [/^\S+@\S+\.\S+$/, '请提供有效的邮箱地址']
},
password: {
type: String,
required: [true, '密码是必需的'],
minlength: [8, '密码至少8个字符'],
select: false // 默认查询时不返回密码
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
avatar: {
type: String,
default: null
},
isActive: {
type: Boolean,
default: true
},
lastLoginAt: {
type: Date,
default: null
}
}, {
timestamps: true, // 自动添加 createdAt 和 updatedAt
toJSON: {
transform: function(doc, ret) {
delete ret.password
delete ret.__v
return ret
}
}
})
// 索引
userSchema.index({ email: 1 })
userSchema.index({ username: 1 })
userSchema.index({ createdAt: -1 })
module.exports = mongoose.model('User', userSchema)
🧪 测试
使用 Jest 和 Supertest
javascript
// tests/user.test.js
const request = require('supertest')
const app = require('../src/app')
const User = require('../src/models/User')
describe('用户API测试', () => {
beforeEach(async () => {
await User.deleteMany({})
})
describe('POST /api/users/register', () => {
it('应该成功注册新用户', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'Test123456'
}
const response = await request(app)
.post('/api/users/register')
.send(userData)
.expect(201)
expect(response.body.message).toBe('注册成功')
expect(response.body.user.email).toBe(userData.email)
expect(response.body.token).toBeDefined()
})
it('应该拒绝重复的邮箱', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'Test123456'
}
// 第一次注册
await request(app)
.post('/api/users/register')
.send(userData)
.expect(201)
// 第二次注册相同邮箱
const response = await request(app)
.post('/api/users/register')
.send({ ...userData, username: 'testuser2' })
.expect(409)
expect(response.body.error).toBe('用户已存在')
})
})
describe('POST /api/users/login', () => {
beforeEach(async () => {
// 创建测试用户
await request(app)
.post('/api/users/register')
.send({
username: 'testuser',
email: 'test@example.com',
password: 'Test123456'
})
})
it('应该成功登录', async () => {
const response = await request(app)
.post('/api/users/login')
.send({
email: 'test@example.com',
password: 'Test123456'
})
.expect(200)
expect(response.body.message).toBe('登录成功')
expect(response.body.token).toBeDefined()
})
it('应该拒绝错误的密码', async () => {
const response = await request(app)
.post('/api/users/login')
.send({
email: 'test@example.com',
password: 'wrongpassword'
})
.expect(401)
expect(response.body.error).toBe('登录失败')
})
})
})
📝 总结
这个Node.js + Express指南涵盖了:
- 🏗️ 项目结构: 清晰的文件组织
- 🛣️ 路由设计: RESTful API设计
- 🛡️ 安全性: 认证、授权、输入验证
- 🗄️ 数据模型: MongoDB/Mongoose集成
- 🧪 测试: 单元测试和集成测试
- 📊 错误处理: 统一的错误处理机制
通过这些最佳实践,你可以构建出安全、可维护、可扩展的Node.js应用!
希望这个指南对你的Node.js开发有所帮助! 🚀