Deno Web 开发与云端部署
用 Hono 构建 REST API,接入 Deno KV 存储与定时任务,部署到 Deno Deploy 边缘网络。
0. 一句话理解
用 Hono 写路由、用 Deno KV 存数据、部署到 Deno Deploy——三样都是”边缘原生”,一个项目从零到上线不需要自建服务器。
本篇把快速入门里学到的语法升级成一个真实的小服务:为”虚拟歌手音乐平台”实现待办式演出任务 API,本地开发、测试、部署三步走完。
1. Hono 第一个 API
// main.ts
import { Hono } from "npm:hono@4"
const app = new Hono()
app.get("/", (c) => c.text("你好,Deno!"))
app.get("/hello/:name", (c) => {
const name = c.req.param("name")
return c.json({ message: `你好,${name}` })
})
// 带中间件的错误兜底:业务代码抛异常时返回统一 JSON
app.onError((err, c) => {
console.error(err)
return c.json({ error: "服务内部错误" }, 500)
})
Deno.serve(app.fetch)
deno run --allow-net=:8000 main.ts
# curl http://localhost:8000/hello/初音
{"message":"你好,初音"}
讲解:
new Hono()创建应用,app.get("/", 处理函数)注册路由;c是上下文对象,c.text/c.json返回响应。c.req.param("name")读取路径参数,/hello/初音会返回 JSON 消息;查询参数用c.req.query("key")。Deno.serve(app.fetch)把 Hono 应用挂到 Deno 内置 HTTP 服务器上,不需要 Express 或额外依赖;--allow-net=:8000把监听端口也收进白名单。app.onError是 Hono 的统一异常出口,对应 NestJS 异常过滤器的角色——框架不同,“错误集中处理”的架构思想完全一致。- Hono 是轻量路由层,选择它是因为 Web 标准 API(Request/Response)与 Deno 原生服务器天然契合;Oak、Fresh 也是常见选择,思路相通。
2. 接入 Deno KV
// kv_todo.ts
import { Hono } from "npm:hono@4"
const kv = await Deno.openKv()
const app = new Hono()
app.post("/todos", async (c) => {
const { title } = await c.req.json()
const id = crypto.randomUUID()
await kv.set(["todos", id], { title, done: false })
return c.json({ id, title, done: false }, 201)
})
app.get("/todos", async (c) => {
const list = []
for await (const entry of kv.list({ prefix: ["todos"] })) {
list.push({ id: entry.key[1], ...entry.value })
}
return c.json(list)
})
app.delete("/todos/:id", async (c) => {
await kv.delete(["todos", c.req.param("id")])
return new Response(null, { status: 204 })
})
Deno.serve(app.fetch)
deno run --unstable-kv --allow-net=:8000 kv_todo.ts
讲解:
Deno.openKv()打开内置键值存储,零配置即可持久化:本地是落在数据目录的 SQLite 文件,部署到 Deno Deploy 后是托管的 FoundationDB 后端,代码不变。注意--unstable-kv:截至 Deno 2.9,KV 仍是官方标注的不稳定 API,运行时需要这个标记开启。kv.set(["todos", id], 对象)以数组作为分层键,kv.list({ prefix: ["todos"] })遍历该前缀下的所有记录;数组键等价于”表名 + 主键”的两级结构。crypto.randomUUID()生成唯一 id;for await异步遍历 KV 结果集。- KV 天然适合会话、配置、小型业务数据;原子操作、watch 监听与适用边界(何时换 PostgreSQL)见Deno KV 与队列。
3. 定时任务与测试
// cron.ts —— 每天凌晨 3 点清理已完成的演出任务
Deno.cron("cleanup", "0 3 * * *", async () => {
const kv = await Deno.openKv()
for await (const entry of kv.list({ prefix: ["todos"] })) {
if (entry.value.done) await kv.delete(entry.key)
}
})
deno test --allow-net=localhost --allow-env
讲解:
Deno.cron(名字, cron 表达式, 函数)是内置定时任务:本地由 Deno 调度,部署到 Deno Deploy 后自动成为云端定时任务,两侧语义一致。- cron 表达式与 Linux crontab 相同:
0 3 * * *即每天 03:00;写错表达式会在启动时报错,不用担心静默不执行。 - 测试沿用
Deno.test+@std/assert:把路由处理函数抽成独立函数后可脱离 HTTP 直接断言;要测真实接口时用app.request("/todos", { method: "POST", body })构造请求,不需要真开端口。
4. 部署到 Deno Deploy
# 安装部署 CLI
deno install -gArf jsr:@deno/deployctl
# 登录并部署(首次会打开浏览器授权)
deployctl deploy --project=my-deno-app main.ts
讲解:
deployctl deploy把项目推送到 Deno Deploy 边缘网络,全球节点就近执行,无需配置服务器。- 每次部署会生成新的预览 URL,正式域名在控制台绑定;也可以接入 GitHub 仓库,push 即自动部署。
- 云端自动提供
Deno.openKv()、Deno.cron等托管服务,本地与线上 API 一致,无环境差异——这是”边缘原生”相对于传统部署最大的体验优势。 - 服务器环境的权限由平台代管:不再需要手敲
--allow-net,但本地开发时仍要养成最小权限的习惯,否则迁移到自建 Docker 时会踩权限坑。 - 不想绑定单一平台时,标准做法是
deno compile出单文件可执行程序放进 Docker 镜像,部署到任意云主机——Deno 不锁定部署目标。
5. 常见陷阱
- 本地 KV 数据”消失”:
Deno.openKv()本地默认按项目路径存 SQLite 文件,换目录运行就是新库;需要共享数据时显式传路径Deno.openKv("./data/kv.db")。 - Deploy 上环境变量未设置:控制台配置的环境变量与本地
.env是两套体系,上线前逐项核对;密钥类变量用 Deploy 的 secrets 功能。 - 忘记返回状态码:
c.json(data)默认 200,创建资源应显式c.json(data, 201),删除成功返回 204(无 body)——语义正确的状态码是 API 的基本素养。 for await遍历 KV 时边遍历边写:遍历前缀时插入/删除同前缀 key 可能漏读或重读,先收集再处理更稳妥。- 把 Hono 当全功能后端框架:Hono 只管 HTTP 路由,没有依赖注入与模块系统;业务复杂后要么自己分层,要么迁移到 NestJS(见概述与快速上手,本模块主线)——小工具用 Hono,企业应用用 Nest,不是替代关系。
6. 动手试试
- 给待办 API 增加
PATCH /todos/:id,用kv.set原地更新done字段,并测试切换效果。 - 用
Deno.cron写一个每分钟执行的统计任务,把任务总数写入["stats", "todo-count"]。 - 把项目部署到 Deno Deploy,用浏览器访问线上接口,再在控制台试试回滚到上一个版本。
- 用
app.request()给POST /todos写两个测试用例:合法创建返回 201、空 body 返回 400。
7. 本篇小结
初学者要点:
- Deno 的 Web 开发链路最短:Hono 写 API、KV 存数据、Deploy 一键上线,权限参数决定它能碰什么。
- 本地与云端 API 一致(KV、Cron),是 Deno Deploy 区别于传统部署的核心优势。
- 状态码语义(201/204/400/500)与统一错误出口(
app.onError)从第一个接口就要做对。
进阶注意:
- KV 的进阶用法(原子操作、watch、与 Postgres 的取舍)在Deno KV 与队列展开;本篇先建立”边缘原生”的直觉。
- Hono 解决”路由”,NestJS 解决”架构”;当模块、依赖注入、校验、守卫这些词开始出现在需求里,就是切换框架的信号。