𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

Node.js服务器开发完全指南

作为一名从零开始学习Node.js的开发者,我深知这个旅程的不易。还记得第一次用Node.js搭建服务器时的兴奋,也有被异步编程搞晕的瞬间。今天,我想以过来人的身份,系统地为大家梳理Node.js服务器开发的方方面面,帮你少走弯路。

一、Node.js简介

什么是Node.js?

Node.js是一个基于Chrome V8引擎的JavaScript运行时环境。它让JavaScript可以在服务器端运行,实现了”用JavaScript写全栈”的梦想。

# 检查Node.js是否安装
node --version
npm --version

Node.js的特点

  1. 异步非阻塞I/O:事件循环机制让Node.js能处理大量并发连接
  2. 单线程事件驱动:通过事件循环实现高并发
  3. 轻量高效:相比传统的多线程服务器,资源占用更少
  4. 丰富的npm生态:拥有世界上最大的包管理系统

为什么选择Node.js?

// 传统服务器(如Apache)处理请求的方式
// 每个请求占用一个线程,1000个请求需要1000个线程

// Node.js处理请求的方式
// 单线程 + 事件循环,可以处理数万个并发连接

二、基础概念

事件循环

Node.js的核心是事件循环机制。让我们深入理解它的工作原理:

// 同步代码会阻塞事件循环
console.log('1') // 立即执行
console.log('2') // 立即执行
setTimeout(() => console.log('3'), 0) // 下一个事件循环
console.log('4') // 立即执行

// 输出顺序:1, 2, 4, 3

// 验证同步代码会阻塞
const start = Date.now()
console.log('开始同步操作')

// 模拟耗时同步操作
for (let i = 0; i < 1000000; i++) {
Math.sqrt(i)
}

console.log('同步操作完成,耗时:', Date.now() - start, 'ms')
setTimeout(() => console.log('定时器回调'), 0)

// 在同步操作期间,定时器回调不会执行

异步编程模式

Node.js支持多种异步编程模式:

// 1. 回调函数模式
fs.readFile('file.txt', (err, data) => {
if (err) {
console.error('读取失败:', err)
return
}
console.log(data)
})

// 2. Promise模式
fs.promises.readFile('file.txt')
.then(data => console.log(data))
.catch(err => console.error('读取失败:', err))

// 3. async/await模式(推荐)
async function readFile() {
try {
const data = await fs.promises.readFile('file.txt')
console.log(data)
} catch (err) {
console.error('读取失败:', err)
}
}

模块系统

Node.js使用CommonJS模块系统:

// module.js
const PI = 3.14159
const circleArea = (radius) => PI * radius * radius
const squareArea = (side) => side * side

module.exports = {
PI,
circleArea,
squareArea
}

// main.js
const { PI, circleArea, squareArea } = require('./module')
console.log(PI) // 3.14159
console.log(circleArea(5)) // 78.53975
console.log(squareArea(4)) // 16

// ES模块(Node.js支持)
// module.mjs
export const PI = 3.14159
export const circleArea = (radius) => PI * radius * radius

// main.mjs
import { PI, circleArea } from './module.mjs'

三、搭建基础服务器

简单HTTP服务器

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

const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true)

// 设置响应头
res.writeHead(200, {
'Content-Type': 'text/plain',
'X-Powered-By': 'Node.js'
})

// 根据路径返回不同内容
if (parsedUrl.pathname === '/') {
res.end('Welcome to Node.js Server!')
} else if (parsedUrl.pathname === '/about') {
res.end('About Us')
} else if (parsedUrl.pathname === '/api/users') {
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
res.end(JSON.stringify(users))
} else {
res.writeHead(404)
res.end('404 Not Found')
}
})

const PORT = 3000
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`)
})

使用中间件概念

// 请求日志中间件
function logger(req, res, next) {
console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`)
next()
}

// 身份验证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization

if (token === 'secret-token') {
req.user = { id: 1, name: '张三' }
next()
} else {
res.writeHead(401)
res.end('Unauthorized')
}
}

// 使用中间件
const server = http.createServer((req, res) => {
logger(req, res, () => {
if (req.url.startsWith('/protected')) {
authenticate(req, res, () => {
res.writeHead(200, { 'Content-Type': 'application/json' })
res.end(JSON.stringify(req.user))
})
} else {
res.writeHead(200, { 'Content-Type': 'text/plain' })
res.end('Public content')
}
})
})

