在函数中处理路由
Handle custom routing within Edge Functions.
通常,Edge Function 是用来执行单一操作的(例如,把一条记录写入数据库)。不过,如果你的应用逻辑分成了多个 Edge Function,每个操作的请求可能会显得更慢。
🌐 Usually, an Edge Function is written to perform a single action (e.g. write a record to the database). However, if your app's logic is split into multiple Edge Functions, requests to each action may seem slower.
每个边缘函数在响应请求前都需要启动(称为冷启动)。如果某个操作执行得不太频繁(例如删除记录),那么这个函数很可能会遇到冷启动。
🌐 Each Edge Function needs to be booted before serving a request (known as cold starts). If an action is performed less frequently (e.g. deleting a record), there is a high chance of that function experiencing a cold start.
减少冷启动并提高性能的一种方法是将多个动作合并到一个 Edge Function 中。这样只需要启动一个实例,它就可以处理针对不同动作的多个请求。
🌐 One way to reduce cold starts and increase performance is to combine multiple actions into a single Edge Function. This way only one instance needs to be booted and it can handle multiple requests to different actions.
这让你可以:
🌐 This allows you to:
- 通过把多个操作合并成一个函数来减少冷启动
- 在一个函数中构建完整的 REST API
- 通过为多个端点保持一个实例处于活跃状态来提升性能
例如,我们可以使用一个 Edge Function 来创建一个典型的 CRUD API(创建、读取、更新、删除记录)。
🌐 For example, we can use a single Edge Function to create a typical CRUD API (create, read, update, delete records).
要将多个端点组合到一个 Edge Function 中,你可以使用像 Express、Oak 或 Hono 这样的网络应用框架。
🌐 To combine multiple endpoints into a single Edge Function, you can use web application frameworks such as Express, Oak, or Hono.
基础路由示例 #
🌐 Basic routing example
这里有一个使用一些流行的网页框架的基本 Hello World 示例:
🌐 Here's a basic hello world example using some popular web frameworks:
1import { Hono } from 'jsr:@hono/hono@^4'23const app = new Hono()45app.post('/hello-world', async (c) => {6 const { name } = await c.req.json()7 return c.json({ message: `Hello ${name}!` })8})910app.get('/hello-world', (c) => {11 return c.json({ message: 'Hello World!' })12})1314export default { fetch: app.fetch }要为每个路由添加 Supabase 认证,可以使用来自 npm:@supabase/server@^1/adapters/hono 的 Hono 适配器。查看 保护边缘函数。
🌐 To add Supabase auth per route, use the Hono adapter from npm:@supabase/server@^1/adapters/hono. See Securing Edge Functions.
在 Edge 函数中,路径应始终以函数名称(在本例中为 hello-world)为前缀。
🌐 Within Edge Functions, paths should always be prefixed with the function name (in this case hello-world).
使用路由参数 #
🌐 Using route parameters
你可以使用路由参数来获取特定 URL 段的值(例如 /tasks/:taskId/notes/:noteId)。
🌐 You can use route parameters to capture values at specific URL segments (e.g. /tasks/:taskId/notes/:noteId).
记住,路径必须以函数名为前缀。路由参数只能在函数名前缀之后使用。
🌐 Keep in mind paths must be prefixed by function name. Route parameters can only be used after the function name prefix.
1import { withSupabase } from 'npm:@supabase/server@^1'23interface Task {4 id: string5 name: string6}78let tasks: Task[] = []910const router = new Map<string, (req: Request) => Promise<Response>>()1112async function getAllTasks(): Promise<Response> {13 return Response.json({ tasks })14}1516async function getTask(id: string): Promise<Response> {17 const task = tasks.find((t) => t.id === id)18 if (task) {19 return Response.json({ task })20 } else {21 return Response.json({ error: 'Task not found' }, { status: 404 })22 }23}2425async function createTask(req: Request): Promise<Response> {26 const id = Math.random().toString(36).substring(7)27 const task = { id, name: '' }28 tasks.push(task)29 return Response.json({ task }, { status: 201 })30}3132async function updateTask(id: string, req: Request): Promise<Response> {33 const index = tasks.findIndex((t) => t.id === id)34 if (index !== -1) {35 const updates = await req.json()36 tasks[index] = { ...tasks[index], ...updates }37 return Response.json({ task: tasks[index] })38 } else {39 return Response.json({ error: 'Task not found' }, { status: 404 })40 }41}4243async function deleteTask(id: string): Promise<Response> {44 const index = tasks.findIndex((t) => t.id === id)45 if (index !== -1) {46 tasks.splice(index, 1)47 return Response.json({ message: 'Task deleted successfully' })48 } else {49 return Response.json({ error: 'Task not found' }, { status: 404 })50 }51}5253export default {54 fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {55 const url = new URL(req.url)56 const method = req.method57 // Extract the last part of the path as the command58 const command = url.pathname.split('/').pop()59 // Assuming the last part of the path is the task ID60 const id = command61 try {62 switch (method) {63 case 'GET':64 if (id) {65 return getTask(id)66 } else {67 return getAllTasks()68 }69 case 'POST':70 return createTask(req)71 case 'PUT':72 if (id) {73 return updateTask(id, req)74 } else {75 return Response.json({ error: 'Bad Request' }, { status: 400 })76 }77 case 'DELETE':78 if (id) {79 return deleteTask(id)80 } else {81 return Response.json({ error: 'Bad Request' }, { status: 400 })82 }83 default:84 return Response.json({ error: 'Method Not Allowed' }, { status: 405 })85 }86 } catch (error) {87 return Response.json({ error: `Internal Server Error: ${error}` }, { status: 500 })88 }89 }),90}URL 模式 API #
🌐 URL Patterns API
如果你不想使用网络框架,你可以直接在 Edge Functions 中使用 URL Pattern API 来实现路由。
🌐 If you prefer not to use a web framework, you can directly use URL Pattern API within your Edge Functions to implement routing.
这对于只有几个路由的小型应用效果很好:
🌐 This works well for small apps with only a couple of routes:
1// ...23export default {4 fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {5 const { url, method } = req67 try {8 // ctx.supabase is scoped to the calling user, so your row-level-security9 // (RLS) policies are applied.10 const supabaseClient = ctx.supabase1112 // For more details on URLPattern, check https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API13 const taskPattern = new URLPattern({ pathname: '/restful-tasks/:id' })14 const matchingPath = taskPattern.exec(url)15 const id = matchingPath ? matchingPath.pathname.groups.id : null1617 let task = null18 if (method === 'POST' || method === 'PUT') {19 const body = await req.json()20 task = body.task21 }2223 // call relevant method based on method and id24 switch (true) {25 case id && method === 'GET':26 return getTask(supabaseClient, id as string)27 case id && method === 'PUT':28 return updateTask(supabaseClient, id as string, task)29 case id && method === 'DELETE':30 return deleteTask(supabaseClient, id as string)31 case method === 'POST':32 return createTask(supabaseClient, task)33 case method === 'GET':34 return getAllTasks(supabaseClient)35 default:36 return getAllTasks(supabaseClient)37 }38 } catch (error) {39 console.error(error)4041 return Response.json({ error: error.message }, { status: 400 })42 }43 }),44}