从零搭建一个 RESTful Todo 服务 —— Bun + TypeScript 全栈最小闭环 本文通过一个极简的**任务清单Todos**项目一步步理解如何用Bun和TypeScript搭建一个 RESTful 风格的后端服务并配合前端页面完成数据展示。文章按建模 → 存储 → 服务 → 路由 → 消费的逻辑线展开。目录项目概览数据建模用 interface 定义资源数据存储内存中的数据库HTTP 服务Bun.serve 启动服务器RESTful 路由一切皆资源5.1 获取全部任务GET /todos5.2 获取单个任务GET /todos/:idCORS 跨域让前端能访问后端前端消费 API7.1 Promise then 链式调用7.2 async/await 异步语法运行项目总结1. 项目概览整个项目只有3 个文件结构极其精简todos/ ├── server.ts # 后端服务核心 ├── index.html # 前端页面 └── readme.md # 概念笔记数据流非常简单浏览器 (index.html) ──GET /todos──▶ Bun 服务器 (server.ts) ──查找──▶ todos 数组 ◀──JSON 数据──2. 数据建模用 interface 定义资源在面向对象编程OOP中接口interface用于声明一个对象长什么样——它必须具备哪些属性和方法。就像给数据签了一份合同确保后续所有操作都遵循同一套结构。interfaceTodo{id:string;title:string;completed:boolean;createdAt:Date;}逐字段解释字段类型含义idstring任务的唯一标识titlestring任务名称如吃饭completedboolean是否完成createdAtDate创建时间interface只在编译时做类型检查编译后不产生任何 JS 代码——零运行时开销。你获得了类型安全却不损失性能。3. 数据存储内存中的数据库为了保持示例简单我们用一个数组代替数据库consttodos:Todo[][{id:1,title:吃饭,completed:false,createdAt:newDate()},{id:2,title:睡觉,completed:false,createdAt:newDate()},{id:3,title:打豆豆,completed:false,createdAt:newDate()},];声明: Todo[]意味着这个数组只能存放符合Todo接口的对象。如果你不小心写了{ id: 1, title: x }缺少completed或createdAtTypeScript 会在编码阶段就报错而不是等到运行时才发现问题——这就是类型系统的价值。4. HTTP 服务Bun.serve 启动服务器下面这行代码是整个后端的心脏constserverBun.serve({port:8080,asyncfetch(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修改任务 1DELETE删除资源DELETE /todos/1删除任务 1本项目目前实现了两个GET路由。5.1 获取全部任务GET /todosconsturlnewURL(req.url);// 解析用户访问的 URLif(req.methodGETurl.pathname/todos){returnResponse.json(todos,{headers});}逻辑拆解new URL(req.url)将浏览器的请求地址如http://127.0.0.1:8080/todos解析为一个 URL 对象方便提取pathname。条件判断同时检查请求方法GET和路径/todos精确匹配。Response.json(todos)将 TypeScript 数组自动序列化为 JSON 格式返回。5.2 获取单个任务GET /todos/:idif(req.methodGETurl.pathname.startsWith(/todos/)){constidurl.pathname.split(/)[2];// /todos/3 → split(/) → [, todos, 3] → 取 [2] 得 3consttodotodos.find((t)t.idid);returnResponse.json(todo);}逻辑拆解startsWith(/todos/)用前缀匹配因为后面跟着动态的id。split(/)[2]从路径中提取id。例如/todos/2分割后得到[, todos, 2]取下标[2]。find()在数组中查找匹配项。如果找不到返回undefined。6. CORS 跨域让前端能访问后端浏览器的同源策略默认禁止不同域名/端口之间的请求。前端页面通常通过file://协议打开与http://127.0.0.1:8080属于不同源因此需要服务端放行constheaders{Access-Control-Allow-Origin:*}Access-Control-Allow-Origin: *允许任何来源的请求访问该接口。这个 headers 对象被注入到每一个Response.json()的返回中。⚠️ 星号*仅适用于开发环境。生产环境中应指定具体的域名。7. 前端消费 API有了后端服务前端页面通过fetchAPI获取数据。项目中展示了两种写法7.1 Promise then 链式调用fetch(http://127.0.0.1:8080/todos).then(resres.json())// 将 Response 转换为 JSON.then(data{// 拿到真正的数据todos.innerHTMLdata.map(todoli${todo.title}/li).join();});执行流程fetch() → 发送 HTTP 请求 .then(res res.json()) → 等待响应将 body 解析为 JSON .then(data ...) → 拿到解析后的 JS 对象链中的每一步都在等上一步完成后才执行——这就是 Promise 的异步模型。7.2 async/await 异步语法asyncfunctionmain(){constresawaitfetch(http://127.0.0.1:8080/todos);constdataawaitres.json();todos.innerHTMLdata.map(todoli${todo.title}/li).join();}main();对比两种写法维度.then()链async/await可读性嵌套较多时易混乱像同步代码直观错误处理.catch()try/catch本质Promise 的原生方法Promise 的语法糖await后面的表达式必须是一个Promise。fetch()和res.json()都返回 Promise所以都可以await。8. 运行项目确保已安装 Bun然后执行# 启动后端服务bun run server.ts# 服务运行在 http://127.0.0.1:8080然后用浏览器打开index.html或者直接访问http://127.0.0.1:8080/todos— 获取全部任务JSONhttp://127.0.0.1:8080/todos/1— 获取 id 为 1 的任务JSON9. 总结这个不到 70 行的项目完整串联了以下知识体系┌─────────────────────────────────────────────────┐ │ TypeScript │ │ interface → 类型约束 → 编译时检查 │ ├─────────────────────────────────────────────────┤ │ OOP 思想 │ │ 面向接口编程 → 上层不依赖底层实现 │ ├─────────────────────────────────────────────────┤ │ Bun 运行时 │ │ Bun.serve → 内置 HTTP 服务 → 零依赖 │ ├─────────────────────────────────────────────────┤ │ RESTful 设计 │ │ 资源 URL HTTP 动词 → 语义化的 API │ ├─────────────────────────────────────────────────┤ │ 前端消费 │ │ fetch Promise → async/await 演进 │ └─────────────────────────────────────────────────┘这条链路从数据建模开始到服务暴露再到前端消费构成了一个完整的全栈最小闭环。理解了这个例子你就掌握了现代 Web 开发的骨架。