四、Express框架

安装Express

npm init -y
npm install express

基础Express应用

// app.js
const express = require('express')
const app = express()
const port = 3000

// 中间件
app.use(express.json()) // 解析JSON请求体
app.use(express.urlencoded({ extended: true })) // 解析URL编码请求体
app.use(express.static('public')) // 静态文件服务

// 路由
app.get('/', (req, res) => {
res.send('Hello World!')
})

app.get('/users', (req, res) => {
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
res.json(users)
})

// 路由参数
app.get('/users/:id', (req, res) => {
const { id } = req.params
const user = { id: parseInt(id), name: '用户' + id }
res.json(user)
})

// 查询参数
app.get('/search', (req, res) => {
const { q, limit } = req.query
res.json({
searchQuery: q,
limit: limit || 10
})
})

// POST请求
app.post('/users', (req, res) => {
const { name, email } = req.body
const newUser = {
id: Date.now(),
name,
email
}
res.status(201).json(newUser)
})

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

// 404处理
app.use((req, res) => {
res.status(404).send('Not Found')
})

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

路由分离

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

router.get('/', (req, res) => {
res.json([{ id: 1, name: '张三' }, { id: 2, name: '李四' }])
})

router.get('/:id', (req, res) => {
res.json({ id: req.params.id, name: '用户' + req.params.id })
})

router.post('/', (req, res) => {
res.status(201).json({ ...req.body, id: Date.now() })
})

module.exports = router

// routes/posts.js
const express = require('express')
const router = express.Router()

router.get('/', (req, res) => {
res.json([{ id: 1, title: '第一篇文章' }])
})

router.get('/:id', (req, res) => {
res.json({ id: req.params.id, title: '文章' + req.params.id })
})

module.exports = router

// app.js
const express = require('express')
const app = express()
const port = 3000

const userRoutes = require('./routes/users')
const postRoutes = require('./routes/posts')

app.use(express.json())

// 使用路由
app.use('/api/users', userRoutes)
app.use('/api/posts', postRoutes)

app.get('/', (req, res) => {
res.send('API Server')
})

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

五、数据库集成

MongoDB + Mongoose

npm install mongoose
// models/User.js
const mongoose = require('mongoose')

const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
age: {
type: Number,
min: 0,
max: 120
},
isActive: {
type: Boolean,
default: true
}
}, {
timestamps: true
})

// 虚拟字段
userSchema.virtual('fullName').get(function() {
return `${this.name} (${this.email})`
})

// 实例方法
userSchema.methods.sayHello = function() {
return `Hello, my name is ${this.name}`
}

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

const User = mongoose.model('User', userSchema)
module.exports = User
// controllers/userController.js
const User = require('../models/User')

// 获取所有用户
exports.getAllUsers = async (req, res) => {
try {
const users = await User.find()
res.json(users)
} catch (err) {
res.status(500).json({ message: err.message })
}
}

// 获取单个用户
exports.getUserById = 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 (err) {
res.status(500).json({ message: err.message })
}
}

// 创建用户
exports.createUser = async (req, res) => {
const user = new User({
name: req.body.name,
email: req.body.email,
age: req.body.age
})

try {
const newUser = await user.save()
res.status(201).json(newUser)
} catch (err) {
res.status(400).json({ message: err.message })
}
}

// 更新用户
exports.updateUser = async (req, res) => {
try {
const updatedUser = await User.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
)

if (!updatedUser) {
return res.status(404).json({ message: 'User not found' })
}

res.json(updatedUser)
} catch (err) {
res.status(400).json({ message: err.message })
}
}

// 删除用户
exports.deleteUser = async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id)
if (!user) {
return res.status(404).json({ message: 'User not found' })
}
res.json({ message: 'User deleted' })
} catch (err) {
res.status(500).json({ message: err.message })
}
}
// routes/users.js
const express = require('express')
const router = express.Router()
const userController = require('../controllers/userController')

// GET /api/users
router.get('/', userController.getAllUsers)

// GET /api/users/:id
router.get('/:id', userController.getUserById)

// POST /api/users
router.post('/', userController.createUser)

