Files
supabase/apps/docs/content/guides/functions/routing.mdx
T
Vaibhav 549c1fb6ca fix(docs): align edge function docs (#47148)
## TL;DR 
aligns the remaining Phase 2 Edge Functions docs snippets with
`@supabase/server`

## Whats Fixed? 
updated outdated imports and version references, and refreshed JSON
examples to use Response.json()
where it makes sense. left non-JSON responses as is where the
integration or format actually needs them
## Ref: 
- towards COM-269 


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Documentation**
* Updated numerous Edge Function guides and examples to use modern
`npm:`/`jsr:` import specifiers instead of legacy Deno URL imports.
* Standardized success and error responses to return JSON consistently
(using `Response.json()` and equivalent helpers) and added/clarified
appropriate HTTP status codes.
* Improved example error payload shapes in several guides for clearer,
structured failures.
* **Chores**
* Refreshed version ranges in documentation and examples across SDKs and
client libraries.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-22 16:06:12 -05:00

426 lines
11 KiB
Plaintext

---
id: 'function-routing'
title: 'Handling Routing in Functions'
description: 'How to handle custom routing within Edge Functions.'
subtitle: '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](https://expressjs.com/), [Oak](https://oakserver.github.io/oak/), or [Hono](https://hono.dev).
---
## Basic routing example
Here's a basic hello world example using some popular web frameworks:
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="hono"
queryGroup="framework"
>
<TabPanel id="deno" label="Deno">
```ts
import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
if (req.method === 'GET') {
return Response.json({ message: 'Hello World!' })
}
const { name } = await req.json()
if (name) {
return Response.json({ message: `Hello ${name}!` })
}
return Response.json({ message: 'Hello World!' })
}),
}
```
</TabPanel>
<TabPanel id="expressjs" label="Express">
```ts
import express from 'npm:express@^5'
const app = express()
app.use(express.json())
// If you want a payload larger than 100kb, then you can tweak it here:
// app.use( express.json({ limit : "300kb" }));
const port = 3000
app.get('/hello-world', (req, res) => {
res.json({ message: 'Hello World!' })
})
app.post('/hello-world', (req, res) => {
const { name } = req.body
res.json({ message: `Hello ${name}!` })
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
```
</TabPanel>
<TabPanel id="oak" label="Oak">
```ts
import { Application } from 'jsr:@oak/oak@^17/application'
import { Router } from 'jsr:@oak/oak@^17/router'
const router = new Router()
router.get('/hello-world', (ctx) => {
ctx.response.body = { message: 'Hello World!' }
})
router.post('/hello-world', async (ctx) => {
const { name } = await ctx.request.body.json()
ctx.response.body = { message: `Hello ${name}!` }
})
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
app.listen({ port: 3000 })
```
</TabPanel>
<TabPanel id="hono" label="Hono">
```ts
import { Hono } from 'jsr:@hono/hono@^4'
const app = new Hono()
app.post('/hello-world', async (c) => {
const { name } = await c.req.json()
return c.json({ message: `Hello ${name}!` })
})
app.get('/hello-world', (c) => {
return c.json({ message: 'Hello World!' })
})
export 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](/docs/guides/functions/auth).
</TabPanel>
</Tabs>
<Admonition type="caution">
Within Edge Functions, paths should always be prefixed with the function name (in this case `hello-world`).
</Admonition>
---
## 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.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="deno"
queryGroup="framework"
>
<TabPanel id="deno" label="Deno">
```ts
import { withSupabase } from 'npm:@supabase/server@^1'
interface Task {
id: string
name: string
}
let tasks: Task[] = []
const router = new Map<string, (req: Request) => Promise<Response>>()
async function getAllTasks(): Promise<Response> {
return Response.json({ tasks })
}
async function getTask(id: string): Promise<Response> {
const task = tasks.find((t) => t.id === id)
if (task) {
return Response.json({ task })
} else {
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
async function createTask(req: Request): Promise<Response> {
const id = Math.random().toString(36).substring(7)
const task = { id, name: '' }
tasks.push(task)
return Response.json({ task }, { status: 201 })
}
async function updateTask(id: string, req: Request): Promise<Response> {
const index = tasks.findIndex((t) => t.id === id)
if (index !== -1) {
const updates = await req.json()
tasks[index] = { ...tasks[index], ...updates }
return Response.json({ task: tasks[index] })
} else {
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
async function deleteTask(id: string): Promise<Response> {
const index = tasks.findIndex((t) => t.id === id)
if (index !== -1) {
tasks.splice(index, 1)
return Response.json({ message: 'Task deleted successfully' })
} else {
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const url = new URL(req.url)
const method = req.method
// Extract the last part of the path as the command
const command = url.pathname.split('/').pop()
// Assuming the last part of the path is the task ID
const id = command
try {
switch (method) {
case 'GET':
if (id) {
return getTask(id)
} else {
return getAllTasks()
}
case 'POST':
return createTask(req)
case 'PUT':
if (id) {
return updateTask(id, req)
} else {
return Response.json({ error: 'Bad Request' }, { status: 400 })
}
case 'DELETE':
if (id) {
return deleteTask(id)
} else {
return Response.json({ error: 'Bad Request' }, { status: 400 })
}
default:
return Response.json({ error: 'Method Not Allowed' }, { status: 405 })
}
} catch (error) {
return Response.json({ error: `Internal Server Error: ${error}` }, { status: 500 })
}
}),
}
```
</TabPanel>
<TabPanel id="expressjs" label="Express">
```ts
import express from 'npm:express@^5'
const app = express()
app.use(express.json())
app.get('/tasks', async (req, res) => {
// return all tasks
})
app.post('/tasks', async (req, res) => {
// create a task
})
app.get('/tasks/:id', async (req, res) => {
const id = req.params.id
const task = {} // get task
res.json(task)
})
app.patch('/tasks/:id', async (req, res) => {
const id = req.params.id
// modify task
})
app.delete('/tasks/:id', async (req, res) => {
const id = req.params.id
// delete task
})
```
</TabPanel>
<TabPanel id="oak" label="Oak">
```ts
import { Application } from 'jsr:@oak/oak@^17/application'
import { Router } from 'jsr:@oak/oak@^17/router'
const router = new Router()
let tasks: { [id: string]: any } = {}
router
.get('/tasks', (ctx) => {
ctx.response.body = Object.values(tasks)
})
.post('/tasks', async (ctx) => {
const body = ctx.request.body()
const { name } = await body.value
const id = Math.random().toString(36).substring(7)
tasks[id] = { id, name }
ctx.response.body = tasks[id]
})
.get('/tasks/:id', (ctx) => {
const id = ctx.params.id
const task = tasks[id]
if (task) {
ctx.response.body = task
} else {
ctx.response.status = 404
ctx.response.body = 'Task not found'
}
})
.patch('/tasks/:id', async (ctx) => {
const id = ctx.params.id
const body = ctx.request.body()
const updates = await body.value
const task = tasks[id]
if (task) {
tasks[id] = { ...task, ...updates }
ctx.response.body = tasks[id]
} else {
ctx.response.status = 404
ctx.response.body = 'Task not found'
}
})
.delete('/tasks/:id', (ctx) => {
const id = ctx.params.id
if (tasks[id]) {
delete tasks[id]
ctx.response.body = 'Task deleted successfully'
} else {
ctx.response.status = 404
ctx.response.body = 'Task not found'
}
})
const app = new Application()
app.use(router.routes())
app.use(router.allowedMethods())
app.listen({ port: 3000 })
```
</TabPanel>
<TabPanel id="hono" label="Hono">
```ts
import { Hono } from 'jsr:@hono/hono@^4'
// You can set the basePath with Hono
const functionName = 'tasks'
const app = new Hono().basePath(`/${functionName}`)
// /tasks/id
app.get('/:id', async (c) => {
const id = c.req.param('id')
const task = {} // Fetch task by id here
if (task) {
return c.json({ task })
} else {
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
app.patch('/:id', async (c) => {
const id = c.req.param('id')
const body = await c.req.body()
const updates = body.value
const task = {} // Fetch task by id here
if (task) {
Object.assign(task, updates)
return c.json({ task })
} else {
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
app.delete('/:id', async (c) => {
const id = c.req.param('id')
const task = {} // Fetch task by id here
if (task) {
// Delete task
return c.json({ message: 'Task deleted successfully' })
} else {
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
export 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](/docs/guides/functions/auth).
</TabPanel>
</Tabs>
---
{/* supa-mdx-lint-disable Rule001HeadingCase */}
## URL Patterns API
If you prefer not to use a web framework, you can directly use [URL Pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) within your Edge Functions to implement routing.
This works well for small apps with only a couple of routes:
<$CodeSample
path="/edge-functions/supabase/functions/restful-tasks/index.ts"
lines={[[48, -1]]}
meta="restful-tasks/index.ts"
/>