Node.js 后端开发实战
Created on
前言
作为前端开发者,掌握 Node.js 后端开发可以让你成为全栈工程师,独立完成完整的项目。本文将系统讲解如何使用 Node.js + Express 构建生产级后端应用。
项目初始化
# 创建项目
mkdir node-api-server
cd node-api-server
# 初始化 package.json
npm init -y
# 安装依赖
npm install express
npm install --save-dev nodemon
# TypeScript 支持 (可选)
npm install --save-dev typescript @types/node @types/express ts-node
npx tsc --init
// package.json
{
"scripts": {
"dev": "nodemon src/index.js",
"start": "node src/index.js"
}
}
Express 基础
创建服务器
// src/index.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 })); // 解析 URL-encoded
// 基础路由
app.get("/", (req, res) => {
res.json({ message: "Hello World" });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
RESTful API 设计
路由规范
// routes/users.js
const express = require("express");
const router = express.Router();
// GET /api/users - 获取用户列表
router.get("/", async (req, res) => {
const { page = 1, limit = 10, search } = req.query;
const users = await User.find({
name: new RegExp(search, "i"),
})
.skip((page - 1) * limit)
.limit(Number(limit));
res.json({
success: true,
data: users,
pagination: {
page: Number(page),
limit: Number(limit),
total: await User.countDocuments(),
},
});
});
// GET /api/users/:id - 获取单个用户
router.get("/:id", async (req, res) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: "User not found",
});
}
res.json({
success: true,
data: user,
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
});
}
});
// POST /api/users - 创建用户
router.post("/", async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json({
success: true,
data: user,
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message,
});
}
});
// PUT /api/users/:id - 更新用户
router.put("/:id", async (req, res) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
return res.status(404).json({
success: false,
error: "User not found",
});
}
res.json({
success: true,
data: user,
});
} catch (error) {
res.status(400).json({
success: false,
error: error.message,
});
}
});
// DELETE /api/users/:id - 删除用户
router.delete("/:id", async (req, res) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: "User not found",
});
}
res.json({
success: true,
data: {},
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
});
}
});
module.exports = router;
注册路由
// src/index.js
const userRoutes = require("./routes/users");
app.use("/api/users", userRoutes);
数据库操作 (MongoDB + Mongoose)
连接数据库
// config/database.js
const mongoose = require("mongoose");
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
console.log("MongoDB connected");
} catch (error) {
console.error("MongoDB connection failed:", error.message);
process.exit(1);
}
};
module.exports = connectDB;
// src/index.js
const connectDB = require("./config/database");
connectDB();
定义模型
// models/User.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, "Name is required"],
trim: true,
maxlength: [50, "Name cannot exceed 50 characters"],
},
email: {
type: String,
required: [true, "Email is required"],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, "Please provide a valid email"],
},
password: {
type: String,
required: [true, "Password is required"],
minlength: [6, "Password must be at least 6 characters"],
select: false, // 默认查询不返回密码
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
createdAt: {
type: Date,
default: Date.now,
},
});
// 保存前加密密码
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) {
return next();
}
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});
// 实例方法:验证密码
userSchema.methods.matchPassword = async function (enteredPassword) {
return await bcrypt.compare(enteredPassword, this.password);
};
// 静态方法:查找活跃用户
userSchema.statics.findActiveUsers = function () {
return this.find({ isActive: true });
};
module.exports = mongoose.model("User", userSchema);
身份认证 (JWT)
注册和登录
// controllers/authController.js
const User = require("../models/User");
const jwt = require("jsonwebtoken");
// 生成 JWT Token
const generateToken = (id) => {
return jwt.sign({ id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
};
// 注册
exports.register = async (req, res) => {
try {
const { name, email, password } = req.body;
// 检查用户是否存在
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
success: false,
error: "Email already registered",
});
}
// 创建用户
const user = await User.create({
name,
email,
password,
});
// 生成 Token
const token = generateToken(user._id);
res.status(201).json({
success: true,
data: {
user: {
id: user._id,
name: user.name,
email: user.email,
},
token,
},
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
});
}
};
// 登录
exports.login = async (req, res) => {
try {
const { email, password } = req.body;
// 验证输入
if (!email || !password) {
return res.status(400).json({
success: false,
error: "Please provide email and password",
});
}
// 查找用户 (包含密码字段)
const user = await User.findOne({ email }).select("+password");
if (!user) {
return res.status(401).json({
success: false,
error: "Invalid credentials",
});
}
// 验证密码
const isMatch = await user.matchPassword(password);
if (!isMatch) {
return res.status(401).json({
success: false,
error: "Invalid credentials",
});
}
// 生成 Token
const token = generateToken(user._id);
res.json({
success: true,
data: {
user: {
id: user._id,
name: user.name,
email: user.email,
},
token,
},
});
} catch (error) {
res.status(500).json({
success: false,
error: error.message,
});
}
};
// 获取当前用户
exports.getMe = async (req, res) => {
const user = await User.findById(req.user.id);
res.json({
success: true,
data: user,
});
};
认证中间件
// middleware/auth.js
const jwt = require("jsonwebtoken");
const User = require("../models/User");
exports.protect = async (req, res, next) => {
let token;
// 从 Header 获取 Token
if (req.headers.authorization?.startsWith("Bearer")) {
token = req.headers.authorization.split(" ")[1];
}
// 检查 Token 是否存在
if (!token) {
return res.status(401).json({
success: false,
error: "Not authorized to access this route",
});
}
try {
// 验证 Token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 获取用户信息
req.user = await User.findById(decoded.id);
if (!req.user) {
return res.status(401).json({
success: false,
error: "User not found",
});
}
next();
} catch (error) {
return res.status(401).json({
success: false,
error: "Token is invalid or expired",
});
}
};
// 角色权限检查
exports.authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
error: `User role ${req.user.role} is not authorized to access this route`,
});
}
next();
};
};
使用中间件:
// routes/users.js
const { protect, authorize } = require("../middleware/auth");
// 需要登录
router.get("/profile", protect, getUserProfile);
// 需要管理员权限
router.delete("/:id", protect, authorize("admin"), deleteUser);
输入验证
// middleware/validation.js
const { body, validationResult } = require("express-validator");
// 验证规则
exports.registerValidation = [
body("name")
.trim()
.notEmpty()
.withMessage("Name is required")
.isLength({ max: 50 })
.withMessage("Name cannot exceed 50 characters"),
body("email")
.trim()
.notEmpty()
.withMessage("Email is required")
.isEmail()
.withMessage("Please provide a valid email")
.normalizeEmail(),
body("password")
.notEmpty()
.withMessage("Password is required")
.isLength({ min: 6 })
.withMessage("Password must be at least 6 characters"),
];
// 验证结果处理
exports.validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array().map((err) => ({
field: err.param,
message: err.msg,
})),
});
}
next();
};
使用:
const { registerValidation, validate } = require("../middleware/validation");
router.post("/register", registerValidation, validate, register);
错误处理
自定义错误类
// utils/ErrorResponse.js
class ErrorResponse extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
}
}
module.exports = ErrorResponse;
全局错误处理中间件
// middleware/errorHandler.js
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Mongoose 验证错误
if (err.name === "ValidationError") {
const message = Object.values(err.errors).map((e) => e.message);
error = new ErrorResponse(message, 400);
}
// Mongoose 重复键错误
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
const message = `${field} already exists`;
error = new ErrorResponse(message, 400);
}
// Mongoose CastError (无效 ID)
if (err.name === "CastError") {
const message = "Resource not found";
error = new ErrorResponse(message, 404);
}
// JWT 错误
if (err.name === "JsonWebTokenError") {
const message = "Invalid token";
error = new ErrorResponse(message, 401);
}
if (err.name === "TokenExpiredError") {
const message = "Token expired";
error = new ErrorResponse(message, 401);
}
res.status(error.statusCode || 500).json({
success: false,
error: error.message || "Server Error",
});
};
module.exports = errorHandler;
// src/index.js
const errorHandler = require("./middleware/errorHandler");
// 路由
app.use("/api/users", userRoutes);
// 错误处理 (必须放在最后)
app.use(errorHandler);
文件上传
// npm install multer
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 = /jpeg|jpg|png|gif/;
const extname = allowedTypes.test(
path.extname(file.originalname).toLowerCase()
);
const mimetype = allowedTypes.test(file.mimetype);
if (extname && mimetype) {
cb(null, true);
} else {
cb(new Error("Only images are allowed"));
}
};
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 }, // 5MB
fileFilter,
});
// 单文件上传
router.post("/upload", upload.single("avatar"), (req, res) => {
res.json({
success: true,
file: req.file,
});
});
// 多文件上传
router.post("/upload-multiple", upload.array("photos", 10), (req, res) => {
res.json({
success: true,
files: req.files,
});
});
环境变量配置
# .env
NODE_ENV=development
PORT=3000
MONGO_URI=mongodb://localhost:27017/myapp
JWT_SECRET=your_jwt_secret_key_here
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=[email protected]
SMTP_PASS=your_password
// config/config.js
require("dotenv").config();
module.exports = {
env: process.env.NODE_ENV || "development",
port: process.env.PORT || 3000,
mongoUri: process.env.MONGO_URI,
jwtSecret: process.env.JWT_SECRET,
smtp: {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT,
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
};
日志记录
// npm install winston
const winston = require("winston");
const logger = winston.createLogger({
level: process.env.LOG_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" }),
],
});
if (process.env.NODE_ENV !== "production") {
logger.add(
new winston.transports.Console({
format: winston.format.simple(),
})
);
}
module.exports = logger;
部署
PM2 部署
# 安装 PM2
npm install -g pm2
# 启动应用
pm2 start src/index.js --name "my-api"
# 查看状态
pm2 status
# 查看日志
pm2 logs
# 重启
pm2 restart my-api
# 开机自启
pm2 startup
pm2 save
Docker 部署
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "src/index.js"]
# docker-compose.yml
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- MONGO_URI=mongodb://mongo:27017/myapp
depends_on:
- mongo
mongo:
image: mongo:6
ports:
- "27017:27017"
volumes:
- mongo-data:/data/db
volumes:
mongo-data:
总结
Node.js 后端开发核心要点:
- Express 框架: 路由、中间件、错误处理
- 数据库操作: Mongoose ORM
- 身份认证: JWT Token
- 输入验证: 防止恶意输入
- 错误处理: 统一错误响应
- 安全防护: CORS、Helmet、Rate Limiting
- 部署: PM2、Docker
记住:后端的核心是数据安全和业务逻辑的正确性。