RESTful + Interface:从 todos 项目理解后端接口设计

基于 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#L48new 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. 核心心法

  1. interface 是 TS 的灵魂------编译期类型检查,重构利器
  2. RESTful 不是协议是风格------统一 URL 设计,让 API 有语义
  3. HTTP 动词表达操作------同一个 URL 配不同动词做不同事
  4. 路由 = URL + Method 匹配------Bun.serve 的 fetch 函数里 if 判断
  5. CORS 跨域三件套------Origin + Methods + Headers,还要处理 OPTIONS 预检
  6. 前后端分离------后端提供 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 应用就成型了。

相关推荐
用户69371750013841 小时前
#DeepSeek+Pi‑Agent 王炸组合跑赢 Claude‑Code!
前端·人工智能·后端
前端一课1 小时前
我还在上班,怎么做一个长期有价值的号
前端
前端一课2 小时前
用 TRAE Work 把项目踩坑经验沉淀成「团队可复用工程规范」,新人再也不重复掉坑
前端·后端
cyadyx2 小时前
【Three.js】Three.js 中加载 3D 模型
前端·javascript·3d
汉堡大王95272 小时前
前端工程师 TypeScript 上手指南:一条清晰的从入门到精通路线
前端·javascript·react.js
浅水壁虎2 小时前
vue基础(第四章 Pinia)
前端·javascript·vue.js
用户2181697049302 小时前
Flutter (十四) SingleChildScrollView ListView
前端
昭阳2 小时前
4 个 vibe coding 项目,一个普通前端的半年
前端·人工智能·设计
颜进强2 小时前
Claude Code - 26 效率三件套:cc-switch 切模型 · codeburn 算成本 · claude-hud 看状态
前端·后端·ai编程