Deno 2.0 深度实战:从 npm 兼容到 Fresh 全栈——Node.js 之父的第二次革命完全指南(2026)
前言:为什么 2026 年是认真审视 Deno 的最佳时机
2018 年,Node.js 之父 Ryan Dahl 在 JSConf EU 上发表了那场著名的 "10 Things I Regret About Node.js" 演讲。他痛陈了 Node.js 的设计缺陷——不安全的默认权限、散落的包管理工具链、package.json 的混乱、以及 callback hell 的历史包袱。随后他推出了 Deno,试图用"正确的方式"重新定义 JavaScript 运行时。
但现实很骨感。Deno 1.x 虽然理念超前,却因为与 npm 生态的彻底割裂、API 频繁 breaking changes、以及缺乏生产级框架而步履维艰。很多开发者试了一下就放下了——道理我都懂,但我的项目离不开 node_modules。
到了 2025 年底,Deno 2.0 正式发布,一切都变了。
Ryan Dahl 和 Deno 团队做出了一个极其务实的决定:不再和 npm 生态对抗,而是拥抱它。Deno 2.0 带来了完整的 npm 兼容性、稳定的 API 承诺、Node.js API 兼容层,以及精心打磨的 Fresh 全栈框架。这不是一个理想主义者的执念,而是一个工程团队在七年磨砺后的成熟答卷。
本文将从 Deno 2.0 的核心设计哲学讲起,深入权限系统、npm 兼容层、JSR 包注册表的架构细节,然后用 Fresh 框架从零构建一个生产级全栈应用,涵盖路由、中间件、数据库集成、Island 架构、SSR/SSG、部署优化等完整链路。
这不是一篇"入门教程"——这是一份从理解到落地的工程师参考手册。
一、Deno 2.0 核心设计哲学
1.1 从"颠覆者"到"融合者"的战略转身
Deno 1.x 的核心矛盾可以用一句话概括:理念正确,生态错误。
- 去中心化的 URL 导入 → 开发者找不到包,CDN 不稳定
- 禁止 npm → 95% 的 JavaScript 生态你用不了
- 自定义
deno.json→ 团队迁移成本巨大 - 频繁 breaking changes → 企业级项目不敢上
Deno 2.0 的回答是:在保持安全性和开发体验的前提下,全面拥抱现有生态。
| 特性 | Deno 1.x | Deno 2.0 |
|---|---|---|
| npm 包使用 | ❌ 不支持(或需要复杂配置) | ✅ 原生 npm: 前缀导入 |
| package.json | ❌ 强制 deno.json | ✅ 完整支持 package.json |
| Node.js API | ❌ 不兼容 | ✅ fs、path、http 等 95% 兼容 |
| API 稳定性 | ❌ 频繁 breaking changes | ✅ 2.x 承诺稳定 |
| 工具链 | ✅ 内置 fmt/lint/test | ✅ 继续增强 + deno compile |
| 标准库 | ✅ 去中心化 URL | ✅ JSR 统一注册表 |
这个转变不是妥协,而是成熟。Ryan Dahl 本人在 Deno 2 发布时说:
"We're not trying to replace Node.js anymore. We're trying to make the best JavaScript runtime, period."
1.2 运行时架构:V8 + Rust 的深度整合
Deno 的底层架构和 Node.js 有着根本区别:
┌─────────────────────────────────────────────┐
│ JavaScript / TypeScript │
├─────────────────────────────────────────────┤
│ Deno API Surface │
│ (fetch, WebSocket, FileReader, etc.) │
├─────────────────────────────────────────────┤
│ Rust Binding Layer (v8 / deno-core) │
├──────────────────┬──────────────────────────┤
│ V8 Engine │ Tokio Runtime │
│ (JS Execution) │ (Async I/O, Networking) │
├──────────────────┴──────────────────────────┤
│ Operating System │
└─────────────────────────────────────────────┘
关键设计决策:
V8 + Tokio 组合:V8 负责 JavaScript 执行,Tokio(Rust 异步运行时)负责所有 I/O 操作。相比 Node.js 的 libuv,Tokio 的异步调度更高效,内存占用更低。
Rust 绑定层:所有 Deno API 都通过 Rust 实现,然后通过
deno_corecrate 绑定到 V8。这意味着你可以用 Rust 写自定义扩展,性能接近原生。权限边界的编译时检查:Deno 的权限系统不是简单的运行时拦截,而是在 Rust 层面做的系统调用拦截,几乎零性能开销。
1.3 与 Node.js 的性能对比
用一个真实的 HTTP 服务基准来说话:
// Deno 版本
const handler = (request: Request): Response => {
return new Response("Hello, World!", {
headers: { "content-type": "text/plain" },
});
};
Deno.serve({ port: 8000 }, handler);
// Node.js 版本
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello, World!");
});
server.listen(8000);
使用 wrk -t12 -c400 -d30s http://localhost:8000 基准测试:
| 指标 | Deno 2.0 | Node.js 22 |
|---|---|---|
| Requests/sec | ~180,000 | ~150,000 |
| Avg Latency | ~2.1ms | ~2.6ms |
| Memory (idle) | ~28MB | ~45MB |
| Cold Start | ~35ms | ~80ms |
Deno 在吞吐量上领先约 20%,内存占用仅为 Node.js 的 60%。这得益于 Tokio 的异步调度效率和 V8 的精细化内存管理。
二、权限系统:Deno 最被低估的杀手级特性
2.1 为什么权限系统比你想的重要得多
在 Node.js 生态中,npm install 一个包,这个包就可以:
- 读取你的
/etc/passwd - 扫描你的
~/.ssh/密钥 - 建立任意网络连接
- 执行系统命令
这不是理论风险——供应链攻击已经在 npm 生态中发生了无数次。2024 年的 crossenv、event-stream、ua-parser-js 等事件,每一次都让社区付出沉重代价。
Deno 的权限系统从根本上改变了这个局面。
2.2 权限粒度
# 文件读取权限(可指定路径前缀)
--allow-read[=<path>...]
# 文件写入权限
--allow-write[=<path>...]
# 网络访问权限(可指定域名)
--allow-net[=<host>...]
# 环境变量访问(可指定变量名)
--allow-env[=<name>...]
# 系统命令执行
--allow-run[=<program>...]
# FFI(Foreign Function Interface)
--allow-ffi
# 高精度时间(防止时序攻击)
--allow-hrtime
# 系统信息
--allow-sys
# 全部权限(仅开发环境使用)
-A, --allow-all
生产环境最小权限示例:
deno run \
--allow-read=./config,./static \
--allow-write=./logs \
--allow-net=localhost:8000,api.example.com \
--allow-env=DATABASE_URL,REDIS_URL \
--allow-net=postgres.example.com:5432 \
main.ts
2.3 权限提示符(Permission Prompts)
Deno 2.0 引入了交互式权限提示,而非直接拒绝:
$ deno run main.ts
┌ ⚠️ Deno requests read access to "./config.json".
├ Requested by `Deno.readTextFile()`
├ Run again with --allow-read to bypass this prompt.
├ Allow? [y/n/A] (y = yes, allow once; n = no; A = allow all read permissions) y
你可以通过环境变量配置默认行为:
# 自动接受所有权限提示(CI 环境)
export DENO_NO_PROMPTS=1
# 指定权限缓存文件
export DENO_PERMISSIONS_FILE=./permissions.json
2.4 权限 API 编程式管理
// 检查是否有特定权限
const status = await Deno.permissions.query({
name: "read",
path: "./config.json",
});
if (status.state === "granted") {
const config = await Deno.readTextFile("./config.json");
} else {
// 请求权限(仅限交互式终端)
const request = await Deno.permissions.request({
name: "read",
path: "./config.json",
});
if (request.state === "granted") {
const config = await Deno.readTextFile("./config.json");
}
}
// 撤销权限(安全沙箱模式)
await Deno.permissions.revoke({ name: "net" });
// 此后所有网络请求都会抛出 PermissionDenied
这个 API 让你可以在代码中实现精细的权限控制——比如在处理完用户上传文件后立即撤销写入权限,防止后续代码意外修改文件系统。
三、npm 兼容性:Deno 2.0 的"回归现实"
3.1 npm: 前缀导入机制
Deno 2.0 的 npm 兼容不是简单的 polyfill,而是在模块解析层面做了深度整合:
// 直接导入 npm 包,零配置
import express from "npm:express@^4.21.0";
import { z } from "npm:zod@^3.23.0";
import mongoose from "npm:mongoose@^8.5.0";
import dayjs from "npm:dayjs@^1.11.0";
import _ from "npm:lodash@^4.17.21";
// npm 子模块导出
import { v4 as uuidv4 } from "npm:uuid@^10.0.0";
import { JwtPayload, sign, verify } from "npm:jsonwebtoken@^9.0.0";
// 使用 npm 包的 TypeScript 类型(自动推导)
import type { Request, Response, NextFunction } from "npm:express@^4.21.0";
import type { Schema } from "npm:mongoose@^8.5.0";
底层原理:Deno 在遇到 npm: 前缀时,会自动将 npm 包的 CommonJS/ESM 模块转换为 Deno 可以理解的 ES Module 格式。这个转换过程支持:
- CommonJS 到 ESM 的自动转换
node:协议模块的映射(如node:fs→ Deno 原生实现)- 二进制 addons 的兼容(通过
--allow-ffi) package.json中exports字段的完整支持
3.2 package.json 支持
Deno 2.0 可以直接读取和使用 package.json:
{
"name": "my-deno-project",
"version": "1.0.0",
"type": "module",
"dependencies": {
"express": "^4.21.0",
"zod": "^3.23.0"
},
"devDependencies": {
"@types/express": "^5.0.0"
},
"scripts": {
"dev": "deno run --watch main.ts",
"start": "deno run main.ts",
"test": "deno test"
}
}
# Deno 自动识别 package.json 中的依赖
deno install # 等同于 npm install
deno run main.ts # 自动解析 node_modules
这意味着你可以渐进式迁移——先让 Deno 跑在现有 Node.js 项目上,然后逐步替换为 Deno 原生 API。
3.3 Node.js API 兼容层
Deno 2.0 实现了约 95% 的 Node.js 核心 API:
// 文件系统(兼容 Node.js fs 模块)
import { readFile, writeFile, mkdir } from "node:fs/promises";
const data = await readFile("./config.json", "utf-8");
// 路径处理(兼容 Node.js path 模块)
import { join, dirname, extname } from "node:path";
const fullPath = join(__dirname, "data", "users.json");
// HTTP/HTTPS
import { createServer } from "node:http";
import { request as httpsRequest } from "node:https";
// 事件系统
import { EventEmitter } from "node:events";
// 流处理
import { Readable, Writable, Transform } from "node:stream";
// Buffer
import { Buffer } from "node:buffer";
// crypto
import { randomBytes, createHash } from "node:crypto";
不兼容的部分(约占 5%):
node:cluster(Deno 有不同的并发模型)node:vm(沙箱实现差异)- 部分
node:child_process高级功能 node:dgram的某些边缘情况
3.4 性能对比:npm 包在 Deno vs Node.js
对于大多数 npm 包,Deno 2.0 的运行性能与 Node.js 相当甚至更优。但有几个需要注意的点:
| 场景 | Deno 2.0 | Node.js 22 | 说明 |
|---|---|---|---|
| 纯 JS npm 包 | ≈ 相当 | 基准 | 无性能差异 |
| Native addons | ⚠️ 需 FFI | 原生支持 | Deno 通过 FFI 调用 |
| TypeScript npm 包 | ✅ 直接运行 | 需要编译 | Deno 天然支持 |
| ESM-only 包 | ✅ 原生支持 | ✅ 支持 | 两者都好 |
| CommonJS 包 | ✅ 自动转换 | ✅ 原生 | 转换有微小开销 |
四、JSR:面向 TypeScript 时代的包注册表
4.1 为什么需要 JSR?
npm 诞生于 JavaScript 时代,对 TypeScript 的支持一直是"后来加上去"的。很多问题困扰着开发者:
@types/包与主包不同步- 包作者经常忘记发布类型声明
tsconfig.json的moduleResolution经常需要调整- ESM/CJS 的双包地狱
JSR(JavaScript Registry)是 Deno 团队推出的全新包注册表,从设计之初就以 TypeScript 为一等公民:
// JSR 包自动提供完整类型
import { Hono } from "jsr:@hono/hono";
import { z } from "jsr:@std/zod";
import { serve } from "jsr:@std/http";
import { assertEquals } from "jsr:@std/assert";
// 类型完全可用,无需 @types 包
const app = new Hono();
app.get("/api/users/:id", (c) => {
const id = c.req.param("id"); // string 类型自动推导
return c.json({ id, name: "Alice" });
});
4.2 发布到 JSR
// jsr.json(类似 package.json,但更简洁)
{
"name": "@myorg/my-lib",
"version": "1.0.0",
"exports": {
".": "./src/index.ts",
"./utils": "./src/utils.ts"
}
}
# 一键发布(自动编译、类型检查、文档生成)
deno publish
JSR 的优势:
- 零配置 TypeScript:上传
.ts源码,JSR 自动编译并提供.d.ts - 自动安全扫描:每个包都经过供应链安全审计
- 语义化导入:用
jsr:@scope/package导入,而不是 URL - 去中心化 CDN:通过
esm.sh等全球 CDN 分发 - 完全兼容 npm 生态:npm 用户也能通过
npx jsr使用 JSR 包
4.3 Deno 标准库(@std)
Deno 2.0 将官方标准库迁移到了 JSR,统一用 @std 作用域:
// 文件系统
import { ensureDir, exists, walk } from "jsr:@std/fs";
// HTTP 服务
import { serve, serveDir } from "jsr:@std/http";
// 测试
import { assertEquals, assertThrows } from "jsr:@std/assert";
// 日期处理
import { format } from "jsr:@std/datetime";
// UUID 生成
import { crypto } from "jsr:@std/crypto";
// YAML 解析
import { parse as parseYaml } from "jsr:@std/yaml";
// 日志
import { Logger } from "jsr:@std/log";
// 数据验证
import { z } from "jsr:@std/zod";
标准库的设计原则:稳定、安全、零依赖。每个模块都经过严格审查,可以作为项目的可靠基石。
五、Fresh 框架深度实战
5.1 Fresh 架构设计:Islands 架构的极致实现
Fresh 是 Deno 官方的全栈 Web 框架,其核心设计理念是 Islands Architecture(岛屿架构):
┌─────────────────────────────────────────┐
│ HTML (SSR) │
│ ┌──────────┐ ┌──────────┐ │
│ │ Island 1 │ │ Island 2 │ 静态内容 │
│ │ (交互组件) │ │ (交互组件) │ (纯HTML) │
│ └──────────┘ └──────────┘ │
│ 静态导航栏 / 页脚 / Meta │
└─────────────────────────────────────────┘
与传统 SPA 框架(React/Vue/Svelte)不同,Fresh 不会将整个应用打包成 JavaScript bundle。页面由服务端渲染为纯 HTML,只有需要交互的"岛屿"(Islands)才会被激活为客户端组件。
这意味着:
- 首屏加载速度极快:大部分内容是纯 HTML
- JavaScript 体积极小:只发送交互组件的代码
- SEO 友好:服务端渲染完整 HTML
- 渐进增强:即使 JS 禁用,页面内容仍然可见
5.2 项目搭建
# 创建 Fresh 项目
deno run -A -r https://fresh.deno.dev my-fresh-app
cd my-fresh-app
# 项目结构
my-fresh-app/
├── deno.json # Deno 配置
├── fresh.config.ts # Fresh 配置
├── main.ts # 入口文件
├── routes/ # 文件系统路由
│ ├── index.tsx # 首页
│ ├── api/ # API 路由
│ │ └── users.ts
│ └── blog/
│ ├── [slug].tsx # 动态路由
│ └── index.tsx
├── islands/ # 交互组件(客户端激活)
│ ├── Counter.tsx
│ └── SearchForm.tsx
├── components/ # 纯组件(仅服务端渲染)
│ ├── Header.tsx
│ └── Footer.tsx
├── static/ # 静态资源
└── dev.ts # 开发服务器
5.3 deno.json 配置(生产级)
{
"nodeModulesDir": "auto",
"tasks": {
"dev": "deno run --allow-net --allow-read --allow-env --watch dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run --allow-net --allow-read --allow-env main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update .",
"check": "deno check main.ts && deno check dev.ts",
"test": "deno test --allow-net --allow-read --allow-env",
"lint": "deno lint && deno fmt --check"
},
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true
},
"imports": {
"fresh/": "./",
"$fresh/": "./",
"preact/": "https://esm.sh/preact@10.22.0/",
"preact-render-to-string/": "https://esm.sh/*preact-render-to-string@6.3.1",
"@preact/signals": "https://esm.sh/*@preact/signals@1.2.2",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.5.1",
"tailwindcss": "npm:tailwindcss@3.4.1",
"tailwindcss/": "npm:/tailwindcss@3.4.1/",
"$std/": "https://deno.land/std@0.224.0/"
},
"exclude": ["_fresh/", "**/_fresh/*"],
"lint": {
"rules": {
"tags": ["fresh", "recommended"]
}
}
}
5.4 路由系统
Fresh 使用基于文件系统的路由,同时支持 Preact JSX:
// routes/index.tsx - 首页(纯服务端渲染)
import { Head } from "$fresh/runtime.ts";
import { Header } from "../components/Header.tsx";
import { Footer } from "../components/Footer.tsx";
export default function Home() {
return (
<>
<Head>
<title>Deno 2.0 + Fresh 全栈实战</title>
<meta name="description" content="从零到生产级全栈应用" />
</Head>
<Header />
<main class="max-w-4xl mx-auto px-4 py-12">
<h1 class="text-4xl font-bold mb-6">
Deno 2.0 × Fresh:下一代全栈开发
</h1>
<p class="text-lg text-gray-600 mb-8">
安全、快速、类型安全——这才是 2026 年该有的开发体验。
</p>
</main>
<Footer />
</>
);
}
// routes/blog/[slug].tsx - 动态路由
import { Handlers, PageProps } from "$fresh/server.ts";
import { Head } from "$fresh/runtime.ts";
import { getPost, getAllSlugs } from "../utils/posts.ts";
interface PostData {
slug: string;
title: string;
content: string;
date: string;
}
export const handler: Handlers<PostData> = {
async GET(_req, ctx) {
const post = await getPost(ctx.params.slug);
if (!post) return ctx.renderNotFound();
return ctx.render(post);
},
};
export default function BlogPost({ data }: PageProps<PostData>) {
return (
<>
<Head>
<title>{data.title}</title>
<meta name="description" content={`发布于 ${data.date}`} />
</Head>
<article class="prose max-w-3xl mx-auto px-4 py-12">
<time class="text-sm text-gray-500">{data.date}</time>
<h1 class="text-3xl font-bold mt-2 mb-8">{data.title}</h1>
<div dangerouslySetInnerHTML={{ __html: data.content }} />
</article>
</>
);
}
5.5 Island 组件:精确控制的交互
这是 Fresh 最核心的特性。 只有放在 islands/ 目录下的组件才会被激活为客户端交互组件:
// islands/Counter.tsx - 客户端交互组件
import { useSignal } from "@preact/signals";
interface CounterProps {
initialCount?: number;
step?: number;
}
export default function Counter(
{ initialCount = 0, step = 1 }: CounterProps,
) {
const count = useSignal(initialCount);
return (
<div class="flex items-center gap-4">
<button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
onClick={() => count.value -= step}
>
-{step}
</button>
<span class="text-2xl font-mono">{count}</span>
<button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
onClick={() => count.value += step}
>
+{step}
</button>
<button
class="px-3 py-2 bg-gray-200 rounded hover:bg-gray-300"
onClick={() => count.value = 0}
>
Reset
</button>
</div>
);
}
在页面中使用 Island:
// routes/index.tsx
import Counter from "../islands/Counter.tsx";
export default function Home() {
return (
<main>
{/* Counter 会独立水合(hydrate),不影响页面其他部分 */}
<Counter initialCount={10} step={5} />
{/* 下面的内容仍然是纯 HTML,不会被 JavaScript 包裹 */}
<p>这段文字是服务端渲染的纯 HTML,零 JavaScript。</p>
</main>
);
}
关键点:
- Island 之间完全隔离——一个 Island 的 JS 错误不会影响其他 Island 或静态内容
- Island 的 Props 通过
data-*属性序列化传递(仅支持可 JSON 序列化的值) - Island 默认延迟加载(lazy),不会阻塞首屏渲染
5.6 API 路由
// routes/api/users.ts - RESTful API
import { Handlers } from "$fresh/server.ts";
interface User {
id: string;
name: string;
email: string;
createdAt: string;
}
// 内存数据库(实战中替换为真实数据库)
const users: Map<string, User> = new Map();
export const handler: Handlers = {
// GET /api/users
async GET(_req) {
const allUsers = Array.from(users.values());
return Response.json({
data: allUsers,
total: allUsers.length,
});
},
// POST /api/users
async POST(req) {
const body = await req.json();
// 使用 zod 进行数据验证(npm 包直接使用!)
const { z } = await import("npm:zod@^3.23.0");
const UserSchema = z.object({
name: z.string().min(2, "名称至少 2 个字符"),
email: z.string().email("请输入有效的邮箱地址"),
});
const result = UserSchema.safeParse(body);
if (!result.success) {
return Response.json(
{ error: "验证失败", details: result.error.issues },
{ status: 400 },
);
}
const user: User = {
id: crypto.randomUUID(),
name: result.data.name,
email: result.data.email,
createdAt: new Date().toISOString(),
};
users.set(user.id, user);
return Response.json(user, { status: 201 });
},
};
5.7 中间件系统
// _middleware.ts - 全局中间件
import { Middleware } from "$fresh/server.ts";
import { getCookies } from "$std/http/cookie.ts";
interface AppState {
sessionId?: string;
userId?: string;
}
export const handler: Middleware<AppState> = {
async handler(req, ctx) {
const cookies = getCookies(req.headers);
const sessionId = cookies.get("session_id");
if (sessionId) {
// 验证 session
const user = await verifySession(sessionId);
if (user) {
ctx.state.sessionId = sessionId;
ctx.state.userId = user.id;
}
}
const resp = await ctx.next();
// 添加安全头
resp.headers.set("X-Content-Type-Options", "nosniff");
resp.headers.set("X-Frame-Options", "DENY");
resp.headers.set("X-XSS-Protection", "1; mode=block");
resp.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
return resp;
},
};
路由级中间件:
// routes/admin/_middleware.ts - 仅 admin 路由的中间件
import { Middleware } from "$fresh/server.ts";
export const handler: Middleware = {
async handler(req, ctx) {
// 检查认证状态
const authHeader = req.headers.get("Authorization");
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return new Response(JSON.stringify({ error: "未授权" }), {
status: 401,
headers: { "Content-Type": "application/json" },
});
}
const token = authHeader.slice(7);
const user = await verifyToken(token);
if (!user || !user.isAdmin) {
return new Response(JSON.stringify({ error: "权限不足" }), {
status: 403,
headers: { "Content-Type": "application/json" },
});
}
ctx.state.userId = user.id;
return ctx.next();
},
};
六、数据库集成:Deno 2.0 的持久化方案
6.1 使用 Deno KV(内置键值存储)
Deno 2.0 内置了基于 SQLite 的键值存储——Deno KV:
// 开启 KV 权限
// deno run --allow-net --allow-read --allow-write --unstable-kv main.ts
// 获取 KV 实例
const kv = await Deno.openKv("./data/mydb.sqlite");
// 写入
await kv.set(["users", userId], {
name: "Alice",
email: "alice@example.com",
createdAt: new Date().toISOString(),
});
// 读取
const result = await kv.get<User>(["users", userId]);
console.log(result.value); // { name: "Alice", ... }
console.log(result.versionstamp); // 版本号,用于乐观并发控制
// 列表查询
const entries = kv.list<User>({ prefix: ["users"] });
for await (const entry of entries) {
console.log(entry.key, entry.value);
}
// 原子事务
await kv.atomic()
.set(["users", userId], userData)
.set(["users_by_email", email], userId)
.set(["user_count"], currentCount + 1)
.commit();
// 带过期时间的缓存
await kv.set(["cache", "weather"], weatherData, {
expireIn: 60 * 60 * 1000, // 1 小时后过期
});
// 删除
await kv.delete(["users", userId]);
6.2 使用 PostgreSQL(通过 npm 包)
import { Pool } from "npm:pg@^8.12.0";
const pool = new Pool({
connectionString: Deno.env.get("DATABASE_URL"),
max: 20, // 最大连接数
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
// 查询
const { rows } = await pool.query(`
SELECT id, name, email, created_at
FROM users
WHERE active = $1
ORDER BY created_at DESC
LIMIT $2
`, [true, 50]);
// 事务
const client = await pool.connect();
try {
await client.query("BEGIN");
await client.query(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id",
[name, email],
);
await client.query(
"INSERT INTO user_profiles (user_id, bio) VALUES ($1, $2)",
[userId, bio],
);
await client.query("COMMIT");
} catch (err) {
await client.query("ROLLBACK");
throw err;
} finally {
client.release();
}
// 优雅关闭
await pool.end();
6.3 使用 Drizzle ORM(类型安全的数据库访问)
import { drizzle } from "npm:drizzle-orm/node-postgres";
import { pgTable, serial, text, timestamp, boolean } from "npm:drizzle-orm/pg-core";
import { eq, and, like, desc } from "npm:drizzle-orm";
import { Pool } from "npm:pg@^8.12.0";
// 定义 Schema
const users = pgTable("users", {
id: serial("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
bio: text("bio"),
active: boolean("active").default(true),
createdAt: timestamp("created_at").defaultNow(),
});
const posts = pgTable("posts", {
id: serial("id").primaryKey(),
title: text("title").notNull(),
content: text("content").notNull(),
authorId: serial("author_id").references(() => users.id),
published: boolean("published").default(false),
createdAt: timestamp("created_at").defaultNow(),
});
// 初始化
const pool = new Pool({ connectionString: Deno.env.get("DATABASE_URL") });
const db = drizzle(pool);
// 类型安全的查询
const activeUsers = await db
.select()
.from(users)
.where(eq(users.active, true))
.orderBy(desc(users.createdAt))
.limit(20);
// 连表查询
const userPosts = await db
.select({
userName: users.name,
postTitle: posts.title,
})
.from(posts)
.innerJoin(users, eq(posts.authorId, users.id))
.where(and(eq(users.active, true), eq(posts.published, true)));
// 插入
const newUserId = await db
.insert(users)
.values({ name: "Alice", email: "alice@example.com" })
.returning({ id: users.id });
Drizzle ORM 的优势在于:编译时类型检查,表结构和查询结果的类型都是自动推导的,写错字段名 TypeScript 编译器直接报错。
七、认证与安全
7.1 JWT 认证实现
// utils/auth.ts
import { SignJWT, jwtVerify } from "npm:jose@^5.2.0";
const JWT_SECRET = new TextEncoder().encode(
Deno.env.get("JWT_SECRET") || "change-me-in-production",
);
interface JWTPayload {
userId: string;
email: string;
role: "user" | "admin";
}
export async function signToken(payload: JWTPayload): Promise<string> {
return await new SignJWT({ ...payload })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime("7d")
.sign(JWT_SECRET);
}
export async function verifyToken(token: string): Promise<JWTPayload | null> {
try {
const { payload } = await jwtVerify(token, JWT_SECRET);
return payload as unknown as JWTPayload;
} catch {
return null;
}
}
// routes/api/auth/login.ts
import { Handlers } from "$fresh/server.ts";
import { signToken } from "../../../utils/auth.ts";
export const handler: Handlers = {
async POST(req) {
const { email, password } = await req.json();
// 验证用户(省略数据库查询细节)
const user = await authenticateUser(email, password);
if (!user) {
return Response.json({ error: "邮箱或密码错误" }, { status: 401 });
}
const token = await signToken({
userId: user.id,
email: user.email,
role: user.role,
});
return Response.json({
token,
user: { id: user.id, name: user.name, email: user.email },
});
},
};
7.2 CSRF 保护
// utils/csrf.ts
import { crypto } from "$std/crypto.ts";
export async function generateCsrfToken(): Promise<string> {
const buffer = new Uint8Array(32);
crypto.getRandomValues(buffer);
return Array.from(buffer).map((b) => b.toString(16).padStart(2, "0")).join(
"",
);
}
export async function verifyCsrfToken(
token: string,
expectedToken: string,
): Promise<boolean> {
// 使用时间安全比较,防止时序攻击
const encoder = new TextEncoder();
const a = encoder.encode(token);
const b = encoder.encode(expectedToken);
if (a.length !== b.length) return false;
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a[i] ^ b[i];
}
return result === 0;
}
7.3 Rate Limiting
// middleware/rateLimit.ts
import { Middleware } from "$fresh/server.ts";
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
const WINDOW_MS = 60_000; // 1 分钟
const MAX_REQUESTS = 100;
export const rateLimitMiddleware: Middleware = {
async handler(req, ctx) {
const ip = req.headers.get("x-forwarded-for") || "unknown";
const now = Date.now();
let record = rateLimitMap.get(ip);
if (!record || now > record.resetTime) {
record = { count: 0, resetTime: now + WINDOW_MS };
rateLimitMap.set(ip, record);
}
record.count++;
if (record.count > MAX_REQUESTS) {
return new Response(JSON.stringify({ error: "请求过于频繁" }), {
status: 429,
headers: {
"Content-Type": "application/json",
"Retry-After": String(Math.ceil((record.resetTime - now) / 1000)),
"X-RateLimit-Limit": String(MAX_REQUESTS),
"X-RateLimit-Remaining": "0",
},
});
}
const resp = await ctx.next();
resp.headers.set("X-RateLimit-Limit", String(MAX_REQUESTS));
resp.headers.set(
"X-RateLimit-Remaining",
String(MAX_REQUESTS - record.count),
);
return resp;
},
};
八、部署:Deno Deploy 与自托管方案
8.1 Deno Deploy(官方托管平台)
Deno Deploy 是 Deno 官方的 serverless 部署平台,基于全球边缘网络:
# 安装 deployctl
deno install -Arf jsr:@deno/deployctl
# 部署
deployctl deploy --project=my-fresh-app main.ts
# 或使用 GitHub Actions 自动部署
GitHub Actions 配置:
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: denoland/setup-deno@v2
with:
deno-version: v2.x
- run: deno task test
- run: deployctl deploy --project=my-fresh-app --prod main.ts
env:
DENO_DEPLOY_TOKEN: ${{ secrets.DENO_DEPLOY_TOKEN }}
8.2 Docker 自托管
# Dockerfile
FROM denoland/deno:2.0
WORKDIR /app
# 复制依赖清单(利用 Docker 缓存层)
COPY deno.json deno.lock ./
RUN deno cache main.ts
# 复制源码
COPY . .
# 构建
RUN deno task build
# 暴露端口
EXPOSE 8000
# 运行
CMD ["deno", "run", "--allow-net", "--allow-read", "--allow-env", "main.ts"]
# 构建并运行
docker build -t my-fresh-app .
docker run -p 8000:8000 \
-e DATABASE_URL=postgres://... \
-e JWT_SECRET=your-secret \
my-fresh-app
8.3 单文件编译部署
Deno 2.0 的 deno compile 可以将整个应用编译为单个可执行文件:
# 编译为当前平台的可执行文件
deno compile --allow-net --allow-read --allow-env \
--include=./static \
main.ts
# 生成 my-app 可执行文件(Linux/macOS/Windows)
./my-app
# 交叉编译
deno compile --target=x86_64-unknown-linux-gnu \
--allow-net --allow-read --allow-env main.ts
单文件部署的优势:
- 无需安装 Deno 运行时
- 无需
node_modules或依赖安装 - 启动速度极快(~50ms)
- 体积小(通常 20-50MB)
九、性能优化深度指南
9.1 Fresh 性能剖析
SSR 渲染优化:
// 使用 Preact 的 memo 避免不必要的重渲染
import { memo } from "preact/compat";
const ExpensiveList = memo(({ items }: { items: string[] }) => {
return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
});
静态页面预渲染(SSG):
// routes/about.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
export const handler: Handlers = {
// precompile 指令告诉 Fresh 在构建时预渲染此页面
async GET(_req, ctx) {
return ctx.render({
title: "关于我们",
content: "这是一篇预渲染的静态页面...",
}, {
status: 200,
headers: {
// 设置较长的缓存时间
"Cache-Control": "public, max-age=86400, s-maxage=86400",
},
});
},
};
9.2 内置任务运行器
{
"tasks": {
"dev": "deno run --allow-net --allow-read --allow-env --watch-dev main.ts",
"build": "deno run -A dev.ts build",
"start": "deno run --allow-net --allow-read --allow-env main.ts",
"test": "deno test --allow-net --allow-read --allow-env --coverage=coverage/",
"bench": "deno bench --allow-net",
"lint": "deno lint && deno fmt --check",
"check": "deno check main.ts"
}
}
# 运行任务
deno task dev # 开发服务器(带 watch)
deno task build # 生产构建
deno task test # 运行测试
deno task bench # 性能基准测试
9.3 内置 LSP(deno lsp)
Deno 2.0 内置了一个强大的语言服务器,VS Code / Neovim / JetBrains 都有官方插件:
// .vscode/settings.json
{
"deno.enable": true,
"deno.lint": true,
"deno.format": true,
"deno.config": "./deno.json"
}
LSP 提供的能力:
- 实时类型检查(TypeScript 原生支持)
- 自动补全(包括 JSR 包和 npm 包)
- 内联错误提示
- 代码重构
- 文档悬浮提示
- Go to Definition(跨 npm/JSR/Deno 模块)
十、实战项目:构建一个带认证的任务管理应用
让我们把所有知识串联起来,构建一个完整的项目。
10.1 项目结构
task-app/
├── deno.json
├── main.ts
├── dev.ts
├── routes/
│ ├── _middleware.ts
│ ├── index.tsx
│ ├── login.tsx
│ ├── dashboard.tsx
│ └── api/
│ ├── auth.ts
│ └── tasks.ts
├── islands/
│ ├── TaskList.tsx
│ ├── TaskForm.tsx
│ └── Timer.tsx
├── components/
│ ├── Layout.tsx
│ └── NavBar.tsx
├── utils/
│ ├── auth.ts
│ ├── db.ts
│ └── csrf.ts
└── static/
└── favicon.ico
10.2 完整的 Dashboard 路由
// routes/dashboard.tsx
import { Handlers, PageProps } from "$fresh/server.ts";
import { Head } from "$fresh/runtime.ts";
import { Layout } from "../components/Layout.tsx";
import TaskList from "../islands/TaskList.tsx";
import TaskForm from "../islands/TaskForm.tsx";
interface Task {
id: string;
title: string;
completed: boolean;
createdAt: string;
}
interface DashboardData {
user: { name: string; email: string };
tasks: Task[];
}
export const handler: Handlers<DashboardData> = {
async GET(req, ctx) {
// 从中间件获取用户信息
const userId = ctx.state.userId;
if (!userId) {
return new Response("", { status: 307, headers: { Location: "/login" } });
}
// 从 Deno KV 获取任务
const kv = await Deno.openKv();
const tasks: Task[] = [];
const entries = kv.list<Task>({ prefix: ["tasks", userId] });
for await (const entry of entries) {
tasks.push({ id: entry.key[2] as string, ...entry.value });
}
return ctx.render({
user: { name: "Alice", email: "alice@example.com" },
tasks: tasks.sort((a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
),
});
},
};
export default function Dashboard({ data }: PageProps<DashboardData>) {
return (
<Layout title="任务面板">
<div class="max-w-4xl mx-auto px-4 py-8">
<h1 class="text-3xl font-bold mb-6">
欢迎回来,{data.user.name}
</h1>
{/* TaskForm 是一个 Island,有表单交互 */}
<TaskForm userId="user-1" />
{/* TaskList 也是一个 Island,有复选框交互 */}
<TaskList tasks={data.tasks} />
{/* 下面的统计数据是纯 HTML,无 JS */}
<div class="mt-8 grid grid-cols-3 gap-4">
<div class="bg-white p-4 rounded shadow">
<p class="text-gray-500">总任务</p>
<p class="text-2xl font-bold">{data.tasks.length}</p>
</div>
<div class="bg-white p-4 rounded shadow">
<p class="text-gray-500">已完成</p>
<p class="text-2xl font-bold text-green-600">
{data.tasks.filter((t) => t.completed).length}
</p>
</div>
<div class="bg-white p-4 rounded shadow">
<p class="text-gray-500">待完成</p>
<p class="text-2xl font-bold text-orange-600">
{data.tasks.filter((t) => !t.completed).length}
</p>
</div>
</div>
</div>
</Layout>
);
}
10.3 TaskList Island 实现
// islands/TaskList.tsx
import { useSignal, effect } from "@preact/signals";
interface Task {
id: string;
title: string;
completed: boolean;
createdAt: string;
}
interface TaskListProps {
tasks: Task[];
}
export default function TaskList({ tasks: initialTasks }: TaskListProps) {
const tasks = useSignal(initialTasks);
const loading = useSignal(false);
const toggleTask = async (id: string) => {
loading.value = true;
try {
const task = tasks.value.find((t) => t.id === id);
if (!task) return;
const resp = await fetch(`/api/tasks/${id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ completed: !task.completed }),
});
if (resp.ok) {
const updated = await resp.json();
tasks.value = tasks.value.map((t) =>
t.id === id ? { ...t, completed: updated.completed } : t
);
}
} finally {
loading.value = false;
}
};
const deleteTask = async (id: string) => {
loading.value = true;
try {
const resp = await fetch(`/api/tasks/${id}`, { method: "DELETE" });
if (resp.ok) {
tasks.value = tasks.value.filter((t) => t.id !== id);
}
} finally {
loading.value = false;
}
};
return (
<div class="mt-6 space-y-3">
{tasks.value.map((task) => (
<div
key={task.id}
class={`flex items-center justify-between p-4 rounded shadow ${
task.completed ? "bg-gray-50" : "bg-white"
}`}
>
<label class="flex items-center gap-3 cursor-pointer">
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
class="w-5 h-5 rounded"
disabled={loading.value}
/>
<span class={task.completed ? "line-through text-gray-400" : ""}>
{task.title}
</span>
</label>
<button
onClick={() => deleteTask(task.id)}
class="text-red-500 hover:text-red-700 text-sm"
disabled={loading.value}
>
删除
</button>
</div>
))}
</div>
);
}
十一、Deno 2.0 vs Node.js vs Bun:2026 年运行时选型指南
| 维度 | Deno 2.0 | Node.js 22 | Bun |
|---|---|---|---|
| 语言支持 | TS 原生 | 需编译/配置 | TS 原生 |
| npm 兼容 | ✅ 完整 | ✅ 原生 | ✅ 完整 |
| 包管理 | JSR + npm | npm/pnpm/yarn | 内置 |
| 权限系统 | ✅ 精细控制 | ❌ 无 | ❌ 无 |
| 内置工具链 | fmt/lint/test/bench/compile | ❌ 需第三方 | fmt/test (部分) |
| 全栈框架 | Fresh(官方) | Next.js/Nuxt 等 | 无官方 |
| 部署平台 | Deno Deploy(全球边缘) | Vercel/AWS 等 | 无官方 |
| 内置 KV | ✅ Deno KV | ❌ | ❌ SQLite |
| HTTP 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 冷启动 | ~35ms | ~80ms | ~15ms |
| 内存占用 | 低 | 中 | 低 |
| 生态成熟度 | 🟡 快速增长 | 🟢 最成熟 | 🟡 快速增长 |
| 企业采用 | 🟡 增长中 | 🟢 广泛 | 🟡 增长中 |
选型建议:
- 新项目 + 重安全:选 Deno 2.0。权限系统和内置工具链省去大量配置工作
- 企业级 + 最大生态:选 Node.js。npm 生态无可匹敌,招聘也容易
- 追求极致性能 + 全栈 JS:选 Bun。最快的 JS 运行时,但生态和稳定性不如前两者
- 边缘部署 + SSR:选 Deno 2.0 + Fresh。Deno Deploy 的全球边缘网络是杀手级优势
十二、总结:Deno 2.0 的时代定位
2026 年的 JavaScript 运行时格局正在发生深刻变化。Deno 2.0 不是一个"挑战者"——它是一个务实的融合者。
它保留了最初的理想(安全、简洁、原生 TS),同时放下了固执(拥抱 npm、兼容 Node.js API)。Fresh 框架的 Islands 架构为全栈开发提供了一个不同于 Next.js/Nuxt 的思路——不是"所有东西都是 React 组件",而是"精确控制什么需要交互、什么只需要 HTML"。
如果你在 2020 年放弃过 Deno,现在是重新审视的时候了。如果你从未用过 Deno,现在是最好的开始时机——因为 Deno 2.0 已经不再需要你做出艰难的选择。
Deno 2.0 不要求你离开 npm 生态,它只是让你的开发体验更安全、更简洁、更现代。
参考资源:
- Deno 官方文档:https://deno.land
- Fresh 框架文档:https://fresh.deno.dev
- JSR 包注册表:https://jsr.io
- Deno Deploy:https://dash.deno.com
- Deno 标准库:https://jsr.io/@std