mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
## 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 -->
426 lines
11 KiB
Plaintext
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"
|
|
/>
|