RESTful Todo 任务清单 —— 从零学习前后端

🌟 RESTful Todo 任务清单 ------ 从零学习前后端

🎯 一篇带你从零掌握前后端交互的实战教程 · 适合初学者


📋 项目概述

这是一个极简的任务清单(Todo List) 项目,使用 Bun 作为后端服务器,纯 HTML + JavaScript 作为前端。

通过这个项目,可以学习到:

  • TypeScript 类型系统(接口、类型约束)
  • 面向对象编程思想(OOP)
  • RESTful API 设计风格
  • 前后端交互(Fetch API + HTTP 协议)
  • Bun 运行时 的基本使用

📁 一、项目结构

bash 复制代码
todo/
├── server.ts        # 后端:提供 RESTful API
├── index.html       # 前端:展示任务列表
└── readme.md        # 项目说明(就是这个文件)

💡 零依赖! 不需要安装任何第三方包,只需要 Bun 一个运行时即可运行。


🧠 二、核心编程思想

1. 面向接口编程(Interface)🎯

面向对象编程(OOP)是基础,面向接口编程 是设计模式的基石。

什么是接口?

接口就像是"合同"或"模板"。它规定了一个对象必须要有哪些属性和方法,但不关心具体怎么实现。

typescript 复制代码
interface Todo {
  id: string;
  title: string;
  completed: boolean;
  createdAt: Date;
}

这段代码的意思是:任何一个 Todo 任务,都必须有 id、title、completed、createdAt 这四个属性,缺一不可。

为什么要用接口?

优势 说明
🔒 规范 保证数据格式统一,不出错
📖 可读性 看一眼接口就知道数据长什么样
🤝 协作 前后端开发人员按同一份"合同"开发

类型体操小知识:

typescript 复制代码
const todos: Todo[] = [ ... ];  // Todo[] 表示"这是一个数组,里面的每一项都必须是 Todo 类型"
todos.push({ id: "4", title: "学习" }); // ❌ 报错!缺少 completed 和 createdAt 属性

如果少写了属性或类型不对,TypeScript 在写代码时就会提示错误 ,不用等到运行才发现 ------ 这就是强类型的好处。


2. RESTful 风格 🔄

REST = Representational State Transfer(表现层状态转移)

核心思想:一切皆资源

  • 🔗 URL 只使用名词 表示资源:/todos
  • 🎬 用 HTTP 动词 表示操作:
HTTP 方法 操作 示例 URL 含义
🟢 GET 查询列表 /todos 获取所有任务
🟢 GET 查询详情 /todos/1 获取 id=1 的任务
🟡 POST 新增 /todos 创建新任务
🔵 PUT 全量修改 /todos/1 替换整个任务
🟣 PATCH 部分修改 /todos/1 修改部分字段
🔴 DELETE 删除 /todos/1 删除任务

⚠️ 当前项目只实现了 GET 方法,其余方法可作为练习自行补充。

类比理解:

  • 🏪 资源 = 仓库里的货物
  • 📦 URL = 货架位置(用名词命名)
  • 🛠️ HTTP 方法 = 对货物做什么(查、增、改、删)
  • 🧑‍💼 路由 = 仓库管理员(根据请求类型把任务分发给对应处理函数)

3. 异步编程(Async / Await)⏳

javascript 复制代码
async function main() {
  const res = await fetch("http://localhost:8080/todos");
  const data = await res.json();
  // ...
}
  • async:声明这是一个"异步函数"
  • await:等待一个异步操作完成(比如网络请求)
  • 可以理解为:"我先去拿数据,你等着我,拿到之后再继续往下走"

对比回调方式(.then() 链),async/await 让异步代码看起来像同步代码,更易读。

💡 代码中保留了 Promise.then() 的注释版本,方便对比两种写法的区别。


4. CORS 跨域 ------ 浏览器的一道"安检" 🛡️

typescript 复制代码
const headers = {
  'Access-Control-Allow-Origin': '*',  // 允许所有来源访问
};