// PUT /api/users/:id
router.put('/:id', userController.updateUser)

// DELETE /api/users/:id
router.delete('/:id', userController.deleteUser)

module.exports = router

MySQL + Sequelize

npm install mysql2 sequelize
// config/database.js
const { Sequelize } = require('sequelize')

// 创建Sequelize实例
const sequelize = new Sequelize('test_db', 'root', 'password', {
host: 'localhost',
dialect: 'mysql',
logging: console.log,
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
}
})

module.exports = sequelize
// models/User.js
const { DataTypes } = require('sequelize')
const sequelize = require('../config/database')

const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
validate: {
len: [2, 50]
}
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
validate: {
isEmail: true
}
},
age: {
type: DataTypes.INTEGER,
allowNull: true,
validate: {
min: 0,
max: 150
}
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true
}
}, {
tableName: 'users',
timestamps: true
})

module.exports = User
// models/index.js
const sequelize = require('./database')
const User = require('./User')

// 同步数据库
const syncDatabase = async () => {
try {
await sequelize.authenticate()
console.log('数据库连接成功')
await sequelize.sync({ force: false })
console.log('数据库同步完成')
} catch (error) {
console.error('数据库连接失败:', error)
}
}

module.exports = {
sequelize,
User,
syncDatabase
}

六、身份验证与授权

JWT认证

npm install jsonwebtoken bcryptjs
// utils/auth.js
const jwt = require('jsonwebtoken')
const bcrypt = require('bcryptjs')

const JWT_SECRET = 'your-secret-key'

// 生成JWT token
const generateToken = (user) => {
return jwt.sign(
{ id: user.id, email: user.email },
JWT_SECRET,
{ expiresIn: '1h' }
)
}

// 验证JWT token
const verifyToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET)
} catch (err) {
return null
}
}

// 密码哈希
const hashPassword = async (password) => {
const saltRounds = 10
return await bcrypt.hash(password, saltRounds)
}

// 验证密码
const verifyPassword = async (password, hashedPassword) => {
return await bcrypt.compare(password, hashedPassword)
}

module.exports = {
generateToken,
verifyToken,
hashPassword,
verifyPassword
}
// middleware/auth.js
const { verifyToken } = require('../utils/auth')

exports.authenticate = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '')

if (!token) {
return res.status(401).json({ message: 'Access denied' })
}

const decoded = verifyToken(token)
if (!decoded) {
return res.status(401).json({ message: 'Invalid token' })
}

req.user = decoded
next()
}

// 角色授权中间件
exports.authorize = (roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ message: 'Access denied' })
}
next()
}
}
// routes/auth.js
const express = require('express')
const router = express.Router()
const User = require('../models/User')
const { generateToken, hashPassword, verifyPassword } = require('../utils/auth')

// 注册
router.post('/register', async (req, res) => {
try {
const { name, email, password, role = 'user' } = req.body

// 检查用户是否已存在
const existingUser = await User.findByEmail(email)
if (existingUser) {
return res.status(400).json({ message: 'User already exists' })
}

// 创建用户
const hashedPassword = await hashPassword(password)
const user = new User({
name,
email,
password: hashedPassword,
role
})

await user.save()

// 生成token
const token = generateToken(user)

res.status(201).json({
message: 'User created successfully',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
})
} catch (err) {
res.status(500).json({ message: err.message })
}
})

// 登录
router.post('/login', async (req, res) => {
try {
const { email, password } = req.body

// 查找用户
const user = await User.findByEmail(email)
if (!user) {
return res.status(400).json({ message: 'Invalid credentials' })
}

// 验证密码
const isPasswordValid = await verifyPassword(password, user.password)
if (!isPasswordValid) {
return res.status(400).json({ message: 'Invalid credentials' })
}

// 生成token
const token = generateToken(user)

res.json({
message: 'Login successful',
token,
user: {
id: user._id,
name: user.name,
email: user.email,
role: user.role
}
})
} catch (err) {
res.status(500).json({ message: err.message })
}
})

module.exports = router

七、文件上传

处理文件上传

npm install multer
// utils/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']

if (allowedTypes.includes(file.mimetype)) {
cb(null, true)
} else {
cb(new Error('Invalid file type'), false)
}
}

