你写的代码,不用买服务器也能跑遍全球
你写的代码,不用买服务器也能跑遍全球
你写的代码,
不用买服务器也能跑遍全球
边缘函数 · 写给 TypeScript 新手的零门槛入门
一、先别学语法,搞清楚它为啥存在
假设你做了个小程序:用户点一下「生成今日鸡汤」,让他等了 1.2 秒。不是你代码慢,是请求绕了大半个地球。
你的服务器在新加坡,用户在深圳。一次点击的真实路线:
光在路上就花掉大半
谁喊,谁旁边就跑一下
它不是「你的那台服务器」,而是一个共享的、按需现起的运行时池子:没请求不占资源、不花钱;来一个请求就近抓个节点,冷启动几十毫秒,跑完释放。下次再来,可能换另一个节点——所以它无状态,别指望内存里存个变量下次还在。
二、它到底解决了哪三件事
① 距离:让代码贴着用户跑
适合那种每个用户都要跑一遍、逻辑又轻的活:把三四个接口的数据拼成一包返回、A/B 分流、灰度开关、地区与语言初始化、算个签名。
冷启动快 + 离人近,这是数据库和普通后端都平替不了的。
② 脏活:有些事数据库「不该干,也干不了」
| 脏活 | 为啥不能全丢给数据库 |
|---|---|
| 调微信支付 / OpenAI / 发短信邮件 | 要往外发请求,密钥不能露 |
| 会变的业务规则 | SQL 写起来想哭,改一次要迁移 |
| 接 Webhook 回调 | 要验签、要异步、要重试 |
| 给用户返回「加工过」的数据 | 前端不该自己拼,后端又懒得搭 |
边缘函数就是数据库和第三方之间的胶水层:密钥塞进 Supabase 的 Secrets,代码里只管 fetch。
③ 运维:不为一小段逻辑养一台机器
为了「注册完发封邮件」你要干嘛?买机器、装 Node、配 nginx、守护进程、监控、扩容、半夜看告警……
# 就这一句
supabase functions deploy send-welcome-email
完了。没流量 0 成本,涨了它自己扛。
三、一次调用,背后到底发生了什么
这张图从上往下读,就是完整链路:
前端 fetch 一下函数 URL
Supabase 给你的地址,形如 https://项目.functions.supabase.co/hello。记得带 apikey 头,那是门禁。
DNS 把你甩到最近的节点
深圳进华南、纽约进美东。这事你一行代码都不用写,平台自动选。
Deno 沙箱当场跑你的函数
它默认啥也碰不到:不能读硬盘、不能乱联网,想干嘛得显式开权限。密钥从后台 Secrets 运行时注入,代码扒走了也扒不到 sk_live_xxx。
以「调用者本人」的身份查库
函数跑在服务端,但行级权限(RLS)得知道「现在是谁在点」。把前端 Authorization 令牌原样透传过去,RLS 一看:是小明本人,该看的放行。
Response 飞回前端
全程常在一百毫秒内,吐一个 JSON 完事。
四、那个「标配开场白」,逐行拆给你
你迟早会看到这两行。先说个反直觉的:第一行经常可以没有。
// 你常在模板里看到的「两行标配」 import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
其实多半是多余的
Deno.serve(handler) 是运行时内置的全局 API,跟浏览器里的 fetch、console 一样,不用引。
close/onListen)才去引 std 版。只要碰库,必写
Deno 不玩 npm install、没有 node_modules,依赖直接从网址拉。这一个包进来,其实是五合一。
createClient 三个参数,各有各的活
const supabase = createClient( Deno.env.get("SUPABASE_URL")!, // 你项目的地址 Deno.env.get("SUPABASE_ANON_KEY")!, // 公开钥匙,能放前端 { global: { headers: { Authorization: req.headers.get("Authorization")! }, }, } );
| 参数 | 是啥 | 为什么放这 |
|---|---|---|
| URL | 项目地址 | 告诉客户端往哪打请求 |
| ANON_KEY | 公开钥匙 | 负责「进门」,真正的锁是 RLS |
| Authorization 透传 | 用户的登录令牌 | 让查库「装得成那个人」 |
五、一个能直接跑的最小闭环
新建 supabase/functions/hello/index.ts,整段贴进去:
Deno.serve(async (req) => { // 1️⃣ 只接 GET,别的姿势直接轰走 if (req.method !== "GET") { return new Response("只收 GET 哦", { status: 405 }); } // 2️⃣ 抠出 ?name=xxx,没有就默认「路人」 const name = new URL(req.url).searchParams.get("name") ?? "路人"; // 3️⃣ 读密钥:代码里没明文,只在后台 Secrets 配 const apiKey = Deno.env.get("MY_SECRET") ?? "没配置密钥"; // 4️⃣ 拼结果(密钥只露头四位,好习惯) const payload = { msg: `嗨 ${name},你刚 hit 到了边缘函数!`, time: new Date().toISOString(), node: "离你最近的那个节点,平台自动选", keyPreview: apiKey.slice(0, 4) + "****", }; // 5️⃣ 回 JSON + 跨域头(不加浏览器会拦) return new Response(JSON.stringify(payload), { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", }, }); });
| 行 | 人话 |
|---|---|
Deno.serve(...) | 接口入口,来一个请求跑一次里面的函数 |
req.method | 门卫,只放 GET |
new URL(req.url) | 拆 ?name=小明 这种参数 |
Deno.env.get(...) | 从后台注入密钥,前端永远摸不到 |
slice(0,4)+"****" | 就算返回也打码 |
new Response(...) | 标准答法,浏览器那套,零学习成本 |
Access-Control-Allow-Origin | 跨域通行证,本地调试最常卡这 |
跑起来,三步
# 1. 本地起,浏览器访问 localhost:54321/functions/v1/hello?name=小明 supabase functions serve hello# 2. 推到云端,拿到真实 URL
supabase functions deploy hello# 3. 后台 Settings → Edge Functions → Secrets 加一条 MY_SECRET
前端调它,跟你平时 fetch 没有任何区别:
const res = await fetch( "https://你的项目.functions.supabase.co/hello?name=小明", { headers: { apikey: "你的 SUPABASE_ANON_KEY" } } ); const data = await res.json(); console.log(data.msg); // 嗨 小明,你刚 hit 到了边缘函数!
六、别踩这三个坑
Deno.env.get,返回前先打码。Deno.serve,碰数据库加 createClient,剩下的,交给离用户最近的那个节点。