做后端这几年,不断有朋友问我新手入门到底选哪条路,我基本上都会提到 Node.js + Express + MongoDB 这条组合。这套技术栈不算新,但胜在轻、快、生态成熟,尤其适合做中小型项目的 API 服务、小程序后端、企业内部工具,甚至是一些快速验证的原型系统。今天这篇就结合我自己从零搭过好几个项目的实际体会,把 node、express、mongodb 这套后端开发的完整路径梳理一遍,从环境准备、项目结构、核心代码到常见的坑和排查方法,尽量讲得实在一点,能让照着做的人真的跑起来。
1. 选型之前:这套组合到底解决什么问题
1.1 三个组件各管哪一段
先说清楚 Node.js、Express、MongoDB 在整套架构里的分工。Node.js 是运行时环境,它让 JavaScript 能跑在服务器上,负责处理网络请求、读写文件、调用系统能力。Express 是运行在 Node.js 之上的 Web 应用框架,它帮你把 HTTP 请求路由、中间件处理、参数解析这些脏活累活封装好,你不用自己写原生的 http.createServer 再手动去 parse URL。MongoDB 是文档型数据库,数据以类 JSON 的 BSON 格式存储,字段可以灵活变化,不需要预先定义表结构。
这三者合在一起,最直观的价值就是“语言统一”。前端用 JavaScript,后端也写 JavaScript,数据从数据库查出来是 JSON-like 结构,接口返回的也是 JSON,整条链路的数据形态几乎不需要做转换。对个人开发者或者小团队来说,心智负担会小很多,一个人同时顾前端和后端也不至于在语言切换上消耗太多精力。
1.2 什么场景适合、什么场景不建议
我做过的项目里,这类组合最适合的是:数据模型变化较快、需要快速迭代的初创产品;读写并发不算极端的管理系统;需要跟前端紧密配合的全栈项目;还有教学演示、比赛 demo 这类对开发速度要求高但对极端性能不敏感的场景。
但如果你的业务是强事务类型的,比如账务系统、订单库存这类需要复杂跨文档一致性保证的场景,MongoDB 做起来会比较别扭。虽然它支持事务,但心智模型始终不是它的强项。另外如果团队里全是 Java 背景、对 JVM 体系更熟,那强行上 Node 也不明智。技术选型永远不是选最好的,而是选当下最适合团队和业务的那个。
1.3 为什么 Express 仍是首选而不是 NestJS 等框架
现在提起 Node 后端,很多人会推荐 NestJS,它引入了依赖注入、模块化、装饰器这些概念,架构上确实更规整,适合大型团队。但 Express 的价值在于极低的上手门槛和极高的自由度。它的中间件模型极其直观,一个请求从进入到返回,就是沿着中间件链一路走下来,你随时可以插一段逻辑进去。这种透明性对新手理解 HTTP 服务本质非常有帮助。我自己带过的几个新人,都是先靠 Express 把请求处理流程彻底搞明白,再去接触更重的框架就顺理成章了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与工程初始化避坑指南
2.1 Node.js 版本管理:别再直接去官网装一个用到底
第一次装 Node 的朋友最容易踩的坑,就是用安装包装了一个版本之后,哪天某个项目需要旧的 Node 版本,比如早期 node-sass 项目锁在 14,而现在项目普遍要求 18 或 20,这时候卸载重装非常痛苦。
我的建议是直接用 nvm(Node Version Manager)管理 Node 版本。它可以让你在同一台机器上随时切换多个 Node 版本,不同项目的 .nvmrc 文件里写明版本号,进目录后 nvm use 就能切过去。实际操作中几个常用命令:
bash复制# 安装指定版本
nvm install 20.11.0
# 查看已安装列表
nvm list
# 切换版本
nvm use 20.11.0
# 把某个版本设为默认
nvm alias default 20.11.0
如果你用的是 Windows,建议找 nvm-windows 这个发行版,注意它跟 Linux/macOS 上的 nvm 不是同一个项目,安装路径、命令风格有一点点差别,比如 Windows 上管理已安装版本用 nvm list,但安装时要以管理员身份运行命令行,不然创建符号链接那一步容易报权限错误。
2.2 MongoDB 安装的两个高频问题
MongoDB 的安装在不同系统下体验差别很大。Ubuntu/Debian 上我推荐用官方 apt 仓库安装,直接用发行版自带的源经常拿不到最新版本,甚至可能出现你要 6.x 结果只有 4.x 的尴尬情况。用官方仓库的方式:
bash复制# 导入 MongoDB 官方 GPG 公钥
wget -qO - https://www.mongodb.org/static/pgp/server-7.0.asc | sudo apt-key add -
# 写入 apt 源
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
# 更新并安装
sudo apt-get update
sudo apt-get install -y mongodb-org
这里有个很常见的坑:安装完成后 mongod 进程起不来,十有八九是 /data/db 目录不存在或者当前用户没有写权限。你可以在启动命令里显式指定数据目录:
bash复制mkdir -p ~/data/db
mongod --dbpath ~/data/db
Windows 上安装 MongoDB 时,如果勾选了安装服务选项,安装过程中偶尔会卡在“正在启动服务”这步,一般是因为本机 27017 端口被占用或者服务权限不够。解决的思路是先不勾选服务方式安装,装完后手动用 mongod --dbpath 方式启动验证,确认没问题再考虑注册成服务。
2.3 项目管理工具选择与项目初始化
选包管理器上,新项目我直接建议用 npm(Node 自带)或者 pnpm。npm 的 package-lock.json 能保证团队成员装的依赖版本一致,pnpm 则胜在磁盘占用小、安装速度快。如果你的项目会被部署到国内服务器,可以把 registry 切换到国内镜像,实测安装速度提升非常明显:
bash复制npm config set registry https://registry.npmmirror.com
项目初始化只需要一行命令:
bash复制npm init -y
然后手动安装 Express 和 MongoDB 的 Node 驱动,如果不想直接操作底层的驱动,也可以装 Mongoose 这类 ODM 来管理数据模型:
bash复制npm install express
npm install mongodb
# 或者
npm install mongoose
需要重点提一句:目前 npm 上默认安装的 Express 已经是 5.x 版本。Express 5 跟 4.x 有一些不兼容的改动,比如路由通配符语法、弃用了一些回调 API。很多老教程写的 app.get('*', ...) 在 Express 5 里会直接报错,要用 app.get('/*splat', ...) 或者改用中间件处理。如果你是想照着老经验做项目,建议先显式安装 4.x 版本:
bash复制npm install express@4.21.2
我个人现在做新项目会直接用 Express 5,但如果你去看老项目或者团队有历史代码,注意版本差异,别把 4.x 的语法硬套到 5 上。
3. 从空目录到一个能跑的接口服务
3.1 目录结构怎么摆才能不越写越乱
我见过太多人把所有代码堆在一个 app.js 里,路由也写里面、数据库连接也写里面、业务逻辑也写里面,刚开始几百行还能忍,做到后面随便加一个功能都要在几百上千行里滚动找位置。
我习惯的最小可维护目录结构大致是这样:
text复制project-root/
├── src/
│ ├── app.js # Express 实例创建、中间件注册
│ ├── server.js # 启动入口,监听端口
│ ├── config/
│ │ └── db.js # 数据库连接配置
│ ├── models/ # Mongoose 数据模型
│ │ └── user.js
│ ├── routes/ # 路由定义
│ │ └── user.js
│ ├── controllers/ # 业务处理逻辑
│ │ └── userController.js
│ └── middlewares/ # 自定义中间件
│ └── auth.js
├── .env # 环境变量(不进 git)
├── package.json
└── .gitignore
这个结构的好处是每个文件职责单一,路由只做转发,控制器处理业务,模型负责和数据库打交道。新手不要一上来就追求什么复杂的分层架构,先做到路由、模型、业务三件事分开,你后续的维护成本就能下降一大半。
3.2 Express 里最重要的两个概念:中间件和路由
理解 Express,核心就理解两件事:中间件(middleware)和路由(router)。
中间件本质就是个函数,签名是 (req, res, next)。每个请求进入服务端后,会依次经过所有匹配的中间件函数,每个中间件可以做三件事:修改 req 或 res 对象、返回响应、调用 next() 放行给下一个中间件。整个流转就像流水线,每个工位处理完自己的工序,再把半成品传给下一环节。
实际开发中,中间件最常见的用途是解析请求体、打印日志、做登录鉴权、统一处理错误。比如现在几乎所有接口都要求支持 JSON 请求体,那就需要注册内置的 JSON 解析中间件:
javascript复制// src/app.js
const express = require('express');
const app = express();
// 解析 application/json 请求体,挂载到 req.body
app.use(express.json());
// 自定义日志中间件:记录每个请求的方法和路径
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.originalUrl}`);
next();
});
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
module.exports = app;
路由则负责把不同的 URL 和 HTTP 方法映射到对应的处理函数。比如用户模块的接口,我会单独建一个路由文件:
javascript复制// src/routes/user.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.list);
router.get('/:id', userController.detail);
router.post('/', userController.create);
router.put('/:id', userController.update);
router.delete('/:id', userController.remove);
module.exports = router;
然后在 app.js 里把它挂载到 /api/users 前缀上:
javascript复制app.use('/api/users', require('./routes/user'));
这样你访问 /api/users/123 时,先去掉前缀 /api/users,剩下来的 /:id 路径参数 123 就会被 Express 解析到 req.params.id 中。
3.3 连接 MongoDB:两种驱动你至少要会一种
Node 连 MongoDB 有两条路:直接用官方 mongodb 驱动,或者用 Mongoose。前者更底层、性能更好掌控;后者提供了数据建模、校验、中间件等抽象。
如果你用官方驱动,连接和查询大概是这样的:
javascript复制// src/config/db.js
const { MongoClient } = require('mongodb');
const uri = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
const client = new MongoClient(uri);
async function connectDB() {
await client.connect();
console.log('MongoDB connected');
return client;
}
function getDB() {
return client.db('myapp');
}
module.exports = { connectDB, getDB };
查数据的代码就是:
javascript复制const { getDB } = require('../config/db');
async function listUsers() {
const db = getDB();
const users = await db.collection('users').find({}).toArray();
return users;
}
如果使用 Mongoose,第一步是定义 Schema(模式)。Schema 的作用是告诉 Mongoose 这个集合里的文档大致长什么样,哪些字段是必须的,字段类型是什么。这个设计其实弥补了 MongoDB 无模式的短板,让代码层面的数据约束变得可控:
javascript复制// src/models/user.js
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, lowercase: true },
age: { type: Number, min: 0 },
tags: [String],
createdAt: { type: Date, default: Date.now }
});
module.exports = mongoose.model('User', userSchema);
连接数据库和增删改查的操作路径会变得更像“面向对象”,具体见下一节的实战。
4. 实战落地:用 Mongoose 完成一整套增删改查
4.1 连接初始化:别在每次请求时都去连一次
新手最容易写出这样的代码:每个请求处理函数里都去 mongoose.connect() 一次,然后报各种连接超时或者连接数爆炸。正确做法是应用启动时连接一次,之后复用同一个连接。
启动入口文件负责连接数据库后再监听端口:
javascript复制// src/server.js
const mongoose = require('mongoose');
const app = require('./app');
const PORT = process.env.PORT || 3000;
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017/myapp';
async function start() {
try {
await mongoose.connect(MONGODB_URI);
console.log('MongoDB connected');
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
} catch (err) {
console.error('Failed to connect MongoDB:', err.message);
process.exit(1);
}
}
start();
注意 mongoose.connect() 返回的是 Promise,必须等待它成功后再启动 HTTP 服务。如果你把监听端口写在 connect 之前,数据库挂了服务还在跑,接口一调到数据就报错,排查起来反而更麻烦。
4.2 控制器中的 CRUD:async/await + try/catch
控制器的任务是接收路由传进来的参数,调用模型操作数据库,最后把处理结果返回给客户端。我通常会在 controller 里写完整的 try/catch,并把错误交给统一的错误处理中间件。
以下是一组完整的用户模块 CRUD 示例:
javascript复制// src/controllers/userController.js
const User = require('../models/user');
// 查询用户列表,支持可选翻页
exports.list = async (req, res, next) => {
try {
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.pageSize) || 10;
const skip = (page - 1) * pageSize;
const users = await User.find({})
.skip(skip)
.limit(pageSize)
.sort({ createdAt: -1 });
const total = await User.countDocuments();
res.json({
data: users,
pagination: { page, pageSize, total }
});
} catch (err) {
next(err);
}
};
// 查询单个用户
exports.detail = async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
res.json({ data: user });
} catch (err) {
next(err);
}
};
// 新增用户
exports.create = async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json({ data: user });
} catch (err) {
next(err);
}
};
// 更新用户
exports.update = async (req, res, next) => {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true, // 返回更新后的文档
runValidators: true // 更新时同样校验 Schema 规则
});
if (!user) {
return res.status(404).json({ message: '用户不存在' });
}
res.json({ data: user });
} catch (err) {
next(err);
}
};
// 删除用户
exports.remove = async (req, res, next) => {
try {
const result = await User.findByIdAndDelete(req.params.id);
if (!result) {
return res.status(404).json({ message: '用户不存在' });
}
res.status(204).end();
} catch (err) {
next(err);
}
};
几个细节值得展开说一下。第一,findByIdAndUpdate 默认返回的是更新前的文档,很多人第一次用都会踩这个坑,所以一定要传 { new: true }。第二,默认情况下 findByIdAndUpdate 不会执行 Schema 里定义的字段校验,很多数据问题就是这么悄悄溜进去的,runValidators: true 必须加上。第三,删除成功以后返回 204 No Content 比返回 200 带一段说明文字更符合接口语义,前端处理起来也更干净。
4.3 统一错误处理:别让后端报错把堆栈裸奔给前端
如果没有错误处理中间件,Express 默认会把带着堆栈信息的 HTML 错误页面返回给客户端,既不友好也不安全。我的习惯是注册一个放在所有路由之后、并且带有四个参数的中间件:
javascript复制// src/middlewares/errorHandler.js
module.exports = function errorHandler(err, req, res, next) {
console.error(err.stack);
// Mongoose 校验错误
if (err.name === 'ValidationError') {
return res.status(400).json({
message: '参数校验失败',
errors: Object.values(err.errors).map(e => e.message)
});
}
// MongoDB 主键错误,比如唯一索引冲突
if (err.code === 11000) {
return res.status(409).json({ message: '数据已存在' });
}
res.status(err.status || 500).json({
message: err.message || '服务器内部错误'
});
};
然后在 app.js 里挂载:
javascript复制const errorHandler = require('./middlewares/errorHandler');
// ...路由都在这里注册...
app.use('/api/users', require('./routes/user'));
// 这行必须在最后,承接前面所有 next(err) 抛过来的错误
app.use(errorHandler);
记住,Express 错误处理中间件必须有四个参数 (err, req, res, next),少一个都不行,框架是靠函数参数的个数来识别它是普通中间件还是错误处理中间件的。
4.4 配置管理:硬编码连接串是看得见的隐患
很多教程里连接字符串、端口号都直接写在代码里,这在小 demo 里没毛病,但项目一旦多人协作或者部署到不同环境,这种方式就是灾难。我现在的做法是使用环境变量配合 .env 文件。
安装依赖:
bash复制npm install dotenv
入口文件最顶部加载配置:
javascript复制require('dotenv').config();
在项目根目录创建 .env 文件:
text复制PORT=3000
MONGODB_URI=mongodb://localhost:27017/myapp
JWT_SECRET=please_change_me
然后把 .env 写进 .gitignore,避免敏感配置被提交到代码仓库。为什么强调这一点?我见过不止一次有人把真实数据库的账号密码提交到 GitHub 上,接着被爬虫扫到,数据库被勒索清空的事件。密钥这种东西,一旦泄漏就必须当作已经暴露处理,立刻轮换。
5. 接口开发中的细节打磨与性能注意点
5.1 RESTful 接口设计的基本约定
接口设计上太自由其实对前后端协作是不利的。我倾向于遵循一套简单清晰的 REST 约定:资源的复数名词作为路径,方法表达动作。比如文章模块就是 GET /api/articles、GET /api/articles/:id、POST /api/articles、PUT /api/articles/:id、DELETE /api/articles/:id,分别对应列表、详情、新增、更新、删除。
另外几个约定对前端非常友好:列表接口返回 { data: [...], pagination: {...} } 结构,让前端能直接拿到翻页信息;错误时返回 { message: '可读的错误描述' },不要只丢一个数字状态码;不管成功失败,统一用 JSON 格式返回,不要一会儿返回 JSON、一会儿又返回纯文本。
关于参数校验,很多人图省事把校验逻辑写在 controller 里,结果每个函数开头就是一个 if 长龙。更体面的方式是用第三方库如 Joi 或 express-validator 做参数校验,把规则声明成独立的 schema,代码可读性会好很多。有没有必要上这类库取决于项目规模,但至少对自己写的接口要有校验意识,不能客户端传一个空字符串、传一个 NaN,后端就毫无防备地往数据库里写。
5.2 查询性能的几个低级误区
MongoDB 很容易误用,最大的误解是“这数据库不需要优化”。我在实际项目里看到最多的性能问题主要有三种:
第一种是全表扫描不带条件。业务早期数据量只有几百条,全表扫也没感觉,到几万条、几十万条时接口延迟立刻暴露。解决办法是给高频查询字段建索引:
javascript复制// 给 username 字段建唯一索引
await User.collection.createIndex({ username: 1 }, { unique: true });
// 给复合查询条件建复合索引
await User.collection.createIndex({ status: 1, createdAt: -1 });
第二种是返回了前端不需要的大字段。很多业务文档里会存描述文本、图片 base64 甚至记录一些日志型的大块内容,列表接口没必要返回全字段,查询时可以投影:
javascript复制// 只返回 username、email、createdAt
const users = await User.find({})
.select('username email createdAt')
.lean();
第三种是不分页却把全部数据返回给前端。哪怕是小工具,列表接口也建议默认分页,limit 和 skip 是成本最低的保险。
我特别推荐在查询后加 .lean(),这个方法会把 Mongoose 文档转成纯 JavaScript 对象,跳过 Mongoose 实例化那一层开销,查询性能有明显提升。代价是返回的对象失去了调用 save() 这类方法的能力,但纯粹的列表展示场景完全够用。
5.3 几个必学的 Mongoose 高级用法
开发到一定阶段,你会频繁用到下面几个能力。
第一是 populate。MongoDB 本身没有外键概念,但业务里经常需要关联查询。比如文章里有作者 ID,查文章时希望把作者姓名带出来。定义模型时用 ref 关联:
javascript复制const articleSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }
});
查询时:
javascript复制const articles = await Article.find().populate('author', 'username email');
要注意 populate 本质上是额外发一次查询去关联集合拿数据,如果关联层级很深或者数据量很大,性能会成问题。做联表关联特别复杂的报表时,应该考虑在设计阶段就冗余存储一些展示字段,比如在文章里直接冗余一个 authorName,省掉每次查询的关联开销。
第二是预保存钩子(pre-save middleware)。处理密码哈希时特别有用,在保存前自动执行逻辑:
javascript复制const bcrypt = require('bcryptjs');
userSchema.pre('save', async function (next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
第三是静态方法和实例方法。把对模型的常用操作封装成方法,避免 controller 里到处写重复的查询条件。比如:
javascript复制userSchema.statics.findByEmail = function (email) {
return this.findOne({ email: email.toLowerCase() });
};
之后控制器的代码就会非常简洁:const user = await User.findByEmail(req.body.email);
6. 新手必看的常见问题与排查经验
6.1 端口被占用、模块加载报错这类启动问题
老生常谈但天天有人遇到的是 EADDRINUSE,端口被占用。在 Windows 下排查:
bat复制netstat -ano | findstr :3000
taskkill /PID <进程号> /F
Linux 下用:
bash复制lsof -i :3000
kill -9 <PID>
另外,如果你把依赖装好之后运行项目报 Cannot find module 'express',先别怀疑是代码问题,大概率是你在项目根目录之外执行了 node app.js,Node 的模块解析默认从当前工作目录往上层找 node_modules,找不到自然就报错。在项目根目录执行才是正解。还有一种情况是把项目复制到别的机器后忘了跑 npm install,也会报同样的错,需要先安装依赖。
6.2 Node 版本差异导致的诡异报错
后端开发中特别多项目跑不起来的根因是 Node 版本不对。上面提到 nvm 时我也说过,node-sass 这类老库基本绑定在老版本 Node 上,在新版本 Node 环境下安装时经常报编译错误或者直接不兼容。SyntaxError: The requested module 'node:util' does not provide an export named ... 这类 ESM 导出报错,也常跟 Node 版本以及项目的模块系统配置有关。
遇到跟版本相关的报错,第一反应就是用 node -v 看清楚当前版本,再用 nvm list 确认有没有装别的版本,切换到项目要求的版本试试。一个项目对应一个稳定的开发环境,是后端开发的基本素养。
6.3 MongoDB 连接失败与数据丢失风险排查
MongoDB 连接失败的常见原因就几个:服务没启动、端口被占用、连接串写错、鉴权没配。我排查的第一步永远是先确认 mongod 进程是否在跑:
bash复制# 查看进程
ps aux | grep mongod
# 如果没起来,前台启动看日志
mongod --dbpath ~/data/db
如果服务在跑但 Node 连不上,再用 mongosh 命令行工具手动试连。能连上说明 MongoDB 本身没问题,问题就在 Node 的连接串或者网络层。
另外必须提醒一句备份的重要性。MongoDB 默认不开启访问控制,也就是任何人只要能访问到你的 27017 端口,就能连接数据库做任意操作。生产环境至少要做到:启用认证、创建专用于业务的低权限账号、把实例部署在防火墙后面不直接暴露公网、定期用 mongodump 做备份。
6.4 关于调试工具的个人心得
接口开发阶段,我强烈建议装一个 Postman 或者 Apifox 之类的接口调试工具。很多人一开始就在浏览器地址栏直接访问 POST 接口,结果发现浏览器默认发的是 GET 请求,自然看不到效果,还会疑惑“我明明写了接口为什么不行”。其实用接口调试工具还有一个隐藏好处:可以保存历史请求,过了一个月再回来调试某个接口,翻记录就能找到上次测的请求和参数。
查看 MongoDB 里的数据,我更多是在命令行用 mongosh,因为快。如果数据要可视化地看,MongoDB Compass 官方图形界面工具也值得装一个,特别适合刚入门想直观理解集合、文档概念的人。另一个建议是给本地开发环境装一个像 nodemon 这样的自动重启工具:
bash复制npm install -D nodemon
package.json 里配置:
json复制"scripts": {
"dev": "nodemon src/server.js",
"start": "node src/server.js"
}
改完代码保存,服务自动重启,开发效率提升非常明显。
6.5 一个容易被忽略的生产环境问题
本地开发时你大概会习惯用 npm run dev 跑 nodemon,但在服务器上部署时一定要用 npm start 跑正式的 node 命令。nodemon 依赖的是开发态的文件监听能力,既消耗额外 CPU,而且代码更新不该由它来管理,生产环境应该由进程守护工具监管,崩了能自动拉起。Node 项目比较常见的守护方案是 pm2,一个命令搞定:
bash复制npm install -g pm2
pm2 start src/server.js --name myapp
pm2 save
pm2 logs myapp
pm2 除了进程守护,还能帮你管理多实例、查看日志、设置开机自启。后端服务上线,建议先把 pm2 用熟。
7. 关于继续深入学习的一点个人建议
写完一个能跑通的增删改查接口,只代表你摸到了后端开发的门槛,接下来有太多东西可以继续往深挖。我的建议是不要急着学下一个框架,先把当前这套吃透:中间件机制是不是能自己手写一个简化版?错误处理有没有覆盖所有分支?数据库索引是不是真的理解走了哪个索引?这些底层问题搞明白了,以后换任何语言、任何框架,你会发现核心思想都是相通的。
从学习路径上,可以依次攻克鉴权与安全(JWT、密码加密、接口限流)、日志与监控、单元测试和接口测试、Docker 容器化部署、持续集成。每打通一个环节,你对后端开发的理解就会上一个台阶。
最后说一个我踩过很多次才意识到的问题:写得好的代码不是功能跑通就算完,而是三个月后你自己回来看还能快速看懂,换一个人接手也不至于抓狂。所以从一开始就养成拆分模块、写清晰注释、保留可读性好的接口文档的习惯,这些短期内看似“浪费时间”的工作,长期会带给你超额的回报。做后端的人,耐得住性子打磨基本功,比追逐技术热点要重要得多。