// 配置上传
const upload = multer({
storage: storage,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB
files: 10 // 最多10个文件
},
fileFilter: fileFilter
})

module.exports = upload
// routes/upload.js
const express = require('express')
const router = express.Router()
const upload = require('../utils/upload')
const path = require('path')

// 单文件上传
router.post('/single', upload.single('file'), (req, res) => {
if (!req.file) {
return res.status(400).json({ message: 'No file uploaded' })
}

res.json({
message: 'File uploaded successfully',
file: {
originalName: req.file.originalname,
filename: req.file.filename,
path: req.file.path,
size: req.file.size
}
})
})

// 多文件上传
router.post('/multiple', upload.array('files', 5), (req, res) => {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ message: 'No files uploaded' })
}

const files = req.files.map(file => ({
originalName: file.originalname,
filename: file.filename,
path: file.path,
size: file.size
}))

res.json({
message: 'Files uploaded successfully',
files: files
})
})

module.exports = router

八、API设计最佳实践

RESTful API设计

// routes/api/v1/products.js
const express = require('express')
const router = express.Router()
const Product = require('../../models/Product')

// GET /api/v1/products - 获取所有产品
router.get('/', async (req, res) => {
try {
const { page = 1, limit = 10, category, minPrice, maxPrice } = req.query

// 构建查询条件
const query = {}
if (category) query.category = category
if (minPrice || maxPrice) {
query.price = {}
if (minPrice) query.price.$gte = parseFloat(minPrice)
if (maxPrice) query.price.$lte = parseFloat(maxPrice)
}

const products = await Product.find(query)
.limit(limit * 1)
.skip((page - 1) * limit)
.lean()

const total = await Product.countDocuments(query)

res.json({
products,
pagination: {
current: parseInt(page),
pages: Math.ceil(total / limit),
total
}
})
} catch (err) {
res.status(500).json({ message: err.message })
}
})

// GET /api/v1/products/:id - 获取单个产品
router.get('/:id', async (req, res) => {
try {
const product = await Product.findById(req.params.id)

if (!product) {
return res.status(404).json({ message: 'Product not found' })
}

res.json(product)
} catch (err) {
res.status(500).json({ message: err.message })
}
})

// POST /api/v1/products - 创建产品
router.post('/', async (req, res) => {
const product = new Product({
name: req.body.name,
description: req.body.description,
price: req.body.price,
category: req.body.category,
stock: req.body.stock
})

try {
const newProduct = await product.save()
res.status(201).json(newProduct)
} catch (err) {
res.status(400).json({ message: err.message })
}
})

// PUT /api/v1/products/:id - 更新产品
router.put('/:id', async (req, res) => {
try {
const product = await Product.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true }
)

if (!product) {
return res.status(404).json({ message: 'Product not found' })
}

res.json(product)
} catch (err) {
res.status(400).json({ message: err.message })
}
})

// DELETE /api/v1/products/:id - 删除产品
router.delete('/:id', async (req, res) => {
try {
const product = await Product.findByIdAndDelete(req.params.id)

if (!product) {
return res.status(404).json({ message: 'Product not found' })
}

res.json({ message: 'Product deleted' })
} catch (err) {
res.status(500).json({ message: err.message })
}
})

module.exports = router

API响应标准化

// utils/response.js
class ApiResponse {
constructor(success, statusCode, message, data = null) {
this.success = success
this.statusCode = statusCode
this.message = message
this.data = data
this.timestamp = new Date().toISOString()
}

static success(message, data = null, statusCode = 200) {
return new ApiResponse(true, statusCode, message, data)
}

static error(message, statusCode = 500) {
return new ApiResponse(false, statusCode, message, null)
}

static created(message, data = null) {
return this.success(message, data, 201)
}

static badRequest(message) {
return this.error(message, 400)
}

static unauthorized(message) {
return this.error(message, 401)
}

static forbidden(message) {
return this.error(message, 403)
}

static notFound(message) {
return this.error(message, 404)
}

static internalError(message) {
return this.error(message, 500)
}
}

module.exports = ApiResponse
// 使用标准化响应
const ApiResponse = require('../utils/response')