为什么会用到它?

  • 📄 你的前端文件 index.html 是在本地直接打开的(file:// 协议)
  • 🖥️ 后端跑在 http://localhost:8080http:// 协议)
  • 🚫 协议不同 → 跨域了!浏览器会拦截响应

简单理解:

浏览器就像一个小区的保安。前端是住户,后端是快递站。

住户(前端)想去快递站(后端)拿包裹,保安(浏览器)要检查 ------ 快递站是否允许这个住户取件?

Access-Control-Allow-Origin: '*' 就相当于快递站贴了告示:"所有人均可取件"。


📝 三、逐文件语法 & 知识点解析

📄 server.ts

typescript 复制代码
// 1. 接口定义 ------ 约束数据格式
interface Todo {
  id: string;
  title: string;
  completed: boolean;
  createdAt: Date;
}
语法 说明
interface Todo { } 定义一个接口,描述 Todo 对象的结构
id: string 属性名: 类型 ------ 强类型约束,id 必须是字符串
Date TypeScript 内置类型,表示日期时间。new Date() 创建"当前时刻"
typescript 复制代码
// 2. 类型注解 ------ 给变量"贴标签"
const todos: Todo[] = [ ... ];
语法 说明
: Todo[] 声明 todos 是一个数组,且数组里的每个元素都必须是 Todo 类型
typescript 复制代码
// 3. Bun.serve ------ 启动 HTTP 服务器
const server = Bun.serve({
  port: 8080,
  async fetch(req) { ... }
});
语法 说明
Bun.serve() Bun 内置方法,创建一个 HTTP 服务器(无需额外安装 Express)
port: 8080 监听端口号,浏览器通过 http://localhost:8080 访问
async fetch(req) 每当有请求进来,都会调用这个函数处理
req Request 对象 ------ 包含请求方法、URL、头信息等

🚪 什么是端口?

一台服务器只有一个 IP 地址(比如 127.0.0.1),但可以提供多个服务。

端口号就像"房间号":127.0.0.1:8080 表示"去 8080 号房间找 HTTP 服务"。

typescript 复制代码
// 4. URL 解析与路由判断
const url = new URL(req.url);

if (req.method === 'GET' && url.pathname === "/todos") {
  return Response.json(todos, { headers });
}
语法 说明
new URL(req.url) 将请求的 URL 字符串解析成 URL 对象,方便取 pathname 等
req.method 获取 HTTP 方法(GET / POST / PUT / DELETE)
url.pathname 获取 URL 中的路径部分(例如 /todos

路径解析拆解:

请求 http://localhost:8080/todos/1 时:

perl 复制代码
url.pathname → "/todos/1"
url.pathname.split("/") → ["", "todos", "1"]
                     索引:   0     1       2
url.pathname.split("/")[2] → "1"   ← 这就是我们要的 id

特别注意 startsWith 的坑:

typescript 复制代码
url.pathname.startsWith("/todos/")  // ✅ 正确!匹配 /todos/1、/todos/abc 等
url.pathname.startsWith("/todos")   // ❌ 有问题!/todosABC 也会匹配上

路由流程图:

bash 复制代码
浏览器请求 → Bun.serve → fetch(req) 处理
                           ├── GET /todos      → 返回所有任务
                           ├── GET /todos/:id   → 返回指定任务
                           └── 其他             → 返回 { msg: 'hello world' }
typescript 复制代码
// 5. 数组查找 ------ find() 方法
const todo = todos.find((t) => t.id === id);
语法 说明
todos.find(条件函数) 遍历数组,找到第一个满足条件的元素,返回该元素
(t) => t.id === id 箭头函数 ------ 对每个元素 t,判断 t.id 是否等于变量 id
find() 找不到怎么办? 返回 undefined,不会报错,但前端接收到的是 null

📄 index.html

html 复制代码
<ul id="todos"></ul>
<script>
  async function main() {
    const res = await fetch("http://localhost:8080/todos");
    const data = await res.json();
    todos.innerHTML = data.map(
      todo => `<li>${todo.title}</li>`
    ).join('');
  }
  main();
</script>
语法 / 概念 说明
fetch(url) 浏览器内置 API,发送 HTTP 请求,返回 Promise 对象
res.json() 把响应体解析成 JSON 对象(也是一个异步操作)
data.map(todo => ...) 遍历任务数组,把每个任务转成 <li> HTML 字符串
.join('') 把数组拼接成一个字符串(避免逗号)
todos.innerHTML = ... 把生成的 HTML 放入 <ul> 中,页面就看到了

fetch 的两种写法对比:

javascript 复制代码
// 写法一:Promise 链式调用(传统)
fetch("/todos")
  .then(res => res.json())
  .then(data => { /* 处理数据 */ });

// 写法二:async/await(推荐,更清晰)
const res = await fetch("/todos");
const data = await res.json();

关键区别.then() 需要在回调函数里继续写逻辑;await 则可以让代码"停下来等结果",按顺序往下写即可。

为什么用 join('')

javascript 复制代码
["<li>吃饭</li>", "<li>睡觉</li>", "<li>打豆豆</li>"]
// .join('') 之后变成:
"<li>吃饭</li><li>睡觉</li><li>打豆豆</li>"  // ✅ 一个完整的字符串
// 如果不加 .join(''),数组 toString() 会自动加逗号:
"<li>吃饭</li>,<li>睡觉</li>,<li>打豆豆</li>"  // ❌ 页面会显示逗号!

🌐 四、HTTP 协议速通

请求(Request) → 响应(Response)

这是整个 Web 世界最基本的运作模式:

复制代码
你(浏览器)                   服务器
  │                              │
  ├── ① 输入 URL,发送请求 ─────→ │
  │                              ├── ② 处理请求(查数据库等)
  │                              │
  │  ←─── ③ 返回响应 ────────────┤
  │                              │
  ├── ④ 浏览器解析响应,渲染页面  │

常见的 HTTP 状态码

后端虽然没有显式设置状态码,但 Response.json() 默认返回 200(成功)。

状态码 含义 说明
200 ✅ OK 请求成功
201 🆕 Created 创建成功(POST 新增时使用)
204 🗑️ No Content 删除成功,没有返回体
400 ❌ Bad Request 客户端请求有误(比如参数不对)
404 🔍 Not Found 资源不存在
500 💥 Internal Server Error 服务器内部出错

🚀 五、Bun 运行时简介

Bun 是一个新兴的 JavaScript/TypeScript 运行时,可以理解为 Node.js 的"升级替代品"。

特性 Bun Node.js
⚡ 启动速度 极快(毫秒级) 较慢(需要加载大量内置模块)
🛠️ 内置工具 打包器、转译器、测试运行器、包管理器全内置 需要额外安装(webpack、tsc 等)
📘 TypeScript 支持 开箱即用,无需配置 tsconfig 需要 ts-node 或 tsc 编译
🌐 HTTP 服务器 Bun.serve() 零配置启动 需要 http 模块或 Express 框架

在这个项目中,Bun 让你 无需安装 Express、无需配置 tsconfig、无需 nodemon,一个文件 + 一行命令就跑起来了。


▶️ 六、运行方式

bash 复制代码
# 确保已安装 Bun(https://bun.sh)
# 在 todo 目录下执行:
bun run server.ts

看到终端输出类似 listening on http://localhost:8080/ 即表示启动成功。

然后浏览器访问:

  • 📄 任务列表页 :打开 index.html(直接用浏览器打开即可,无需额外服务器)
  • 🔌 API 测试http://localhost:8080/todos
  • 🔍 任务详情http://localhost:8080/todos/1

💡 查看详情时,注意浏览器地址栏下方的控制台(F12 → Console),会打印出找到的 todo 对象。


🗺️ 七、学习路线图

如果这个项目你已经理解了,下一步可以尝试:

阶段 学习目标 难度
1️⃣ 新增功能:实现 POST(新增任务)、DELETE(删除任务)、PATCH(修改完成状态) ⭐⭐
2️⃣ 数据持久化:把数据存到 JSON 文件或 SQLite 数据库,而不是内存数组(重启服务数据会丢失) ⭐⭐⭐
3️⃣ 前端增强:添加新增输入框、删除按钮、勾选完成复选框 ⭐⭐
4️⃣ 使用框架:前端用 React/Vue,后端用 Express/Koa 重构 ⭐⭐⭐⭐
5️⃣ 错误处理 :当 find() 找不到任务时,返回 404 状态码而不是空数据 ⭐⭐
6️⃣ UUID 生成 :新增任务时用 crypto.randomUUID() 替代手动指定 id

📊 八、总结

这个 Todo 项目虽小,但"五脏俱全":

知识点 应用场景
🎯 TypeScript 接口 约束数据结构,保证类型安全
🔄 RESTful API 设计 规范 URL 和 HTTP 方法
⚡ Bun.serve 零配置启动 HTTP 服务器
⏳ Async/Await 优雅处理异步网络请求
🌐 Fetch API 前后端数据交互
🎨 DOM 操作 把数据渲染到页面
🛡️ CORS 跨域 解决不同源的请求限制
📡 HTTP 状态码 表示请求的处理结果

编程思想总结:

  • 🧬 OOP(面向对象):用接口定义对象的"形状"
  • 📐 面向接口编程:不关心具体实现,只关心"必须有什么"
  • 🏗️ RESTful:用资源 + 动词来组织 API
  • 🔄 异步编程:用 async/await 让异步代码像同步一样清晰

🎉 学完这个项目,你就掌握了前后端交互的最小闭环!

麻雀虽小,但每一个知识点都是后续深入学习 Web 开发的基石。


💬 写在最后

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、转发🔄!

有任何问题或建议,欢迎在评论区留言交流~ 💬

Happy Coding! 🚀

相关推荐
Alan_754 小时前
SpringBoot API参数校验
api
GuWenyue6 小时前
10分钟搞定TodoList实战!从0搭建Bun+TS的RESTful接口服务
前端·typescript·bun
带娃的IT创业者11 小时前
深度解析 Bun:重新定义 JavaScript 运行时的性能边界
开发语言·javascript·node.js·ecmascript·bun·运行时
AIFQuant11 小时前
全球行情自动更新、多品种展示、性能优化实战指南
python·性能优化·金融·node.js·restful
山里幽默的程序员1 天前
DevOps 必备:盘点2026 年最强RESTful API 接口测试方案
运维·restful·devops·api开发·api开发工具
网安情报局2 天前
告别排队与高延迟:直连GPT全系列,解锁低门槛、高稳定的AI生产力
人工智能·gpt·api·ai大模型
铁皮饭盒2 天前
20 个后端"黑话"Bunjs代码演示(限流、熔断、降级、舱壁…)一看就会
bun
天蓝色的鱼鱼2 天前
10 分钟用 Bun + Hono + SQLite 跑通一个全栈 API
bun
JieE2123 天前
Bun + TypeScript:下一代 JavaScript 全栈开发的正确打开方式
typescript·全栈·bun