Node.js 后端开发入门教程
📖 本教程面向零基础读者,从零开始讲解 Node.js 后端开发的核心知识。学完本教程,你将能够独立搭建一个完整的 RESTful API 服务。
目录
- Node.js 是什么
- 安装 Node.js
- 第一个 Node.js 程序
- 模块系统
- npm 包管理器
- 文件操作
- HTTP 服务基础
- Express 框架入门
- REST API 设计与实现
- 数据库连接
- 实战项目:搭建完整的 RESTful API
- 常见问题与调试技巧
- 下一步学习建议
1. Node.js 是什么
1.1 基本概念
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境。简单来说,它让你可以在浏览器之外运行 JavaScript 代码。
在 Node.js 出现之前,JavaScript 只能在浏览器中运行,主要用于网页的交互效果。Node.js 的出现打破了这个限制,让 JavaScript 成为了一门可以编写服务端程序的语言。
1.2 为什么选择 Node.js
- 语言统一:前后端都用 JavaScript,降低学习成本
- 高性能:基于 V8 引擎,执行速度快
- 事件驱动、非阻塞 I/O:非常适合处理高并发场景,如实时应用、API 服务
- 丰富的生态系统:npm 拥有超过百万个开源包
- 活跃的社区:遇到问题容易找到解决方案
1.3 Node.js 的适用场景
| 适用场景 | 说明 |
|---|---|
| REST API 服务 | 最常见的用途,为前端或移动端提供数据接口 |
| 实时应用 | 聊天应用、协作工具、实时通知 |
| 微服务架构 | 轻量级服务,易于部署和扩展 |
| 命令行工具 | 构建开发工具、脚本自动化 |
| 爬虫程序 | 数据抓取和处理 |
1.4 Node.js 的架构
┌─────────────────────────────────────┐
│ 你的 JavaScript 代码 │
├─────────────────────────────────────┤
│ Node.js 标准库 │
│ (fs, http, path, events...) │
├─────────────────────────────────────┤
│ V8 引擎 (执行JS) │
├─────────────────────────────────────┤
│ libuv (异步 I/O 处理) │
├─────────────────────────────────────┤
│ 操作系统 │
└─────────────────────────────────────┘
Node.js 的核心是 事件循环(Event Loop) 机制。它通过单线程处理请求,但利用 libuv 库在底层进行异步 I/O 操作,从而实现高并发。
2. 安装 Node.js
2.1 下载安装
方式一:官网下载(推荐新手)
访问 Node.js 官网,下载 LTS(长期支持)版本,按提示安装即可。
方式二:使用 nvm 管理版本(推荐进阶用户)
# 安装 nvm(Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# 重新加载 shell 配置
source ~/.bashrc
# 安装最新 LTS 版本
nvm install --lts
# 使用该版本
nvm use --lts
2.2 验证安装
# 查看 Node.js 版本
node --version
# 输出示例:v22.22.1
# 查看 npm 版本
npm --version
# 输出示例:10.9.0
如果两个命令都能正常输出版本号,说明安装成功。
2.3 了解 REPL 环境
Node.js 自带一个交互式命令行环境(REPL),可以直接在终端中执行 JavaScript 代码:
# 进入 REPL
node
# 试试这些命令
> 1 + 1
2
> console.log('Hello Node.js!')
Hello Node.js!
> process.version
'v22.22.1'
# 退出 REPL(按两次 Ctrl+C 或输入 .exit)
> .exit
REPL 非常适合快速测试代码片段和学习 JavaScript 语法。
3. 第一个 Node.js 程序
3.1 创建项目目录
mkdir my-first-node-app
cd my-first-node-app
3.2 编写代码
创建文件 hello.js:
// hello.js
// 获取命令行参数(跳过前两个:node 和 文件路径)
const args = process.argv.slice(2);
if (args.length === 0) {
console.log('你好!请告诉我你的名字:');
console.log('用法:node hello.js <你的名字>');
} else {
const name = args[0];
console.log(`你好,${name}!欢迎学习 Node.js!`);
console.log(`当前时间:${new Date().toLocaleString('zh-CN')}`);
console.log(`Node.js 版本:${process.version}`);
console.log(`运行平台:${process.platform}`);
}
3.3 运行程序
node hello.js
# 输出:你好!请告诉我你的名字...
node hello.js 小明
# 输出:你好,小明!欢迎学习 Node.js!
# 当前时间:2026/5/28 08:30:00
# Node.js 版本:v22.22.1
# 运行平台:linux
3.4 初始化项目
每个正式的 Node.js 项目都应该通过 npm init 初始化:
npm init -y
这会生成 package.json 文件,它是项目的"身份证",记录了项目名称、版本、依赖等信息:
{
"name": "my-first-node-app",
"version": "1.0.0",
"description": "",
"main": "hello.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
你可以编辑 package.json,在 scripts 中添加自定义命令:
{
"scripts": {
"start": "node hello.js 小明",
"dev": "node --watch hello.js 小明"
}
}
然后用 npm start 或 npm run dev 来运行。--watch 参数会在文件修改后自动重启,方便开发调试。
4. 模块系统
模块系统是 Node.js 最重要的概念之一。它让你可以把代码拆分成多个文件,实现代码复用和组织管理。
4.1 CommonJS 模块(Node.js 默认)
导出模块 —— 创建 math.js:
// math.js - 数学工具模块
// 方式一:逐个导出
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
function multiply(a, b) {
return a * b;
}
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为零');
}
return a / b;
}
// 导出函数
module.exports = { add, subtract, multiply, divide };
// 方式二(简写):直接在 exports 上挂载
// exports.add = add;
// exports.subtract = subtract;
导入模块 —— 创建 app.js:
// app.js
const math = require('./math');
console.log(math.add(10, 5)); // 15
console.log(math.subtract(10, 5)); // 5
console.log(math.multiply(10, 5)); // 50
console.log(math.divide(10, 5)); // 2
// 也可以解构导入
const { add, subtract } = require('./math');
console.log(add(100, 200)); // 300
4.2 ES Module(现代写法)
从 Node.js 12 开始支持 ES Module 语法。需要在 package.json 中声明:
{
"type": "module"
}
然后就可以使用 import/export 语法:
// math.mjs(或 .js + "type": "module")
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
// app.mjs
import { add, subtract } from './math.mjs';
console.log(add(10, 5)); // 15
4.3 内置模块
Node.js 提供了大量内置模块,无需安装即可使用:
// 路径处理
const path = require('path');
console.log(path.join('/root', 'data', 'file.txt'));
// 输出:/root/data/file.txt
console.log(path.extname('photo.jpg'));
// 输出:.jpg
// 操作系统信息
const os = require('os');
console.log('操作系统:', os.type());
console.log('总内存:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2) + ' GB');
console.log('空闲内存:', (os.freemem() / 1024 / 1024 / 1024).toFixed(2) + ' GB');
console.log('CPU 核心数:', os.cpus().length);
// 事件系统
const EventEmitter = require('events');
const emitter = new EventEmitter();
// 监听事件
emitter.on('greet', (name) => {
console.log(`你好,${name}!`);
});
// 触发事件
emitter.emit('greet', '小明'); // 输出:你好,小明!
// URL 解析
const url = require('url');
const parsed = new URL('https://example.com:3000/path?name=test&id=1');
console.log(parsed.hostname); // example.com
console.log(parsed.port); // 3000
console.log(parsed.pathname); // /path
console.log(parsed.searchParams.get('name')); // test
4.4 模块查找规则
当你写 require('express') 时,Node.js 按以下顺序查找:
- 核心模块:如
fs、http、path等,直接返回 - 相对/绝对路径:如
./math、/home/user/lib/utils - node_modules 目录:从当前目录向上逐级查找
node_modules文件夹
当前文件: /project/src/app.js
require('lodash') 的查找顺序:
/project/src/node_modules/lodash
/project/node_modules/lodash
/node_modules/lodash
5. npm 包管理器
npm(Node Package Manager)是 Node.js 的包管理工具,用于安装和管理第三方库。
5.1 常用命令
# 初始化项目
npm init -y
# 安装包(添加到 dependencies)
npm install express
npm i express # 简写
# 安装包(添加到 devDependencies,仅开发时使用)
npm install --save-dev nodemon
npm i -D nodemon # 简写
# 安装指定版本
npm install express@4.18.2
# 全局安装
npm install -g nodemon
# 卸载包
npm uninstall express
# 更新包
npm update
# 查看已安装的包
npm list
npm list --depth=0 # 只看顶层依赖
# 运行脚本
npm start # 运行 "start" 脚本
npm run dev # 运行 "dev" 脚本
5.2 package.json 详解
{
"name": "my-api-server",
"version": "1.0.0",
"description": "一个简单的 API 服务器",
"main": "index.js",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.6.0"
},
"devDependencies": {
"nodemon": "^3.0.0"
},
"engines": {
"node": ">=18.0.0"
}
}
dependencies:项目运行时需要的依赖devDependencies:仅开发时需要的依赖(如测试框架、代码格式化工具)scripts:自定义命令,用npm run <命令名>运行engines:指定 Node.js 版本要求
5.3 package-lock.json
package-lock.json 锁定了每个依赖的精确版本,确保团队成员和部署环境使用完全相同的依赖版本。这个文件应该提交到版本控制中。
6. 文件操作
文件操作是后端开发的基础能力。Node.js 的 fs(File System)模块提供了丰富的文件操作 API。
6.1 同步 vs 异步
Node.js 中的文件操作有两种模式:
- 同步(Sync):阻塞执行,代码简单但会卡住主线程
- 异步(Async):非阻塞执行,推荐在服务端使用
const fs = require('fs');
// ========== 同步方式(简单但会阻塞) ==========
try {
const data = fs.readFileSync('./data.txt', 'utf-8');
console.log('同步读取:', data);
} catch (err) {
console.error('读取失败:', err.message);
}
// ========== 异步方式(推荐) ==========
fs.readFile('./data.txt', 'utf-8', (err, data) => {
if (err) {
console.error('读取失败:', err.message);
return;
}
console.log('异步读取:', data);
});
// ========== Promise 方式(最推荐) ==========
const fsPromises = require('fs').promises;
async function readMyFile() {
try {
const data = await fsPromises.readFile('./data.txt', 'utf-8');
console.log('Promise 读取:', data);
} catch (err) {
console.error('读取失败:', err.message);
}
}
readMyFile();
6.2 文件读写
const fs = require('fs').promises;
const path = require('path');
async function fileOperations() {
const filePath = path.join(__dirname, 'example.txt');
// 写入文件(覆盖)
await fs.writeFile(filePath, 'Hello, Node.js!\n这是第二行内容。', 'utf-8');
console.log('文件写入成功');
// 追加内容
await fs.appendFile(filePath, '\n这是追加的内容。', 'utf-8');
console.log('内容追加成功');
// 读取文件
const content = await fs.readFile(filePath, 'utf-8');
console.log('文件内容:\n', content);
// 检查文件是否存在
try {
await fs.access(filePath);
console.log('文件存在');
} catch {
console.log('文件不存在');
}
// 获取文件信息
const stats = await fs.stat(filePath);
console.log('文件大小:', stats.size, '字节');
console.log('修改时间:', stats.mtime);
// 删除文件
// await fs.unlink(filePath);
// console.log('文件已删除');
}
fileOperations().catch(console.error);
6.3 目录操作
const fs = require('fs').promises;
const path = require('path');
async function dirOperations() {
const dirPath = path.join(__dirname, 'my-folder');
const subDir = path.join(dirPath, 'sub-folder');
// 创建目录(recursive: true 允许递归创建多层目录)
await fs.mkdir(subDir, { recursive: true });
console.log('目录创建成功');
// 在目录中创建几个文件
await fs.writeFile(path.join(dirPath, 'file1.txt'), '文件1的内容');
await fs.writeFile(path.join(dirPath, 'file2.json'), '{"name": "test"}');
await fs.writeFile(path.join(subDir, 'file3.txt'), '文件3的内容');
// 读取目录内容
const files = await fs.readdir(dirPath);
console.log('目录内容:', files);
// 输出:['file1.txt', 'file2.json', 'sub-folder']
// 读取目录内容(带详细信息)
const filesWithTypes = await fs.readdir(dirPath, { withFileTypes: true });
for (const file of filesWithTypes) {
const type = file.isDirectory() ? '📁 目录' : '📄 文件';
console.log(` ${type}: ${file.name}`);
}
// 递归列出所有文件
async function listAllFiles(dir, prefix = '') {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
console.log(`${prefix}📁 ${entry.name}/`);
await listAllFiles(fullPath, prefix + ' ');
} else {
console.log(`${prefix}📄 ${entry.name}`);
}
}
}
console.log('\n完整目录结构:');
await listAllFiles(dirPath);
}
dirOperations().catch(console.error);
6.4 JSON 文件操作
后端开发中经常需要读写 JSON 配置文件或数据文件:
const fs = require('fs').promises;
const path = require('path');
const configPath = path.join(__dirname, 'config.json');
// 读取 JSON 文件
async function readJSON(filePath) {
const data = await fs.readFile(filePath, 'utf-8');
return JSON.parse(data);
}
// 写入 JSON 文件(格式化输出)
async function writeJSON(filePath, data) {
await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
// 使用示例
async function main() {
// 创建初始配置
const config = {
app: { name: 'MyApp', version: '1.0.0' },
server: { port: 3000, host: 'localhost' },
database: { url: 'mongodb://localhost:27017/mydb' }
};
await writeJSON(configPath, config);
console.log('配置文件已创建');
// 读取并修改配置
const loaded = await readJSON(configPath);
loaded.server.port = 8080;
await writeJSON(configPath, loaded);
console.log('配置文件已更新');
}
main().catch(console.error);
7. HTTP 服务基础
Node.js 内置了 http 模块,无需任何第三方依赖就能创建 Web 服务器。
7.1 最简单的 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Hello, World!\n');
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
运行后,在浏览器访问 http://localhost:3000 就能看到页面。
7.2 处理不同路由
const http = require('http');
const server = http.createServer((req, res) => {
const { method, url } = req;
// 设置响应头
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (method === 'GET' && url === '/') {
res.writeHead(200);
res.end(JSON.stringify({ message: '欢迎访问首页' }));
} else if (method === 'GET' && url === '/about') {
res.writeHead(200);
res.end(JSON.stringify({ message: '关于我们' }));
} else if (method === 'GET' && url === '/api/users') {
res.writeHead(200);
res.end(JSON.stringify({
users: [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]
}));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: '页面未找到' }));
}
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
7.3 解析请求体
当客户端发送 POST 请求时,需要读取请求体数据:
const http = require('http');
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');
if (req.method === 'POST' && req.url === '/api/data') {
let body = '';
// 监听数据块
req.on('data', (chunk) => {
body += chunk.toString();
});
// 数据接收完毕
req.on('end', () => {
try {
const parsed = JSON.parse(body);
console.log('收到数据:', parsed);
res.writeHead(200);
res.end(JSON.stringify({
success: true,
received: parsed
}));
} catch (err) {
res.writeHead(400);
res.end(JSON.stringify({ error: '无效的 JSON 数据' }));
}
});
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: '未找到' }));
}
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
7.4 原生 HTTP 的局限
虽然原生 http 模块能用,但手动处理路由、请求体解析、错误处理等非常繁琐。这就是为什么实际开发中我们会使用 Express 框架。
8. Express 框架入门
Express 是 Node.js 最流行的 Web 框架,简洁灵活,是大多数 Node.js 项目的首选。
8.1 安装 Express
npm install express
8.2 基本用法
const express = require('express');
const app = express();
const PORT = 3000;
// 中间件:解析 JSON 请求体
app.use(express.json());
// 中间件:解析 URL 编码的请求体
app.use(express.urlencoded({ extended: true }));
// 路由
app.get('/', (req, res) => {
res.json({ message: 'Hello Express!' });
});
app.get('/about', (req, res) => {
res.json({ message: '关于我们' });
});
// 启动服务器
app.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});
8.3 路由详解
const express = require('express');
const app = express();
app.use(express.json());
// ========== 基础路由 ==========
// GET 请求
app.get('/api/products', (req, res) => {
res.json({ products: ['手机', '电脑', '平板'] });
});
// POST 请求
app.post('/api/products', (req, res) => {
const { name, price } = req.body;
res.status(201).json({ message: `商品 ${name} 创建成功`, price });
});
// PUT 请求
app.put('/api/products/:id', (req, res) => {
const { id } = req.params;
const { name, price } = req.body;
res.json({ message: `商品 ${id} 更新成功`, name, price });
});
// DELETE 请求
app.delete('/api/products/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `商品 ${id} 删除成功` });
});
// ========== 路由参数 ==========
app.get('/api/users/:id', (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// 多个参数
app.get('/api/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});
// ========== 查询参数 ==========
// GET /api/search?q=nodejs&page=1&limit=10
app.get('/api/search', (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({
query: q,
page: Number(page),
limit: Number(limit)
});
});
app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
8.4 中间件
中间件是 Express 的核心概念。每个中间件函数可以访问请求对象(req)、响应对象(res)和下一个中间件函数(next)。
const express = require('express');
const app = express();
app.use(express.json());
// ========== 自定义中间件 ==========
// 请求日志中间件
function logger(req, res, next) {
const start = Date.now();
const { method, url } = req;
// 在响应结束时记录耗时
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${method} ${url} ${res.statusCode} - ${duration}ms`);
});
next(); // 调用下一个中间件
}
// 认证中间件
function authenticate(req, res, next) {
const token = req.headers.authorization;
if (!token || token !== 'Bearer my-secret-token') {
return res.status(401).json({ error: '未授权,请提供有效的 token' });
}
// 将用户信息挂载到 req 上
req.user = { id: 1, name: '张三' };
next();
}
// 使用全局中间件
app.use(logger);
// 公开路由(不需要认证)
app.get('/api/public', (req, res) => {
res.json({ message: '这是公开数据' });
});
// 受保护的路由(需要认证)
app.get('/api/profile', authenticate, (req, res) => {
res.json({ user: req.user });
});
// 也可以对一组路由使用中间件
app.use('/api/admin', authenticate);
app.get('/api/admin/dashboard', (req, res) => {
res.json({ message: '管理后台数据' });
});
app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
8.5 错误处理
const express = require('express');
const app = express();
app.use(express.json());
app.get('/api/users/:id', async (req, res, next) => {
try {
const { id } = req.params;
// 模拟数据库查询
if (id === '999') {
throw new Error('用户不存在');
}
res.json({ id, name: '张三' });
} catch (err) {
next(err); // 将错误传递给错误处理中间件
}
});
// 错误处理中间件(必须有 4 个参数)
app.use((err, req, res, next) => {
console.error('错误:', err.message);
res.status(500).json({
error: '服务器内部错误',
message: err.message
});
});
app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
9. REST API 设计与实现
9.1 REST 基本概念
REST(Representational State Transfer)是一种 API 设计风格。遵循 REST 规范的 API 叫 RESTful API。
核心原则:
- 资源导向:每个 URL 代表一个资源
- 使用 HTTP 方法:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
- 无状态:每个请求包含所有必要信息
- 统一的响应格式
9.2 设计规范
| 操作 | HTTP 方法 | URL | 说明 |
|---|---|---|---|
| 获取列表 | GET | /api/users | 获取所有用户 |
| 获取单个 | GET | /api/users/:id | 获取指定用户 |
| 创建 | POST | /api/users | 创建新用户 |
| 更新 | PUT | /api/users/:id | 更新指定用户 |
| 删除 | DELETE | /api/users/:id | 删除指定用户 |
9.3 完整的 REST API 示例
const express = require('express');
const app = express();
app.use(express.json());
// 模拟数据库(用内存中的数组代替)
let users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com', age: 28 },
{ id: 2, name: '李四', email: 'lisi@example.com', age: 32 },
{ id: 3, name: '王五', email: 'wangwu@example.com', age: 25 }
];
let nextId = 4;
// ========== 工具函数 ==========
function findUserById(id) {
return users.find(u => u.id === Number(id));
}
function validateUser(data) {
const errors = [];
if (!data.name || data.name.trim() === '') {
errors.push('姓名不能为空');
}
if (!data.email || !data.email.includes('@')) {
errors.push('请提供有效的邮箱地址');
}
if (data.age !== undefined && (data.age < 0 || data.age > 150)) {
errors.push('年龄必须在 0-150 之间');
}
return errors;
}
// ========== 路由 ==========
// GET /api/users - 获取用户列表(支持分页和筛选)
app.get('/api/users', (req, res) => {
let { page = 1, limit = 10, name, minAge, maxAge } = req.query;
page = Math.max(1, Number(page));
limit = Math.min(100, Math.max(1, Number(limit)));
let filtered = [...users];
// 按姓名筛选
if (name) {
filtered = filtered.filter(u =>
u.name.includes(name)
);
}
// 按年龄范围筛选
if (minAge) {
filtered = filtered.filter(u => u.age >= Number(minAge));
}
if (maxAge) {
filtered = filtered.filter(u => u.age <= Number(maxAge));
}
// 分页
const total = filtered.length;
const start = (page - 1) * limit;
const paginated = filtered.slice(start, start + limit);
res.json({
success: true,
data: paginated,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
});
});
// GET /api/users/:id - 获取单个用户
app.get('/api/users/:id', (req, res) => {
const user = findUserById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: '用户不存在'
});
}
res.json({ success: true, data: user });
});
// POST /api/users - 创建用户
app.post('/api/users', (req, res) => {
const errors = validateUser(req.body);
if (errors.length > 0) {
return res.status(400).json({ success: false, errors });
}
// 检查邮箱是否已存在
if (users.some(u => u.email === req.body.email)) {
return res.status(409).json({
success: false,
error: '该邮箱已被注册'
});
}
const newUser = {
id: nextId++,
name: req.body.name.trim(),
email: req.body.email.trim(),
age: req.body.age || null
};
users.push(newUser);
res.status(201).json({ success: true, data: newUser });
});
// PUT /api/users/:id - 更新用户(全量更新)
app.put('/api/users/:id', (req, res) => {
const user = findUserById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: '用户不存在'
});
}
const errors = validateUser(req.body);
if (errors.length > 0) {
return res.status(400).json({ success: false, errors });
}
user.name = req.body.name.trim();
user.email = req.body.email.trim();
user.age = req.body.age || null;
res.json({ success: true, data: user });
});
// PATCH /api/users/:id - 部分更新
app.patch('/api/users/:id', (req, res) => {
const user = findUserById(req.params.id);
if (!user) {
return res.status(404).json({
success: false,
error: '用户不存在'
});
}
// 只更新传入的字段
if (req.body.name !== undefined) user.name = req.body.name.trim();
if (req.body.email !== undefined) user.email = req.body.email.trim();
if (req.body.age !== undefined) user.age = req.body.age;
res.json({ success: true, data: user });
});
// DELETE /api/users/:id - 删除用户
app.delete('/api/users/:id', (req, res) => {
const index = users.findIndex(u => u.id === Number(req.params.id));
if (index === -1) {
return res.status(404).json({
success: false,
error: '用户不存在'
});
}
const deleted = users.splice(index, 1)[0];
res.json({ success: true, data: deleted });
});
// ========== 404 处理 ==========
app.use((req, res) => {
res.status(404).json({ error: `未找到路由: ${req.method} ${req.url}` });
});
// ========== 错误处理 ==========
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: '服务器内部错误' });
});
// ========== 启动服务器 ==========
app.listen(3000, () => {
console.log('API 服务器运行在 http://localhost:3000');
console.log('试试这些端点:');
console.log(' GET /api/users');
console.log(' GET /api/users/1');
console.log(' POST /api/users');
console.log(' PUT /api/users/1');
console.log(' DELETE /api/users/1');
});
10. 数据库连接
实际项目中,数据需要持久化存储。这里介绍两种常用数据库的连接方式。
10.1 MongoDB + Mongoose
MongoDB 是一个 NoSQL 文档数据库,适合存储 JSON 格式的数据。Mongoose 是 MongoDB 的 ODM(对象文档映射)库,提供了数据验证、类型转换等功能。
安装:
npm install mongoose
连接与使用:
const mongoose = require('mongoose');
// 连接数据库
async function connectDB() {
try {
await mongoose.connect('mongodb://localhost:27017/myapp');
console.log('MongoDB 连接成功');
} catch (err) {
console.error('MongoDB 连接失败:', err.message);
process.exit(1);
}
}
// 定义数据模型(Schema)
const userSchema = new mongoose.Schema({
name: {
type: String,
required: [true, '姓名是必填项'],
trim: true,
maxlength: [50, '姓名不能超过50个字符']
},
email: {
type: String,
required: [true, '邮箱是必填项'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '请提供有效的邮箱地址']
},
age: {
type: Number,
min: [0, '年龄不能为负数'],
max: [150, '年龄不能超过150']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
});
// 添加虚拟字段
userSchema.virtual('displayName').get(function () {
return `${this.name} (${this.email})`;
});
// 添加实例方法
userSchema.methods.toSafeJSON = function () {
const obj = this.toObject();
delete obj.__v;
return obj;
};
// 添加静态方法
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email: email.toLowerCase() });
};
// 创建模型
const User = mongoose.model('User', userSchema);
// ========== CRUD 操作示例 ==========
async function main() {
await connectDB();
// 创建用户
const user = await User.create({
name: '张三',
email: 'zhangsan@example.com',
age: 28
});
console.log('创建用户:', user.toSafeJSON());
// 查询用户
const found = await User.findByEmail('zhangsan@example.com');
console.log('查询用户:', found.displayName);
// 更新用户
const updated = await User.findByIdAndUpdate(
user._id,
{ age: 29 },
{ new: true, runValidators: true }
);
console.log('更新后:', updated.toSafeJSON());
// 查询多个
const allUsers = await User.find({ role: 'user' }).sort({ createdAt: -1 });
console.log('所有用户:', allUsers.length, '个');
// 删除用户
await User.findByIdAndDelete(user._id);
console.log('用户已删除');
// 断开连接
await mongoose.disconnect();
}
main().catch(console.error);
10.2 MySQL + mysql2
MySQL 是最流行的关系型数据库之一。mysql2 是一个高性能的 MySQL 客户端库。
安装:
npm install mysql2
连接与使用:
const mysql = require('mysql2/promise');
// 创建连接池
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'your_password',
database: 'myapp',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
// ========== 初始化表 ==========
async function initDB() {
const connection = await pool.getConnection();
try {
await connection.execute(`
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
`);
console.log('表初始化成功');
} finally {
connection.release();
}
}
// ========== CRUD 操作 ==========
// 创建
async function createUser(name, email, age) {
const [result] = await pool.execute(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
[name, email, age]
);
return { id: result.insertId, name, email, age };
}
// 查询所有
async function getAllUsers() {
const [rows] = await pool.execute('SELECT * FROM users ORDER BY created_at DESC');
return rows;
}
// 查询单个
async function getUserById(id) {
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);
return rows[0] || null;
}
// 更新
async function updateUser(id, data) {
const fields = [];
const values = [];
if (data.name !== undefined) { fields.push('name = ?'); values.push(data.name); }
if (data.email !== undefined) { fields.push('email = ?'); values.push(data.email); }
if (data.age !== undefined) { fields.push('age = ?'); values.push(data.age); }
if (fields.length === 0) return null;
values.push(id);
await pool.execute(`UPDATE users SET ${fields.join(', ')} WHERE id = ?`, values);
return getUserById(id);
}
// 删除
async function deleteUser(id) {
const user = await getUserById(id);
if (!user) return null;
await pool.execute('DELETE FROM users WHERE id = ?', [id]);
return user;
}
// ========== 主程序 ==========
async function main() {
await initDB();
const user = await createUser('张三', 'zhangsan@example.com', 28);
console.log('创建用户:', user);
const all = await getAllUsers();
console.log('所有用户:', all);
await pool.end();
}
main().catch(console.error);
10.3 SQLite(轻量级选择)
如果项目不需要独立的数据库服务器,SQLite 是一个很好的选择。它将数据存储在单个文件中。
npm install better-sqlite3
const Database = require('better-sqlite3');
const path = require('path');
// 创建/打开数据库(会在当前目录生成 data.db 文件)
const db = new Database(path.join(__dirname, 'data.db'));
// 启用 WAL 模式(提升并发性能)
db.pragma('journal_mode = WAL');
// 创建表
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL UNIQUE,
age INTEGER,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// 预编译 SQL 语句(提升性能)
const insertUser = db.prepare('INSERT INTO users (name, email, age) VALUES (?, ?, ?)');
const findUser = db.prepare('SELECT * FROM users WHERE id = ?');
const allUsers = db.prepare('SELECT * FROM users');
const removeUser = db.prepare('DELETE FROM users WHERE id = ?');
// 创建
const result = insertUser.run('张三', 'zhangsan@example.com', 28);
console.log('创建成功,ID:', result.lastInsertRowid);
// 查询
const user = findUser.get(1);
console.log('查询结果:', user);
// 查询所有
const users = allUsers.all();
console.log('所有用户:', users);
// 删除
removeUser.run(1);
// 关闭数据库
db.close();
11. 实战项目:搭建完整的 RESTful API
现在让我们把前面学到的知识整合起来,搭建一个完整的「待办事项 API」。
11.1 项目结构
todo-api/
├── package.json
├── src/
│ ├── index.js # 入口文件
│ ├── routes/
│ │ └── todos.js # 路由定义
│ ├── middleware/
│ │ ├── logger.js # 日志中间件
│ │ └── errorHandler.js # 错误处理
│ └── data/
│ └── todos.json # 数据存储(模拟数据库)
└── README.md
11.2 初始化项目
mkdir todo-api
cd todo-api
npm init -y
npm install express
npm install -D nodemon
编辑 package.json:
{
"name": "todo-api",
"version": "1.0.0",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
}
}
11.3 完整代码
src/index.js —— 入口文件:
const express = require('express');
const path = require('path');
const todoRoutes = require('./routes/todos');
const logger = require('./middleware/logger');
const errorHandler = require('./middleware/errorHandler');
const app = express();
const PORT = process.env.PORT || 3000;
// ========== 中间件 ==========
app.use(express.json());
app.use(logger);
// ========== 路由 ==========
app.get('/', (req, res) => {
res.json({
message: '待办事项 API',
version: '1.0.0',
endpoints: {
'GET /api/todos': '获取所有待办',
'GET /api/todos/:id': '获取单个待办',
'POST /api/todos': '创建待办',
'PUT /api/todos/:id': '更新待办',
'PATCH /api/todos/:id/toggle': '切换完成状态',
'DELETE /api/todos/:id': '删除待办',
'GET /api/stats': '获取统计信息'
}
});
});
app.use('/api/todos', todoRoutes);
// ========== 错误处理 ==========
app.use((req, res) => {
res.status(404).json({ error: `未找到路由: ${req.method} ${req.url}` });
});
app.use(errorHandler);
// ========== 启动 ==========
app.listen(PORT, () => {
console.log(`✅ 待办事项 API 运行在 http://localhost:${PORT}`);
console.log(`📝 环境: ${process.env.NODE_ENV || 'development'}`);
});
src/middleware/logger.js —— 日志中间件:
function logger(req, res, next) {
const start = Date.now();
const { method, url } = req;
res.on('finish', () => {
const duration = Date.now() - start;
const status = res.statusCode;
const statusEmoji = status >= 400 ? '❌' : '✅';
console.log(`${statusEmoji} ${method} ${url} ${status} - ${duration}ms`);
});
next();
}
module.exports = logger;
src/middleware/errorHandler.js —— 错误处理中间件:
function errorHandler(err, req, res, next) {
console.error('🔥 错误:', err.message);
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || '服务器内部错误',
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
});
}
module.exports = errorHandler;
src/routes/todos.js —— 待办事项路由:
const express = require('express');
const router = express.Router();
const fs = require('fs').promises;
const path = require('path');
const DATA_FILE = path.join(__dirname, '..', 'data', 'todos.json');
// ========== 数据操作工具 ==========
async function readTodos() {
try {
const data = await fs.readFile(DATA_FILE, 'utf-8');
return JSON.parse(data);
} catch {
return [];
}
}
async function writeTodos(todos) {
await fs.mkdir(path.dirname(DATA_FILE), { recursive: true });
await fs.writeFile(DATA_FILE, JSON.stringify(todos, null, 2), 'utf-8');
}
function validateTodo(data) {
const errors = [];
if (!data.title || data.title.trim() === '') {
errors.push('标题不能为空');
}
if (data.title && data.title.length > 200) {
errors.push('标题不能超过200个字符');
}
if (data.priority && !['low', 'medium', 'high'].includes(data.priority)) {
errors.push('优先级必须是 low、medium 或 high');
}
return errors;
}
// ========== 路由 ==========
// GET /api/todos - 获取待办列表(支持筛选和分页)
router.get('/', async (req, res) => {
const { completed, priority, page = 1, limit = 20 } = req.query;
let todos = await readTodos();
// 筛选
if (completed !== undefined) {
todos = todos.filter(t => t.completed === (completed === 'true'));
}
if (priority) {
todos = todos.filter(t => t.priority === priority);
}
// 按创建时间倒序
todos.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
// 分页
const total = todos.length;
const start = (Number(page) - 1) * Number(limit);
const paginated = todos.slice(start, start + Number(limit));
res.json({
success: true,
data: paginated,
pagination: {
page: Number(page),
limit: Number(limit),
total,
totalPages: Math.ceil(total / Number(limit))
}
});
});
// GET /api/stats - 获取统计信息
router.get('/stats', async (req, res) => {
const todos = await readTodos();
const total = todos.length;
const completed = todos.filter(t => t.completed).length;
const pending = total - completed;
const byPriority = {
high: todos.filter(t => t.priority === 'high').length,
medium: todos.filter(t => t.priority === 'medium').length,
low: todos.filter(t => t.priority === 'low').length
};
res.json({
success: true,
data: { total, completed, pending, byPriority }
});
});
// GET /api/todos/:id - 获取单个待办
router.get('/:id', async (req, res) => {
const todos = await readTodos();
const todo = todos.find(t => t.id === req.params.id);
if (!todo) {
return res.status(404).json({ success: false, error: '待办事项不存在' });
}
res.json({ success: true, data: todo });
});
// POST /api/todos - 创建待办
router.post('/', async (req, res) => {
const errors = validateTodo(req.body);
if (errors.length > 0) {
return res.status(400).json({ success: false, errors });
}
const todos = await readTodos();
const newTodo = {
id: Date.now().toString(36) + Math.random().toString(36).slice(2, 7),
title: req.body.title.trim(),
description: req.body.description?.trim() || '',
completed: false,
priority: req.body.priority || 'medium',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
todos.push(newTodo);
await writeTodos(todos);
res.status(201).json({ success: true, data: newTodo });
});
// PUT /api/todos/:id - 更新待办
router.put('/:id', async (req, res) => {
const errors = validateTodo(req.body);
if (errors.length > 0) {
return res.status(400).json({ success: false, errors });
}
const todos = await readTodos();
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
return res.status(404).json({ success: false, error: '待办事项不存在' });
}
todos[index] = {
...todos[index],
title: req.body.title.trim(),
description: req.body.description?.trim() || todos[index].description,
priority: req.body.priority || todos[index].priority,
updatedAt: new Date().toISOString()
};
await writeTodos(todos);
res.json({ success: true, data: todos[index] });
});
// PATCH /api/todos/:id/toggle - 切换完成状态
router.patch('/:id/toggle', async (req, res) => {
const todos = await readTodos();
const todo = todos.find(t => t.id === req.params.id);
if (!todo) {
return res.status(404).json({ success: false, error: '待办事项不存在' });
}
todo.completed = !todo.completed;
todo.updatedAt = new Date().toISOString();
await writeTodos(todos);
res.json({ success: true, data: todo });
});
// DELETE /api/todos/:id - 删除待办
router.delete('/:id', async (req, res) => {
const todos = await readTodos();
const index = todos.findIndex(t => t.id === req.params.id);
if (index === -1) {
return res.status(404).json({ success: false, error: '待办事项不存在' });
}
const deleted = todos.splice(index, 1)[0];
await writeTodos(todos);
res.json({ success: true, data: deleted });
});
module.exports = router;
11.4 测试 API
启动服务器:
npm run dev
使用 curl 测试各个接口:
# 创建待办
curl -X POST http://localhost:3000/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "学习 Node.js", "priority": "high"}'
curl -X POST http://localhost:3000/api/todos \
-H "Content-Type: application/json" \
-d '{"title": "写单元测试", "priority": "medium"}'
# 获取所有待办
curl http://localhost:3000/api/todos
# 获取单个待办(替换为实际 ID)
curl http://localhost:3000/api/todos/<id>
# 切换完成状态
curl -X PATCH http://localhost:3000/api/todos/<id>/toggle
# 筛选未完成的待办
curl "http://localhost:3000/api/todos?completed=false"
# 获取统计信息
curl http://localhost:3000/api/stats
# 删除待办
curl -X DELETE http://localhost:3000/api/todos/<id>
12. 常见问题与调试技巧
12.1 常见错误
端口被占用:
# 查看占用端口的进程
lsof -i :3000
# 或
netstat -tlnp | grep 3000
# 杀掉进程
kill -9 <PID>
模块找不到:
# 重新安装依赖
rm -rf node_modules package-lock.json
npm install
异步错误未捕获:
// ❌ 错误写法:异步错误不会被 Express 捕获
app.get('/api/data', (req, res) => {
const data = await someAsyncFunction(); // 报错!
res.json(data);
});
// ✅ 正确写法:使用 try-catch
app.get('/api/data', async (req, res) => {
try {
const data = await someAsyncFunction();
res.json(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// ✅ 或使用 express-async-errors 包(自动捕获)
// npm install express-async-errors
require('express-async-errors');
12.2 调试技巧
使用 console 调试:
// 基本输出
console.log('变量值:', someVar);
// 格式化输出
console.log(JSON.stringify(complexObject, null, 2));
// 带标签的输出
console.log('[DB]', '连接成功');
console.log('[API]', '请求处理完成');
// 计时
console.time('数据库查询');
// ... 某些操作
console.timeEnd('数据库查询'); // 输出:数据库查询: 15.326ms
// 错误输出(带堆栈信息)
console.error('错误详情:', error);
使用 Node.js 内置调试器:
# 以调试模式启动
node --inspect src/index.js
# 然后在 Chrome 浏览器中打开 chrome://inspect
# 可以设置断点、查看变量、单步执行
使用环境变量区分环境:
// 根据环境切换行为
const isDev = process.env.NODE_ENV !== 'production';
if (isDev) {
console.log('调试信息...');
}
// 启动时指定环境
// NODE_ENV=production node src/index.js
13. 下一步学习建议
恭喜你完成了 Node.js 后端开发的入门学习!以下是一些进阶方向:
13.1 进阶主题
- 认证与授权:JWT(JSON Web Token)、OAuth 2.0、Session
- 数据验证:使用 Joi 或 Zod 进行请求数据校验
- API 文档:使用 Swagger/OpenAPI 自动生成接口文档
- 测试:Jest(单元测试)、Supertest(接口测试)
- 日志:Winston 或 Pino(结构化日志)
- 安全:CORS、Rate Limiting、Helmet、输入过滤
- 部署:Docker 容器化、PM2 进程管理、Nginx 反向代理
13.2 推荐学习路径
入门(本教程)
↓
Express 深入 + MongoDB/MySQL
↓
认证系统(JWT)
↓
测试驱动开发
↓
TypeScript + Node.js
↓
微服务架构
↓
容器化部署(Docker + K8s)
13.3 有用的资源
- Node.js 官方文档:https://nodejs.org/docs
- Express 官方文档:https://expressjs.com
- MDN Web Docs:https://developer.mozilla.org/zh-CN/
- npm 官网:https://www.npmjs.com
总结
本教程从零开始,带你走过了 Node.js 后端开发的完整学习路径:
- 基础概念:了解了 Node.js 是什么、为什么用它
- 环境搭建:安装 Node.js,了解 REPL 和项目初始化
- 模块系统:掌握了 CommonJS 和 ES Module 两种模块化方式
- 文件操作:学会了文件和目录的读写操作
- HTTP 服务:用原生模块创建了 Web 服务器
- Express 框架:掌握了路由、中间件、错误处理
- REST API:学会了设计和实现规范的 RESTful API
- 数据库:了解了 MongoDB、MySQL、SQLite 三种数据库的接入方式
- 实战项目:完成了一个功能完整的待办事项 API
最重要的是动手实践。看完教程后,试着自己从零搭建一个 API 服务,加入自己的想法和功能。遇到问题时,善用搜索引擎和官方文档,这是每个开发者的日常。
祝你学习愉快,编码顺利!🚀