exports.getProduct = async (req, res) => {
try {
const product = await Product.findById(req.params.id)

if (!product) {
return res.status(404).json(ApiResponse.notFound('Product not found'))
}

res.json(ApiResponse.success('Product retrieved successfully', product))
} catch (err) {
res.status(500).json(ApiResponse.internalError(err.message))
}
}

九、性能优化

缓存策略

// utils/cache.js
const NodeCache = require('node-cache')

// 创建缓存实例
const cache = new NodeCache({
stdTTL: 1000, // 缓存过期时间(秒)
checkperiod: 600, // 检查过期的频率(秒)
useClones: false // 不缓存对象克隆
})

// 缓存键生成器
const generateCacheKey = (prefix, params) => {
return `${prefix}:${JSON.stringify(params)}`
}

// 带缓存的查询
const cachedQuery = async (key, queryFn, ttl = 300) => {
// 检查缓存
const cached = cache.get(key)
if (cached) {
console.log(`Cache hit for key: ${key}`)
return cached
}

// 执行查询
const result = await queryFn()

// 存入缓存
cache.set(key, result, ttl)
console.log(`Cache set for key: ${key}`)

return result
}

module.exports = {
cache,
generateCacheKey,
cachedQuery
}
// 使用缓存
const { generateCacheKey, cachedQuery } = require('../utils/cache')

exports.getProducts = async (req, res) => {
const { category } = req.query
const cacheKey = generateCacheKey('products', { category })

const products = await cachedQuery(
cacheKey,
async () => Product.find({ category: category || { $exists: true } }),
600 // 缓存10分钟
)

res.json(ApiResponse.success('Products retrieved', products))
}

数据库优化

// models/index.js
const sequelize = require('./database')
const { DataTypes } = require('sequelize')

// 创建模型
const User = sequelize.define('User', {
name: DataTypes.STRING,
email: { type: DataTypes.STRING, unique: true },
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
})

const Product = sequelize.define('Product', {
name: DataTypes.STRING,
price: DataTypes.DECIMAL(10, 2),
category: DataTypes.STRING,
stock: DataTypes.INTEGER,
createdAt: DataTypes.DATE,
updatedAt: DataTypes.DATE
})

// 关联关系
User.hasMany(Product, { as: 'products', foreignKey: 'userId' })
Product.belongsTo(User, { as: 'user', foreignKey: 'userId' })

// 数据库优化配置
const optimizeDatabase = async () => {
// 创建索引
await User.sync()
await Product.sync()

// 添加索引
await sequelize.query(`
CREATE INDEX IF NOT EXISTS idx_products_category
ON Products(category)
`)

await sequelize.query(`
CREATE INDEX IF NOT EXISTS idx_products_price
ON Products(price)
`)

console.log('Database optimization completed')
}

module.exports = { sequelize, User, Product, optimizeDatabase }

十、错误处理

自定义错误类

// utils/errors.js
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message)
this.statusCode = statusCode
this.isOperational = true

Error.captureStackTrace(this, this.constructor)
}
}

// 业务错误
class BusinessError extends AppError {
constructor(message) {
super(message, 400)
}
}

// 认证错误
class AuthenticationError extends AppError {
constructor(message) {
super(message, 401)
}
}

// 授权错误
class AuthorizationError extends AppError {
constructor(message) {
super(message, 403)
}
}

// 资源未找到错误
class NotFoundError extends AppError {
constructor(resource) {
super(`${resource} not found`, 404)
}
}

module.exports = {
AppError,
BusinessError,
AuthenticationError,
AuthorizationError,
NotFoundError
}

全局错误处理

// middleware/errorHandler.js
const { AppError } = require('../utils/errors')

const errorHandler = (err, req, res, next) => {
let error = { ...err }
error.message = err.message

// 记录错误日志
console.error(err)

// Sequelize错误处理
if (err.name === 'SequelizeValidationError') {
const message = err.errors.map(error => error.message).join(', ')
error = new AppError(message, 400)
}

if (err.name === 'SequelizeUniqueConstraintError') {
const message = 'Duplicate field value entered'
error = new AppError(message, 400)
}

if (err.name === 'SequelizeForeignKeyConstraintError') {
const message = 'Invalid reference ID'
error = new AppError(message, 400)
}

// Mongoose错误处理
if (err.name === 'CastError') {
const message = `Resource not found with id of ${err.value}`
error = new AppError(message, 404)
}

if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ')
error = new AppError(message, 400)
}

