基于 Bun + TypeScript 的 todos 任务清单项目,讲透两个核心概念:接口(interface) 和 RESTful API 设计。
一、接口 interface 是什么
1. OOP 三大特性
面向对象编程(OOP)有三大核心特性:
| 特性 | 含义 | 作用 |
|---|---|---|
| 封装 | 把数据和操作数据的方法绑在一起,隐藏内部细节 | 降低复杂度,安全 |
| 继承 | 子类继承父类的属性和方法 | 代码复用 |
| 多态 | 同一个方法在不同对象上有不同行为 | 灵活扩展 |
2. 接口的本质
接口(interface)= 对象的"约束契约"。
接口只声明"有哪些属性、哪些方法",不实现具体逻辑。谁来用这个接口,就必须遵守这个约束------这叫"满足接口"。
ts
// 接口:声明约束(只定义形状,不写实现)
interface Todo {
id: string;
title: string;
completed: boolean;
createdAt: Date;
}
// 满足接口:必须实现所有声明的属性
const todo: Todo = {
id: "1",
title: "吃饭",
completed: false,
createdAt: new Date()
};
// ✅ 少一个字段、类型不对,TS 编译期就报错
3. 抽象类与接口
- 抽象类:可以有声明(抽象方法),也可以有实现(具体方法)
- 接口:只有声明,没有实现
ts
// 接口:纯声明
interface Animal {
name: string;
speak(): void; // 只声明,不实现
}
// 抽象类:声明 + 部分实现
abstract class AnimalBase {
name: string;
constructor(name: string) { this.name = name; }
abstract speak(): void; // 抽象方法,子类必须实现
eat() { console.log('吃饭'); } // 具体方法,子类直接用
}
4. 面向接口编程
面向接口编程 是设计模式的基础。核心思想:依赖接口,不依赖具体实现。
ts
// ❌ 依赖具体实现:换数据库要改代码
class TodoService {
save(todo: Todo) {
mysql.insert(todo); // 写死了 MySQL
}
}
// ✅ 依赖接口:换数据库只需换实现
interface Repository<T> {
save(item: T): void;
}
class TodoService {
constructor(private repo: Repository<Todo>) {} // 依赖接口
save(todo: Todo) {
this.repo.save(todo); // 不管是 MySQL 还是 MongoDB 都行
}
}
二、TypeScript 中的 interface
结合 server.ts#L5-L10 的实际代码:
ts
interface Todo {
id: string; // 任务 ID
title: string; // 任务标题
completed: boolean; // 是否完成
createdAt: Date; // 创建时间
}
1. interface 的作用
约束对象的形状------告诉 TS 编译器"Todo 类型的对象必须有这 4 个字段,类型分别是 xxx"。
ts
const todos: Todo[] = [
{
id: "1",
title: "吃饭",
completed: false,
createdAt: new Date()
}
// ✅ 符合 interface 约束
];
const wrong: Todo = {
id: 1, // ❌ 报错:number 不能赋给 string
title: "吃饭"
// ❌ 报错:缺少 completed、createdAt
};
2. 类型检查的好处
| 时机 | JS(无类型) | TS(有 interface) |
|---|---|---|
| 写代码 | 运行时才发现错误 | 编译期就报错 |
| 重构 | 怕改漏字段 | 改 interface 自动提示所有不符处 |
| 协作 | 看 docs 才知道结构 | 看 interface 一目了然 |
| 自动提示 | 无 | IDE 智能补全字段 |
3. interface vs type
ts
// interface:只能描述对象形状
interface Todo { id: string; }
// type:可以描述任何类型(联合、交叉、原始类型)
type ID = string | number;
type Callback = (data: Todo) => void;
口诀:描述对象用 interface,描述其他类型用 type。
三、RESTful 是什么
1. 核心理念:一切皆资源
RESTful 是一种 URL 设计风格,核心思想:
把后端的所有东西都看作"资源",URL 是资源的地址,HTTP 动词是对资源的操作。
| 概念 | 说明 | 例子 |
|---|---|---|
| 资源 | 后端管理的"东西" | 任务、用户、文章 |
| URL | 资源的地址 | /todos、/users |
| HTTP 动词 | 对资源的操作 | GET(查)、POST(增)、PUT(改)、DELETE(删) |
2. URL 规则:资源名词 + HTTP 动词
RESTful 的 URL 只用名词(复数),操作用 HTTP 动词表达:
js
// ✅ RESTful 风格
GET /todos // 获取所有任务
GET /todos/1 // 获取 id=1 的任务
POST /todos // 新增任务
PUT /todos/1 // 修改 id=1 的任务
DELETE /todos/1 // 删除 id=1 的任务
// ❌ 传统风格(动词写在 URL 里,不规范)
GET /getTodos
POST /createTodo
GET /deleteTodoById?id=1
3. 传统 vs RESTful 对比
| 操作 | 传统风格 | RESTful 风格 |
|---|---|---|
| 获取列表 | GET /getTodoList |
GET /todos |
| 获取详情 | GET /getTodo?id=1 |
GET /todos/1 |
| 新增 | POST /addTodo |
POST /todos |
| 修改 | POST /updateTodo |
PUT /todos/1 |
| 删除 | GET /deleteTodo?id=1 |
DELETE /todos/1 |
RESTful 的优势:
- URL 简洁有语义(一看就知道是什么资源)
- 同一个 URL 配合不同动词表达不同操作
- 统一风格,前端调用方便
四、HTTP 七大动词
| 动词 | 作用 | 幂等性 | 安全性 | todos 项目用 |
|---|---|---|---|---|
| GET | 查询资源 | ✅ 幂等 | ✅ 安全 | ✅ 用 |
| POST | 新增资源 | ❌ 不幂等 | ❌ 不安全 | 待实现 |
| PUT | 全量更新资源 | ✅ 幂等 | ❌ 不安全 | 待实现 |
| PATCH | 部分更新资源 | ❌ 不幂等 | ❌ 不安全 | --- |
| DELETE | 删除资源 | ✅ 幂等 | ❌ 不安全 | 待实现 |
| HEAD | 只取响应头 | ✅ 幂等 | ✅ 安全 | --- |
| OPTIONS | 预检请求(CORS) | ✅ 幂等 | ✅ 安全 | ✅ 用 |
幂等性是什么
幂等 = 同一个请求执行一次和执行多次,结果一样。
GET /todos/1:查 100 次,结果还是那条数据 → 幂等DELETE /todos/1:删一次和删十次,结果都是"1 不存在了" → 幂等POST /todos:发 10 次就新增了 10 条 → 不幂等
五、路由(警察)
1. 路由的职责
readme 里说"路由(警察)"------路由就是交警,根据请求的 URL 和方法,把请求"指挥"到对应的处理逻辑。
sql
请求进来 → 路由判断 URL + Method → 分发到对应处理函数
2. URL 结构解析
一个完整的 URL:
bash
https://baidu.com:8080/todos/2?a=1&b=2
└─┬─┘ └──┬───┘ └┬┘ └──┬──┘ └──┬──┘
协议 域名 端口 路径 查询参数
在 server.ts#L48 用 new URL(req.url) 解析后:
js
const url = new URL(req.url);
url.protocol; // "http:"
url.hostname; // "localhost"
url.port; // "8080"
url.pathname; // "/todos/2" ← 路由主要看这个
url.searchParams; // URLSearchParams 对象
3. 代码里的路由匹配
server.ts#L49-L61 的路由:
js
// 路由 1:GET /todos → 获取所有任务
if (req.method === 'GET' && url.pathname === "/todos") {
return Response.json(todos, { headers });
}
// 路由 2:GET /todos/:id → 获取单个任务详情
if (req.method === 'GET' && url.pathname.startsWith("/todos/")) {
const id = url.pathname.split("/")[2]; // 提取 id
const todo = todos.find((t) => t.id === id); // 查找
return Response.json(todo);
}
// 路由 3:默认响应
return Response.json({ msg: 'hello world' });
两种匹配方式:
===精确匹配:pathname === "/todos"(列表)startsWith前缀匹配:pathname.startsWith("/todos/")(详情,后面带 id)
六、完整实战:todos 项目
1. 项目结构
sql
todos/
├── server.ts ← 后端(Bun + TS)
├── index.html ← 前端(fetch 消费接口)
└── readme.md ← 笔记
2. 后端 server.ts 完整流程
ts
// 1. 定义接口(约束资源形状)
interface Todo {
id: string;
title: string;
completed: boolean;
createdAt: Date;
}
// 2. 资源数据(满足 Todo 接口)
const todos: Todo[] = [
{ id: "1", title: "吃饭", completed: false, createdAt: new Date() },
{ id: "2", title: "睡觉", completed: false, createdAt: new Date() },
{ id: "3", title: "想对象", completed: false, createdAt: new Date() }
];
// 3. 启动 Bun 服务器
const server = Bun.serve({
port: 8080,
async fetch(req) {
const url = new URL(req.url);
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};
// 预检请求
if (req.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: corsHeaders });
}
// 路由 1:GET /todos
if (req.method === 'GET' && url.pathname === "/todos") {
return Response.json(todos, { headers: corsHeaders });
}
// 路由 2:GET /todos/:id
if (req.method === 'GET' && url.pathname.startsWith("/todos/")) {
const id = url.pathname.split("/")[2];
const todo = todos.find((t) => t.id === id);
if (todo) return Response.json(todo, { headers: corsHeaders });
return Response.json({ msg: "没找到" }, { status: 404, headers: corsHeaders });
}
// 默认
return Response.json({ msg: 'hello world' }, { headers: corsHeaders });
}
});
3. 前端 index.html 消费接口
js
const todosEl = document.getElementById('todos');
fetch("http://localhost:8080/todos")
.then(res => res.json())
.then(data => {
todosEl.innerHTML = data.map(todo => `<li>${todo.title}</li>`).join('');
});
4. 完整数据流
css
浏览器 index.html
↓ fetch GET http://localhost:8080/todos
↓
Bun.serve 收到请求
↓ fetch 函数处理
↓ new URL(req.url) 解析
↓ 路由匹配:pathname === "/todos"
↓ 返回 Response.json(todos)
↓
浏览器收到 JSON
↓ .then(res => res.json()) 解析
↓ .then(data => ...) 渲染
↓
页面显示:吃饭 / 睡觉 / 想对象
七、RESTful API 设计速查表
1. 五个标准接口
| 操作 | Method | URL | 返回 |
|---|---|---|---|
| 获取列表 | GET | /todos |
[Todo, ...] |
| 获取详情 | GET | /todos/:id |
Todo |
| 新增 | POST | /todos |
新建的 Todo |
| 全量更新 | PUT | /todos/:id |
更新后的 Todo |
| 删除 | DELETE | /todos/:id |
204 No Content |
2. 状态码规范
| 状态码 | 含义 | 什么时候用 |
|---|---|---|
| 200 | OK | 查询/修改成功 |
| 201 | Created | 新增成功 |
| 204 | No Content | 删除成功(无返回体) |
| 400 | Bad Request | 参数错误 |
| 404 | Not Found | 资源不存在 |
| 500 | Internal Server Error | 服务器错误 |
3. 命名规则
- URL 用名词复数 :
/todos(不是/todo或/getTodos) - 路径参数用 id:
/todos/1 - 查询参数用驼峰:
/todos?completed=false&pageSize=10 - 资源嵌套:
/users/1/todos(用户 1 的所有任务)
八、总结
1. 接口口诀
interface = 对象的约束契约,只声明不实现,谁用谁遵守。
面向接口编程 = 依赖契约不依赖实现,换实现不换调用方。
2. RESTful 口诀
一切皆资源,URL 用名词,操作用动词。
GET 查、POST 增、PUT 改、DELETE 删。
3. 路由口诀
路由是警察,看 URL + Method 分发请求。
精确匹配用
===,前缀匹配用startsWith。
4. 核心心法
- interface 是 TS 的灵魂------编译期类型检查,重构利器
- RESTful 不是协议是风格------统一 URL 设计,让 API 有语义
- HTTP 动词表达操作------同一个 URL 配不同动词做不同事
- 路由 = URL + Method 匹配------Bun.serve 的 fetch 函数里 if 判断
- CORS 跨域三件套------Origin + Methods + Headers,还要处理 OPTIONS 预检
- 前后端分离------后端提供 JSON 接口,前端 fetch 消费,各管各的
九、下一步可以扩展
把 todos 项目补全成完整的 CRUD:
ts
// POST /todos - 新增
if (req.method === 'POST' && url.pathname === "/todos") {
const body = await req.json();
const newTodo: Todo = {
id: String(todos.length + 1),
...body,
completed: false,
createdAt: new Date()
};
todos.push(newTodo);
return Response.json(newTodo, { status: 201, headers: corsHeaders });
}
// DELETE /todos/:id - 删除
if (req.method === 'DELETE' && url.pathname.startsWith("/todos/")) {
const id = url.pathname.split("/")[2];
const index = todos.findIndex(t => t.id === id);
if (index !== -1) {
todos.splice(index, 1);
return new Response(null, { status: 204, headers: corsHeaders });
}
return Response.json({ msg: "没找到" }, { status: 404, headers: corsHeaders });
}
这样就完成了 RESTful 的五个标准接口,前后端分离的完整 todos 应用就成型了。