Handling Routing in Functions
Handle custom routing within Edge Functions.
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.
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:
- Reduce cold starts by combining multiple actions into one function
- Build complete REST APIs in a single function
- Improve performance by keeping one instance warm for multiple endpoints
For example, we can use a single Edge Function to create a typical CRUD API (create, read, update, delete records).
To combine multiple endpoints into a single Edge Function, you can use web application frameworks such as Express, Oak, or Hono.
Basic routing example#
Here's a simple hello world example using some popular web frameworks:
1import { Hono } from 'jsr:@hono/hono'23const app = new Hono()45app.post('/hello-world', async (c) => {6 const { name } = await c.req.json()7 return new Response(`Hello ${name}!`)8})910app.get('/hello-world', (c) => {11 return new Response('Hello World!')12})1314export default { fetch: app.fetch }To add Supabase auth per route, use the Hono adapter from npm:@supabase/server@^1/adapters/hono. See Securing Edge Functions.
Within Edge Functions, paths should always be prefixed with the function name (in this case hello-world).
Using route parameters#
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 new Response(JSON.stringify(tasks))14}1516async function getTask(id: string): Promise<Response> {17 const task = tasks.find((t) => t.id === id)18 if (task) {19 return new Response(JSON.stringify(task))20 } else {21 return new Response('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 new Response(JSON.stringify(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 tasks[index] = { ...tasks[index] }36 return new Response(JSON.stringify(tasks[index]))37 } else {38 return new Response('Task not found', { status: 404 })39 }40}4142async function deleteTask(id: string): Promise<Response> {43 const index = tasks.findIndex((t) => t.id === id)44 if (index !== -1) {45 tasks.splice(index, 1)46 return new Response('Task deleted successfully')47 } else {48 return new Response('Task not found', { status: 404 })49 }50}5152export default {53 fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {54 const url = new URL(req.url)55 const method = req.method56 // Extract the last part of the path as the command57 const command = url.pathname.split('/').pop()58 // Assuming the last part of the path is the task ID59 const id = command60 try {61 switch (method) {62 case 'GET':63 if (id) {64 return getTask(id)65 } else {66 return getAllTasks()67 }68 case 'POST':69 return createTask(req)70 case 'PUT':71 if (id) {72 return updateTask(id, req)73 } else {74 return new Response('Bad Request', { status: 400 })75 }76 case 'DELETE':77 if (id) {78 return deleteTask(id)79 } else {80 return new Response('Bad Request', { status: 400 })81 }82 default:83 return new Response('Method Not Allowed', { status: 405 })84 }85 } catch (error) {86 return new Response(`Internal Server Error: ${error}`, { status: 500 })87 }88 }),89}URL Patterns 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}