从零搭建一个 RESTful Todo 服务 —— Bun + TypeScript 全栈最小闭环

本文通过一个极简的**任务清单(Todos)**项目,一步步理解如何用 BunTypeScript 搭建一个 RESTful 风格的后端服务,并配合前端页面完成数据展示。文章按"建模 → 存储 → 服务 → 路由 → 消费"的逻辑线展开。


目录

  1. 项目概览
  2. [数据建模:用 interface 定义资源](#数据建模:用 interface 定义资源)
  3. 数据存储:内存中的"数据库"
  4. [HTTP 服务:Bun.serve 启动服务器](#HTTP 服务:Bun.serve 启动服务器)
  5. [RESTful 路由:一切皆资源](#RESTful 路由:一切皆资源)
    • [5.1 获取全部任务 GET /todos](#5.1 获取全部任务 GET /todos)
    • [5.2 获取单个任务 GET /todos/:id](#5.2 获取单个任务 GET /todos/:id)
  6. [CORS 跨域:让前端能访问后端](#CORS 跨域:让前端能访问后端)
  7. [前端消费 API](#前端消费 API)
    • [7.1 Promise + then 链式调用](#7.1 Promise + then 链式调用)
    • [7.2 async/await 异步语法](#7.2 async/await 异步语法)
  8. 运行项目
  9. 总结

1. 项目概览

整个项目只有 3 个文件,结构极其精简:

复制代码
todos/
├── server.ts      # 后端服务(核心)
├── index.html     # 前端页面
└── readme.md      # 概念笔记

数据流非常简单:

复制代码
浏览器 (index.html)  ──GET /todos──▶  Bun 服务器 (server.ts)  ──查找──▶  todos 数组
                                      ◀──JSON 数据──

2. 数据建模:用 interface 定义资源

在面向对象编程(OOP)中,接口(interface) 用于声明一个对象"长什么样"------它必须具备哪些属性和方法。就像给数据签了一份合同,确保后续所有操作都遵循同一套结构。

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

逐字段解释:

字段 类型 含义
id string 任务的唯一标识
title string 任务名称,如"吃饭"
completed boolean 是否完成
createdAt Date 创建时间

interface 只在编译时做类型检查,编译后不产生任何 JS 代码------零运行时开销。你获得了类型安全,却不损失性能。


3. 数据存储:内存中的"数据库"

为了保持示例简单,我们用一个数组代替数据库:

typescript 复制代码
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()
  },
];

声明 : Todo[] 意味着这个数组只能 存放符合 Todo 接口的对象。如果你不小心写了 { id: 1, title: "x" }(缺少 completedcreatedAt),TypeScript 会在编码阶段就报错,而不是等到运行时才发现问题------这就是类型系统的价值。


4. HTTP 服务:Bun.serve 启动服务器

下面这行代码是整个后端的心脏:

typescript 复制代码
const server = Bun.serve({
  port: 8080,
  async fetch(req) {
    // 所有 HTTP 请求都会进入这个函数
  }
})

解释几个关键点:

  • port: 8080 :服务器监听 127.0.0.1:8080。IP 地址对应一台机器,端口号区分同一台机器上的不同服务(HTTP、邮件、音乐服务等)。
  • fetch(req) :这是 Bun.serve 的内置方法,每一个 到达服务器的 HTTP 请求都会被传入这个函数。req 对象包含了请求的所有信息(方法、路径、头信息等)。
  • HTTP 协议的本质:请求(Request)→ 响应(Response)。浏览器发送一个 Request,服务器处理后返回一个 Response。

Bun 内置了 TypeScript 支持和 HTTP 服务,不需要安装任何第三方依赖。


5. RESTful 路由:一切皆资源

RESTful 的核心理念是"一切皆资源 "。URL 路径对应资源名词 ,HTTP 方法对应操作动词

HTTP 方法 含义 示例
GET 读取资源 GET /todos 获取全部任务
POST 创建资源 POST /todos 新建任务
PUT 更新资源 PUT /todos/1 修改任务 1
DELETE 删除资源 DELETE /todos/1 删除任务 1

本项目目前实现了两个 GET 路由。

5.1 获取全部任务 GET /todos

typescript 复制代码
const url = new URL(req.url);  // 解析用户访问的 URL

if (req.method === 'GET' && url.pathname === "/todos") {
  return Response.json(todos, { headers });
}

逻辑拆解:

  1. new URL(req.url) :将浏览器的请求地址(如 http://127.0.0.1:8080/todos)解析为一个 URL 对象,方便提取 pathname
  2. 条件判断 :同时检查请求方法(GET)和路径(/todos),精确匹配。
  3. Response.json(todos):将 TypeScript 数组自动序列化为 JSON 格式返回。

5.2 获取单个任务 GET /todos/:id

typescript 复制代码
if (req.method === 'GET' && url.pathname.startsWith("/todos/")) {
  const id = url.pathname.split("/")[2];
  // "/todos/3" → split("/") → ["", "todos", "3"] → 取 [2] 得 "3"
  const todo = todos.find((t) => t.id === id);
  return Response.json(todo);
}

逻辑拆解:

  1. startsWith("/todos/") :用前缀匹配,因为后面跟着动态的 id
  2. split("/")[2] :从路径中提取 id。例如 /todos/2 分割后得到 ["", "todos", "2"],取下标 [2]
  3. find() :在数组中查找匹配项。如果找不到,返回 undefined

6. CORS 跨域:让前端能访问后端

浏览器的同源策略 默认禁止不同域名/端口之间的请求。前端页面通常通过 file:// 协议打开,与 http://127.0.0.1:8080 属于不同源,因此需要服务端放行:

typescript 复制代码
const headers = {
  'Access-Control-Allow-Origin': "*"
}
  • Access-Control-Allow-Origin: * :允许任何来源的请求访问该接口。
  • 这个 headers 对象被注入到每一个 Response.json() 的返回中。

⚠️ 星号 * 仅适用于开发环境。生产环境中应指定具体的域名。


7. 前端消费 API

有了后端服务,前端页面通过 fetch API 获取数据。项目中展示了两种写法:

7.1 Promise + then 链式调用

javascript 复制代码
fetch("http://127.0.0.1:8080/todos")
  .then(res => res.json())  // 将 Response 转换为 JSON
  .then(data => {           // 拿到真正的数据
    todos.innerHTML = data
      .map(todo => `<li>${todo.title}</li>`)
      .join('');
  });

执行流程:

复制代码
fetch()          →  发送 HTTP 请求
  .then(res => res.json())   →  等待响应,将 body 解析为 JSON
  .then(data => ...)          →  拿到解析后的 JS 对象

链中的每一步都在等上一步完成后才执行------这就是 Promise 的异步模型。

7.2 async/await 异步语法

javascript 复制代码
async function main() {
  const res = await fetch("http://127.0.0.1:8080/todos");
  const data = await res.json();
  todos.innerHTML = data
    .map(todo => `<li>${todo.title}</li>`)
    .join('');
}
main();

对比两种写法:

维度 .then() async/await
可读性 嵌套较多时易混乱 像同步代码,直观
错误处理 .catch() try/catch
本质 Promise 的原生方法 Promise 的语法糖

await 后面的表达式必须是一个 Promisefetch()res.json() 都返回 Promise,所以都可以 await


8. 运行项目

确保已安装 Bun,然后执行:

bash 复制代码
# 启动后端服务
bun run server.ts

# 服务运行在 http://127.0.0.1:8080

然后用浏览器打开 index.html,或者直接访问:

  • http://127.0.0.1:8080/todos --- 获取全部任务(JSON)
  • http://127.0.0.1:8080/todos/1 --- 获取 id 为 1 的任务(JSON)

9. 总结

这个不到 70 行的项目,完整串联了以下知识体系:

复制代码
┌─────────────────────────────────────────────────┐
│                  TypeScript                      │
│  interface → 类型约束 → 编译时检查              │
├─────────────────────────────────────────────────┤
│                  OOP 思想                        │
│  "面向接口编程" → 上层不依赖底层实现            │
├─────────────────────────────────────────────────┤
│                  Bun 运行时                      │
│  Bun.serve → 内置 HTTP 服务 → 零依赖             │
├─────────────────────────────────────────────────┤
│                  RESTful 设计                    │
│  资源 URL + HTTP 动词 → 语义化的 API             │
├─────────────────────────────────────────────────┤
│                  前端消费                        │
│  fetch + Promise → async/await 演进              │
└─────────────────────────────────────────────────┘

这条链路从数据建模 开始,到服务暴露 ,再到前端消费,构成了一个完整的"全栈最小闭环"。理解了这个例子,你就掌握了现代 Web 开发的骨架。

相关推荐
小闹5492 小时前
一个 65 行的小需求,我让 Claude Code 跑了 25 个 agent、整整两小时
后端·claude
天青色等烟雨..2 小时前
智慧农林核心遥感技术99个案例实践
运维·人工智能·spring boot·后端·自动化
西安邮电大学2 小时前
贪心算法详细讲解
java·后端·其他·算法·面试
橙序员小站2 小时前
从"夯"到"拉":谷歌苹果华为开发者大会,谁在裸泳?
人工智能·后端
退休倒计时2 小时前
【每日一题】LeetCode 19. 删除链表的倒数第 N 个结点 TypeScript
leetcode·链表·typescript
HjhIron2 小时前
从零实现一个待办事项应用:前端必学的Ajax与Node.js实战
前端·后端
浩风祭月2 小时前
我用 AI 辅助重构了遗留项目的认证模块:从明文存储到 OAuth 2.0 的安全升级
后端·php·ai编程
用户34232323763172 小时前
数据质量与异常检测——当采集系统学会了“怀疑“
后端