后端是什么——从 Express API 讲起
前 15 篇我们一直在浏览器里打转 —— HTML 结构、CSS 布局、JavaScript 交互、Astro 组件化。但网页的数据从哪来?谁在「背后」支撑页面内容?
答案是后端。本文用白物集的 Express API 拆给你看。
后端做了什么
前端的本质是「展示」—— 把 HTML + CSS + JS 扔给浏览器渲染。后端是做三件事:接收请求、处理逻辑、返回数据。
你打开 baiwuji.top,浏览器发了一个 HTTP 请求给服务器。服务器上跑的程序不是 Astro 前端,而是另一层 —— API 层,也就是后端。
白物集的 API 架构图长这样(简化版):
浏览器 → 域名 → Nginx → Express API → Supabase 数据库
↑
内容管线自动写稿 → POST
Nginx 负责转发:/api/* 走 Express,/_astro/* 走静态资源,/* 走 Astro SSR。这个拆分意味着前端(Astro)和后端(Express)是两个独立进程,各管各的事。
Express 是什么
Express.js 是 Node.js 最流行的 Web 框架。它做的事极其简单:定义一个路由(route),匹配 HTTP 方法和路径,执行一个函数,返回 JSON 或 HTML。
白物集的主入口只有几十行代码:
import express from 'express';
import cors from 'cors';
const app = express();
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// 路由注册
const v1 = express.Router();
v1.use('/articles', articlesRouter);
v1.use('/content/ingest', ingestRouter);
v1.use('/health', healthRouter);
// ...更多路由
app.use('/api/v1', v1);
app.listen(PORT, '127.0.0.1');
这段代码在服务器启动后,监听了本机的一个端口,等待 HTTP 请求。当收到 GET /api/v1/health 时,交给 health 路由处理 —— 无非是返回一个 JSON:
router.get('/', (req, res) => {
res.json({
status: 'ok',
service: 'baiwuji-api',
version: '2.0.0',
time: new Date().toISOString(),
});
});
这就是后端最原始的形态:HTTP in,JSON out。
「接口」到底是什么
后端开发者常说的「接口」不是 UI 界面,是 API 端点(endpoint)。每个端点就是一个 URL + 方法组合。
白物集有 19 个路由,这里列几个你已经在用的:
| 端点 | 方法 | 功能 |
|---|---|---|
/api/v1/health |
GET | 健康检查 |
/api/v1/articles |
GET | 文章列表 |
/api/v1/articles/:id |
GET | 单篇文章详情 |
/api/v1/content/ingest |
POST | 管线写入新文章 |
前端的 Astro 页面怎么拿到文章列表的?白物集在 SSR 侧写了一个 API 客户端:
const BASE = 'http://127.0.0.1:3001';
async function get(path) {
const res = await fetch(BASE + '/api/v1' + path);
if (!res.ok) throw new Error(`API ${res.status}`);
return res.json();
}
export async function getArticles(opts = {}) {
const p = new URLSearchParams({ limit: '50', offset: '0' });
if (opts.category) p.set('category', opts.category);
return cached('/articles?' + p.toString(), TTL.list, () => get(p.toString()));
}
当用户打开首页,Astro SSR 调用 getArticles() → 请求 http://localhost:3001/api/v1/articles?limit=50 → Express 收到请求 → 从 Supabase 查数据 → 返回 JSON → Astro 用 JSON 渲染页面。整个链路里,Express 就是「中间人」—— 它不和浏览器直接对话(那是 Nginx 的工作),但所有数据都经过它中转。
一个真实的数据查询
白物集的文章列表接口,实际代码做了三件事:校验参数 → 缓存检查 → 查数据库。
router.get('/', async (req, res) => {
const parsed = articleQuerySchema.safeParse(req.query);
if (!parsed.success) return zodError(res, parsed.error);
const { category, status, limit, offset, sort } = parsed.data;
const data = await getOrSet('articles', cacheKey, async () => {
let query = supabase
.from('articles')
.select('id, title, slug, abstract, category, tags, published_at', { count: 'exact' })
.eq('status', status)
.order(sort, { ascending: false })
.range(offset, offset + limit - 1);
if (category) query = query.eq('category', category);
const { data, error } = await query;
if (error) throw error;
return { articles: data, total: count };
});
res.json(data);
});
注意到 getOrSet 了吗?这是一个内存缓存层 —— 同样的请求 300 秒内不再查数据库。后端不是简单的「查了就返回」,性能优化从第一天就要考虑。
为什么前端不能直接连数据库
这是开发者常有的疑问:「既然 Express 也只是查 Supabase,为什么不让浏览器直接连 Supabase?」答案是:安全和控制。
- 密钥管理 —— 数据库连接密钥不能暴露给浏览器。Express 在服务器上持有
SUPABASE_URL和SUPABASE_ANON_KEY,浏览器看不到。 - 数据过滤 —— API 可以选择返回哪些字段(例如列表接口不返回
content_html节省带宽)。 - 鉴权 —— 写操作必须有 API Key 才能执行。白物集的写入路由统一检查
x-api-key头部:
function requireIngestAuth(req, res, next) {
const token = req.headers['x-api-key'];
if (!token || token !== INGEST_KEY) {
return unauthorized(res, 'Invalid or missing API key');
}
next();
}
没有密钥的请求会被直接挡在门外。
「后端 ≠ 数据库」
初学者容易把「后端」和「数据库」画等号。后端是处理逻辑的程序,数据库只是它的存储后端之一。白物集的 Express API 除了查数据库,还做了:
- 速率限制 —— 普通接口每分钟 100 次,写入接口每分钟 20 次
- 请求日志 —— 每个请求的耗时、状态码、来源都记录到结构化日志
- 缓存管理 —— 数据更新后自动清除相关缓存
- OpenAPI 规范生成 —— 自动生成可交互的 API 文档
- 404 处理 —— 未匹配的路由返回友好错误
这些和数据库没关系,但都是后端的本职工作。
这篇文章教了你什么
- 后端 = 接收 HTTP 请求 + 处理逻辑 + 返回 JSON
- Express.js 是组织路由的轻量框架
- API 端点(endpoint)是 URL + 方法 + 函数的三元组
- 前端不直连数据库,通过 API 层中转
- 后端的职责远超「查数据库」
下一篇讲 API 设计入门:RESTful 是什么、怎么用 —— 你会在白物集的路由命名里看到 REST 的影子,我们拆开讲清它的设计原则。