if (err.code === 11000) {
const field = Object.keys(err.keyPattern)[0]
const message = `Duplicate value entered for ${field} field`
error = new AppError(message, 400)
}

// JWT错误处理
if (err.name === 'JsonWebTokenError') {
const message = 'Invalid token. Please log in again.'
error = new AppError(message, 401)
}

if (err.name === 'TokenExpiredError') {
const message = 'Your token has expired. Please log in again.'
error = new AppError(message, 401)
}

res.status(error.statusCode || 500).json({
success: false,
error: error.message || 'Internal Server Error',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
})
}

module.exports = errorHandler
// 使用错误处理
const { NotFoundError } = require('../utils/errors')

exports.getProduct = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id)

if (!product) {
return next(new NotFoundError('Product'))
}

res.json(ApiResponse.success('Product retrieved', product))
} catch (err) {
next(err)
}
}

十一、测试

单元测试

// tests/user.test.js
const request = require('supertest')
const app = require('../app')
const User = require('../models/User')
const { generateToken } = require('../utils/auth')

describe('User API', () => {
let testToken
let testUser

beforeEach(async () => {
// 清理数据库
await User.deleteMany({})

// 创建测试用户
testUser = new User({
name: 'Test User',
email: 'test@example.com',
password: 'password123'
})
await testUser.save()

// 生成token
testToken = generateToken(testUser)
})

describe('POST /api/users', () => {
it('should create a new user', async () => {
const res = await request(app)
.post('/api/users')
.send({
name: 'New User',
email: 'new@example.com',
password: 'password123'
})

expect(res.statusCode).toEqual(201)
expect(res.body.success).toBe(true)
expect(res.body.data.name).toBe('New User')
})

it('should not create user with duplicate email', async () => {
const res = await request(app)
.post('/api/users')
.send({
name: 'Duplicate User',
email: 'test@example.com',
password: 'password123'
})

expect(res.statusCode).toEqual(400)
expect(res.body.success).toBe(false)
})
})

describe('GET /api/users', () => {
it('should get all users', async () => {
const res = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${testToken}`)

expect(res.statusCode).toEqual(200)
expect(res.body.success).toBe(true)
expect(res.body.data.length).toBeGreaterThan(0)
})
})

describe('GET /api/users/:id', () => {
it('should get user by id', async () => {
const res = await request(app)
.get(`/api/users/${testUser._id}`)
.set('Authorization', `Bearer ${testToken}`)

expect(res.statusCode).toEqual(200)
expect(res.body.success).toBe(true)
expect(res.body.data.name).toBe('Test User')
})

it('should not get non-existent user', async () => {
const res = await request(app)
.get('/api/users/507f1f77bcf86cd799439011')
.set('Authorization', `Bearer ${testToken}`)

expect(res.statusCode).toEqual(404)
})
})
})

集成测试

// tests/integration/auth.test.js
const request = require('supertest')
const app = require('../app')
const User = require('../models/User')

describe('Auth Integration Tests', () => {
beforeEach(async () => {
await User.deleteMany({})
})

describe('POST /api/auth/register', () => {
it('should register a new user and return token', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
})

expect(res.statusCode).toEqual(201)
expect(res.body.success).toBe(true)
expect(res.body.token).toBeDefined()
expect(res.body.data.name).toBe('John Doe')
})
})

describe('POST /api/auth/login', () => {
beforeEach(async () => {
// 注册测试用户
await request(app)
.post('/api/auth/register')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
})
})

it('should login with valid credentials', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({
email: 'john@example.com',
password: 'password123'
})

expect(res.statusCode).toEqual(200)
expect(res.body.success).toBe(true)
expect(res.body.token).toBeDefined()
})

it('should not login with invalid credentials', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({
email: 'john@example.com',
password: 'wrongpassword'
})

expect(res.statusCode).toEqual(400)
expect(res.body.success).toBe(false)
})
})
})

十二、部署

生产环境配置

// config/production.js
module.exports = {
port: process.env.PORT || 8080,
dbUrl: process.env.DATABASE_URL,
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: process.env.JWT_EXPIRES_IN || '1h',
nodeEnv: process.env.NODE_ENV || 'production'
}

PM2进程管理

// ecosystem.config.js
module.exports = {
apps: [{
name: 'node-api',
script: 'app.js',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development'
},
env_production: {
NODE_ENV: 'production',
PORT: 8080
}
}]
}

Docker部署

# Dockerfile
FROM node:16-alpine

WORKDIR /app

# 复制package.json和package-lock.json
COPY package*.json ./

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

# 复制源代码
COPY . .

# 暴露端口
EXPOSE 8080

# 启动应用
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'

services:
node-api:
build: .
ports:
- "8080:8080"
environment:
- NODE_ENV=production
- PORT=8080
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
depends_on:
- db
restart: unless-stopped

db:
image: postgres:13
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
restart: unless-stopped

volumes:
postgres_data:

十三、完整项目示例

// app.js
const express = require('express')
const cors = require('cors')
const morgan = require('morgan')
const helmet = require('helmet')
const compression = require('compression')
const rateLimit = require('express-rate-limit')

// 导入路由
const userRoutes = require('./routes/users')
const authRoutes = require('./routes/auth')
const productRoutes = require('./routes/products')
const uploadRoutes = require('./routes/upload')

// 导入中间件
const { authenticate } = require('./middleware/auth')
const errorHandler = require('./middleware/errorHandler')

// 导入配置
const config = require('./config/production')

// 创建Express应用
const app = express()

// 安全中间件
app.use(helmet())
app.use(cors({
origin: process.env.NODE_ENV === 'production'
? 'https://yourdomain.com'
: 'http://localhost:3000',
credentials: true
}))

// 日志中间件
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'))
} else {
app.use(morgan('combined'))
}

// 请求限制
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100, // 每个IP最多100次请求
message: 'Too many requests from this IP, please try again later.'
})
app.use('/api/', limiter)

// 压缩中间件
app.use(compression())

// 解析中间件
app.use(express.json({ limit: '10mb' }))
app.use(express.urlencoded({ extended: true }))

// 健康检查
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
})
})

// API路由
app.use('/api/auth', authRoutes)
app.use('/api/users', authenticate, userRoutes)
app.use('/api/products', authenticate, productRoutes)
app.use('/api/upload', authenticate, uploadRoutes)

// 错误处理
app.use(errorHandler)

// 404处理
app.use('*', (req, res) => {
res.status(404).json({
success: false,
message: 'Route not found'
})
})

// 启动服务器
const PORT = config.port || 3000
app.listen(PORT, () => {
console.log(`Server running on port ${PORT} in ${config.nodeEnv} mode`)
})

// 优雅关闭
process.on('SIGTERM', () => {
console.log('SIGTERM received. Shutting down gracefully...')
server.close(() => {
console.log('Process terminated')
process.exit(0)
})
})

process.on('SIGINT', () => {
console.log('SIGINT received. Shutting down gracefully...')
server.close(() => {
console.log('Process terminated')
process.exit(0)
})
})

十四、总结

Node.js服务器开发是一个广阔的领域,从基础的HTTP服务器到复杂的微服务架构,都需要深入理解和实践经验。

关键要点:

  1. 基础扎实:理解事件循环、异步编程、模块系统
  2. 框架选择:Express是行业标准,但也可以考虑NestJS、Fastify等
  3. 数据库集成:MongoDB+Mongoose或MySQL+Sequelize
  4. 安全认证:JWT是目前最流行的认证方式
  5. 性能优化:缓存、数据库索引、集群模式
  6. 测试:单元测试和集成测试确保代码质量
  7. 部署:容器化、进程管理、监控

最佳实践:

  1. 代码组织:按功能模块组织代码
  2. 错误处理:统一的错误处理机制
  3. API设计:RESTful风格,标准响应格式
  4. 安全防护:输入验证、SQL注入防护、XSS防护
  5. 性能监控:响应时间、错误率、资源使用

Node.js的优势在于其JavaScript生态系统和高效的并发处理能力。选择合适的工具和架构模式,可以构建出高性能、可扩展的后端应用。

如果你在实际开发中遇到任何问题,欢迎在评论区留言交流。祝学习愉快!🎉


最后更新:2026年5月14日
分类:Node.js | 服务器开发 | 后端开发 | JavaScript后端 | API