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 -->
This commit is contained in:
Vaibhav authored and GitHub committed 2026-06-22 16:06:12 -05:00
1 parent 171ca026b5
commit 549c1fb6ca
22 files changed
+153 -125

No files matched your search

@@ -358,7 +358,7 @@ Since Llamafile provides an OpenAI API compatible server, you can either use it
```ts
import { withSupabase } from 'npm:@supabase/server@^1'
import OpenAI from 'https://deno.land/x/openai@v4.53.2/mod.ts'
import OpenAI from 'jsr:@openai/openai@^6'
export default {
fetch: withSupabase({ auth: 'publishable' }, async (req, ctx) => {
@@ -27,7 +27,7 @@ EdgeRuntime.waitUntil(asyncLongRunningTask())
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
return new Response(...)
return Response.json({ ok: true })
}),
}
```
@@ -42,7 +42,7 @@ export default {
// Won't block the request, runs in background.
EdgeRuntime.waitUntil(asyncLongRunningTask())
return new Response(...)
return Response.json({ ok: true })
}),
}
```
@@ -62,7 +62,7 @@ addEventListener('beforeunload', (ev) => {
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
return new Response(...)
return Response.json({ ok: true })
}),
}
```
@@ -31,7 +31,7 @@ export default {
return Response.json({ data })
} catch (err) {
return new Response(String(err?.message ?? err), { status: 500 })
return Response.json({ error: String(err?.message ?? err) }, { status: 500 })
}
}),
}
+3 -3
View File
@@ -31,10 +31,10 @@ If your function doesn't use `withSupabase`, add the headers yourself. See the [
</Admonition>
Import `corsHeaders` from `@supabase/supabase-js/cors` to automatically get all required headers:
Import `corsHeaders` from `npm:@supabase/supabase-js@^2/cors` to automatically get all required headers:
```ts index.ts
import { corsHeaders } from '@supabase/supabase-js/cors'
import { corsHeaders } from 'npm:@supabase/supabase-js@^2/cors'
console.log(`Function "browser-with-cors" up and running!`)
@@ -42,7 +42,7 @@ export default {
fetch: async (req) => {
// Handle the CORS preflight request.
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders })
return Response.json({ ok: true }, { headers: corsHeaders })
}
try {
@@ -46,9 +46,9 @@ And add the code to the `index.ts` file:
```ts index.ts
// We need to mock the file system for the AWS SDK to work.
import { prepareVirtualFile } from 'https://deno.land/x/mock_file@v1.1.2/mod.ts'
import { BedrockRuntimeClient, InvokeModelCommand } from 'npm:@aws-sdk/client-bedrock-runtime'
import { BedrockRuntimeClient, InvokeModelCommand } from 'npm:@aws-sdk/client-bedrock-runtime@^3'
import { withSupabase } from 'npm:@supabase/server@^1'
import { decode } from 'npm:base64-arraybuffer'
import { decode } from 'npm:base64-arraybuffer@^1'
console.log('Hello from Amazon Bedrock!')
@@ -108,7 +108,7 @@ export default {
upsert: false,
})
if (!upload) {
return Response.json(uploadError)
return Response.json({ error: uploadError?.message ?? 'Upload failed' }, { status: 500 })
}
const { data } = ctx.supabase.storage.from('images').getPublicUrl(upload.path!)
return Response.json(data)
@@ -34,11 +34,11 @@ supabase functions new send-email
Paste the following code into the `index.ts` file:
```tsx supabase/functions/send-email/index.ts
import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0'
import { renderAsync } from 'npm:@react-email/components@0.0.22'
import { Webhook } from 'npm:standardwebhooks@^1'
import { renderAsync } from 'npm:@react-email/components@^1'
import { withSupabase } from 'npm:@supabase/server@^1'
import React from 'npm:react@18.3.1'
import { Resend } from 'npm:resend@4.0.0'
import React from 'npm:react@^19'
import { Resend } from 'npm:resend@^6'
import { MagicLinkEmail } from './_templates/magic-link.tsx'
@@ -48,7 +48,7 @@ const hookSecret = (Deno.env.get('SEND_EMAIL_HOOK_SECRET') as string).replace('v
export default {
fetch: withSupabase({ auth: 'none' }, async (req) => {
if (req.method !== 'POST') {
return new Response('not allowed', { status: 400 })
return Response.json({ error: 'not allowed' }, { status: 400 })
}
const payload = await req.text()
@@ -124,8 +124,8 @@ import {
Link,
Preview,
Text,
} from 'npm:@react-email/components@0.0.22'
import * as React from 'npm:react@18.3.1'
} from 'npm:@react-email/components@^1'
import * as React from 'npm:react@^19'
interface MagicLinkEmailProps {
supabase_url: string
@@ -54,9 +54,9 @@ export default {
const outcome = await result.json()
console.log(outcome)
if (outcome.success) {
return new Response('success')
return Response.json({ success: true })
}
return new Response('failure')
return Response.json({ success: false })
}),
}
```
@@ -102,8 +102,8 @@ In your newly created `supabase/functions/text-to-speech/index.ts` file, add the
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
import { withSupabase } from 'npm:@supabase/server@^1'
import { ElevenLabsClient } from 'npm:elevenlabs@1.52.0'
import * as hash from 'npm:object-hash'
import { ElevenLabsClient } from 'npm:elevenlabs@^1'
import * as hash from 'npm:object-hash@^3'
const client = new ElevenLabsClient({
apiKey: Deno.env.get('ELEVENLABS_API_KEY'),
@@ -107,13 +107,13 @@ Since Supabase Edge Function uses the [Deno runtime](https://deno.land/), you do
In your newly created `scribe-bot/index.ts` file, add the following code:
```ts supabase/functions/scribe-bot/index.ts
import { Bot, webhookCallback } from 'https://deno.land/x/grammy@v1.34.0/mod.ts'
import { Bot, webhookCallback } from 'npm:grammy@^1'
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
import { withSupabase } from 'npm:@supabase/server@^1'
import type { SupabaseClient } from 'npm:@supabase/supabase-js@2'
import { ElevenLabsClient } from 'npm:elevenlabs@1.50.5'
import type { SupabaseClient } from 'npm:@supabase/supabase-js@^2'
import { ElevenLabsClient } from 'npm:elevenlabs@^1'
console.log(`Function "elevenlabs-scribe-bot" up and running!`)
@@ -235,7 +235,7 @@ export default {
try {
const url = new URL(req.url)
if (url.searchParams.get('secret') !== Deno.env.get('FUNCTION_SECRET')) {
return new Response('not allowed', { status: 405 })
return Response.json({ error: 'not allowed' }, { status: 405 })
}
supabaseAdmin = ctx.supabaseAdmin
@@ -21,8 +21,8 @@ Generate Open Graph images with Deno and Supabase Edge Functions. [View on GitHu
Create a `handler.tsx` file to construct the OG image in React:
```tsx handler.tsx
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
import React from 'https://esm.sh/react@18.2.0'
import { ImageResponse } from 'npm:@vercel/og@^0'
import React from 'npm:react@^19'
export default function handler(req: Request) {
return new ImageResponse(
@@ -164,7 +164,7 @@ Push notifications are an important part of any mobile app. They allow you to se
```ts supabase/functions/push/index.ts
import { withSupabase } from 'npm:@supabase/server@^1'
import { JWT } from 'npm:google-auth-library@9'
import { JWT } from 'npm:google-auth-library@^10'
import serviceAccount from '../service-account.json' with { type: 'json' }
interface Notification {
@@ -62,7 +62,7 @@ export default {
.eq('id', id)
if (error) console.warn(error.message)
return new Response('ok')
return Response.json({ ok: true })
}),
}
```
@@ -112,7 +112,7 @@ const model = new Supabase.ai.Session('gte-small')
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
const { search } = await req.json()
if (!search) return new Response('Please provide a search param!')
if (!search) return Response.json({ error: 'Please provide a search param!' }, { status: 400 })
// Generate embedding for search term.
const embedding = await model.run(search, {
mean_pool: true,
@@ -128,7 +128,7 @@ export default {
.select('content')
.limit(3)
if (error) {
return Response.json(error)
return Response.json({ error: error.message }, { status: 500 })
}
return Response.json({ search, result })
@@ -23,12 +23,12 @@ supabase functions new sentryfied
Handle exceptions within your function and send them to Sentry.
```tsx
import * as Sentry from 'https://deno.land/x/sentry/index.mjs'
import * as Sentry from 'npm:@sentry/deno@^8'
import { withSupabase } from 'npm:@supabase/server@^1'
Sentry.init({
// https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/#where-to-find-your-dsn
dsn: SENTRY_DSN,
dsn: Deno.env.get('SENTRY_DSN'),
defaultIntegrations: false,
// Performance Monitoring
tracesSampleRate: 1.0,
@@ -55,7 +55,7 @@ export default {
Sentry.captureException(e)
// Flush Sentry before the running process closes
await Sentry.flush(2000)
return Response.json({ msg: 'error' }, { status: 500 })
return Response.json({ error: 'Internal Server Error' }, { status: 500 })
}
}),
}
@@ -27,7 +27,7 @@ set SLACK_TOKEN=<xoxb-0000000000-0000000000-01010101010nacho101010>
Here's the code of the Edge Function, you can change the response to handle the text received:
```ts index.ts
import { WebClient } from 'https://deno.land/x/slack_web_api@6.7.2/mod.js'
import { WebClient } from 'npm:@slack/web-api@^7'
import { withSupabase } from 'npm:@supabase/server@^1'
const slackBotToken = Deno.env.get('SLACK_TOKEN') ?? ''
@@ -55,7 +55,7 @@ export default {
text: `Hello <@${user}>!`,
thread_ts: ts,
})
return new Response('ok', { status: 200 })
return Response.json({ ok: true })
}
} catch (error) {
return Response.json({ error: error.message }, { status: 500 })
@@ -39,7 +39,7 @@ supabase functions new upstash-redis-counter
And add the code to the `index.ts` file:
```ts index.ts
import { Redis } from 'https://deno.land/x/upstash_redis@v1.19.3/mod.ts'
import { Redis } from 'npm:@upstash/redis@^1'
import { withSupabase } from 'npm:@supabase/server@^1'
console.log(`Function "upstash-redis-counter" up and running!`)
@@ -34,7 +34,7 @@ GET YOUR CERT FROM YOUR PROJECT DASHBOARD
Create a `DenoPostgresDriver.ts` file to manage the connection to Postgres via [deno-postgres](https://deno-postgres.com/):
```ts DenoPostgresDriver.ts
import { Pool, PoolClient } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'
import { Pool, PoolClient } from 'jsr:@db/postgres@^0'
import {
CompiledQuery,
DatabaseConnection,
@@ -42,9 +42,9 @@ import {
PostgresCursorConstructor,
QueryResult,
TransactionSettings,
} from 'https://esm.sh/kysely@0.23.4'
import { freeze, isFunction } from 'https://esm.sh/kysely@0.23.4/dist/esm/util/object-utils.js'
import { extendStackTrace } from 'https://esm.sh/kysely@0.23.4/dist/esm/util/stack-trace-utils.js'
} from 'npm:kysely@^0'
import { freeze, isFunction } from 'npm:kysely@^0/dist/esm/util/object-utils.js'
import { extendStackTrace } from 'npm:kysely@^0/dist/esm/util/stack-trace-utils.js'
export interface PostgresDialectConfig {
pool: Pool | (() => Promise<Pool>)
@@ -190,14 +190,14 @@ class PostgresConnection implements DatabaseConnection {
Create an `index.ts` file to execute a query on incoming requests:
```ts index.ts
import { Pool } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'
import { Pool } from 'jsr:@db/postgres@^0'
import {
Generated,
Kysely,
PostgresAdapter,
PostgresIntrospector,
PostgresQueryCompiler,
} from 'https://esm.sh/kysely@0.23.4'
} from 'npm:kysely@^0'
import { withSupabase } from 'npm:@supabase/server@^1'
import { PostgresDriver } from './DenoPostgresDriver.ts'
@@ -258,23 +258,23 @@ export default {
// Neat, it's properly typed \o/
console.log(animals[0].created_at.getFullYear())
// Encode the result as pretty printed JSON
const body = JSON.stringify(
animals,
(key, value) => (typeof value === 'bigint' ? value.toString() : value),
2
const data = animals.map((animal) =>
Object.fromEntries(
Object.entries(animal).map(([key, value]) => [
key,
typeof value === 'bigint' ? value.toString() : value,
])
)
)
// Return the response with the correct content type header
return new Response(body, {
status: 200,
return Response.json(data, {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
})
} catch (err) {
console.error(err)
return new Response(String(err?.message ?? err), { status: 500 })
return Response.json({ error: String(err?.message ?? err) }, { status: 500 })
}
}),
}
+37 -36
View File
@@ -44,13 +44,13 @@ import { withSupabase } from 'npm:@supabase/server@^1'
export default {
fetch: withSupabase({ auth: 'user' }, async (req, ctx) => {
if (req.method === 'GET') {
return new Response('Hello World!')
return Response.json({ message: 'Hello World!' })
}
const { name } = await req.json()
if (name) {
return new Response(`Hello ${name}!`)
return Response.json({ message: `Hello ${name}!` })
}
return new Response('Hello World!')
return Response.json({ message: 'Hello World!' })
}),
}
```
@@ -60,7 +60,7 @@ export default {
<TabPanel id="expressjs" label="Express">
```ts
import express from 'npm:express@4.18.2'
import express from 'npm:express@^5'
const app = express()
app.use(express.json())
@@ -70,12 +70,12 @@ app.use(express.json())
const port = 3000
app.get('/hello-world', (req, res) => {
res.send('Hello World!')
res.json({ message: 'Hello World!' })
})
app.post('/hello-world', (req, res) => {
const { name } = req.body
res.send(`Hello ${name}!`)
res.json({ message: `Hello ${name}!` })
})
app.listen(port, () => {
@@ -88,18 +88,18 @@ app.listen(port, () => {
<TabPanel id="oak" label="Oak">
```ts
import { Application } from 'jsr:@oak/oak@15/application'
import { Router } from 'jsr:@oak/oak@15/router'
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 = 'Hello world!'
ctx.response.body = { message: 'Hello World!' }
})
router.post('/hello-world', async (ctx) => {
const { name } = await ctx.request.body.json()
ctx.response.body = `Hello ${name}!`
ctx.response.body = { message: `Hello ${name}!` }
})
const app = new Application()
@@ -114,17 +114,17 @@ app.listen({ port: 3000 })
<TabPanel id="hono" label="Hono">
```ts
import { Hono } from 'jsr:@hono/hono'
import { Hono } from 'jsr:@hono/hono@^4'
const app = new Hono()
app.post('/hello-world', async (c) => {
const { name } = await c.req.json()
return new Response(`Hello ${name}!`)
return c.json({ message: `Hello ${name}!` })
})
app.get('/hello-world', (c) => {
return new Response('Hello World!')
return c.json({ message: 'Hello World!' })
})
export default { fetch: app.fetch }
@@ -173,15 +173,15 @@ let tasks: Task[] = []
const router = new Map<string, (req: Request) => Promise<Response>>()
async function getAllTasks(): Promise<Response> {
return new Response(JSON.stringify(tasks))
return Response.json({ tasks })
}
async function getTask(id: string): Promise<Response> {
const task = tasks.find((t) => t.id === id)
if (task) {
return new Response(JSON.stringify(task))
return Response.json({ task })
} else {
return new Response('Task not found', { status: 404 })
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
@@ -189,16 +189,17 @@ async function createTask(req: Request): Promise<Response> {
const id = Math.random().toString(36).substring(7)
const task = { id, name: '' }
tasks.push(task)
return new Response(JSON.stringify(task), { status: 201 })
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) {
tasks[index] = { ...tasks[index] }
return new Response(JSON.stringify(tasks[index]))
const updates = await req.json()
tasks[index] = { ...tasks[index], ...updates }
return Response.json({ task: tasks[index] })
} else {
return new Response('Task not found', { status: 404 })
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
@@ -206,9 +207,9 @@ async function deleteTask(id: string): Promise<Response> {
const index = tasks.findIndex((t) => t.id === id)
if (index !== -1) {
tasks.splice(index, 1)
return new Response('Task deleted successfully')
return Response.json({ message: 'Task deleted successfully' })
} else {
return new Response('Task not found', { status: 404 })
return Response.json({ error: 'Task not found' }, { status: 404 })
}
}
@@ -234,19 +235,19 @@ export default {
if (id) {
return updateTask(id, req)
} else {
return new Response('Bad Request', { status: 400 })
return Response.json({ error: 'Bad Request' }, { status: 400 })
}
case 'DELETE':
if (id) {
return deleteTask(id)
} else {
return new Response('Bad Request', { status: 400 })
return Response.json({ error: 'Bad Request' }, { status: 400 })
}
default:
return new Response('Method Not Allowed', { status: 405 })
return Response.json({ error: 'Method Not Allowed' }, { status: 405 })
}
} catch (error) {
return new Response(`Internal Server Error: ${error}`, { status: 500 })
return Response.json({ error: `Internal Server Error: ${error}` }, { status: 500 })
}
}),
}
@@ -257,7 +258,7 @@ export default {
<TabPanel id="expressjs" label="Express">
```ts
import express from 'npm:express@4.18.2'
import express from 'npm:express@^5'
const app = express()
app.use(express.json())
@@ -293,8 +294,8 @@ app.delete('/tasks/:id', async (req, res) => {
<TabPanel id="oak" label="Oak">
```ts
import { Application } from 'jsr:@oak/oak/application'
import { Router } from 'jsr:@oak/oak/router'
import { Application } from 'jsr:@oak/oak@^17/application'
import { Router } from 'jsr:@oak/oak@^17/router'
const router = new Router()
@@ -357,7 +358,7 @@ app.listen({ port: 3000 })
<TabPanel id="hono" label="Hono">
```ts
import { Hono } from 'jsr:@hono/hono'
import { Hono } from 'jsr:@hono/hono@^4'
// You can set the basePath with Hono
const functionName = 'tasks'
@@ -368,9 +369,9 @@ app.get('/:id', async (c) => {
const id = c.req.param('id')
const task = {} // Fetch task by id here
if (task) {
return new Response(JSON.stringify(task))
return c.json({ task })
} else {
return new Response('Task not found', { status: 404 })
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
@@ -381,9 +382,9 @@ app.patch('/:id', async (c) => {
const task = {} // Fetch task by id here
if (task) {
Object.assign(task, updates)
return new Response(JSON.stringify(task))
return c.json({ task })
} else {
return new Response('Task not found', { status: 404 })
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
@@ -392,9 +393,9 @@ app.delete('/:id', async (c) => {
const task = {} // Fetch task by id here
if (task) {
// Delete task
return new Response('Task deleted successfully')
return c.json({ message: 'Task deleted successfully' })
} else {
return new Response('Task not found', { status: 404 })
return c.json({ error: 'Task not found' }, { status: 404 })
}
})
@@ -5,7 +5,8 @@ description: 'How to handle WebSocket connections in Edge Functions'
subtitle: 'Handle WebSocket connections in Edge Functions.'
---
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional communications with browser clients.
Edge Functions supports hosting WebSocket servers that can facilitate bi-directional
communications with browser clients.
This allows you to:
@@ -13,7 +14,8 @@ This allows you to:
- Create WebSocket relay servers for external APIs
- Establish both incoming and outgoing WebSocket connections
For a production-ready reconnect pattern with session persistence and replay, see [Resumable WebSockets with Edge Functions](/docs/guides/functions/examples/resumable-websockets).
For a production-ready reconnect pattern with session persistence and replay, see
[Resumable WebSockets with Edge Functions](/docs/guides/functions/examples/resumable-websockets).
---
@@ -36,7 +38,10 @@ export default {
const upgrade = req.headers.get('upgrade') || ''
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
return Response.json(
{ error: "request isn't trying to upgrade to WebSocket." },
{ status: 400 }
)
}
const { socket, response } = Deno.upgradeWebSocket(req)
@@ -61,7 +66,7 @@ export default {
```ts
import { createServer } from 'node:http'
import { WebSocketServer } from 'npm:ws'
import { WebSocketServer } from 'npm:ws@^8'
const server = createServer()
// Since we manually created the HTTP server,
@@ -105,7 +110,9 @@ server.listen(8080)
You can also establish an outbound WebSocket connection to another server from an Edge Function.
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server) for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
Combining it with incoming WebSocket servers, it's possible to use Edge Functions as a
WebSocket proxy, for example as a [relay server](https://github.com/supabase-community/openai-realtime-console?tab=readme-ov-file#using-supabase-edge-functions-as-a-relay-server)
for the [OpenAI Realtime API](https://platform.openai.com/docs/guides/realtime/overview).
<$CodeSample
external={true}
@@ -121,11 +128,17 @@ lines={[[1, 3], [5, -1]]}
## Authentication
WebSocket browser clients don't have the option to send custom headers. Because of this, Edge Functions won't be able to perform the usual authorization header check to verify the JWT.
WebSocket browser clients don't have the option to send custom headers. Because of this,
Edge Functions won't be able to perform the usual authorization header check to verify
the JWT.
You can skip the default authorization header checks by explicitly providing `--no-verify-jwt` when serving and deploying functions.
You can skip the default authorization header checks by explicitly providing
`--no-verify-jwt` when serving and deploying functions.
To authenticate the user making WebSocket requests, you can pass the JWT in URL query params or via a custom protocol. The [`withSupabase`](/docs/guides/functions/auth) wrapper validates credentials on request headers, so it can't authenticate WebSocket clients. Verify the JWT yourself, as shown below.
To authenticate the user making WebSocket requests, you can pass the JWT in URL query
params or via a custom protocol. The [`withSupabase`](/docs/guides/functions/auth)
wrapper validates credentials on request headers, so it can't authenticate WebSocket
clients. Verify the JWT yourself, as shown below.
<Tabs
scrollable
@@ -137,7 +150,7 @@ To authenticate the user making WebSocket requests, you can pass the JWT in URL
<TabPanel id="query" label="Using query params">
```ts
import { createClient } from 'npm:@supabase/supabase-js@2'
import { createClient } from 'npm:@supabase/supabase-js@^2'
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
@@ -150,7 +163,10 @@ export default {
fetch: async (req) => {
const upgrade = req.headers.get('upgrade') || ''
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
return Response.json(
{ error: "request isn't trying to upgrade to WebSocket." },
{ status: 400 }
)
}
// Please be aware query params may be logged in some logging systems.
@@ -159,19 +175,19 @@ export default {
if (!jwt) {
console.error('Auth token not provided')
return new Response('Auth token not provided', { status: 403 })
return Response.json({ error: 'Auth token not provided' }, { status: 403 })
}
const { error, data } = await supabase.auth.getUser(jwt)
if (error) {
console.error(error)
return new Response('Invalid token provided', { status: 403 })
return Response.json({ error: 'Invalid token provided' }, { status: 403 })
}
if (!data.user) {
console.error('user is not authenticated')
return new Response('User is not authenticated', { status: 403 })
return Response.json({ error: 'User is not authenticated' }, { status: 403 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
@@ -194,7 +210,7 @@ export default {
<TabPanel id="protocol" label="Using custom protocol">
```ts
import { createClient } from 'npm:@supabase/supabase-js@2'
import { createClient } from 'npm:@supabase/supabase-js@^2'
const SUPABASE_SECRET_KEYS = JSON.parse(Deno.env.get('SUPABASE_SECRET_KEYS')!)
const supabase = createClient(
@@ -207,7 +223,10 @@ export default {
fetch: async (req) => {
const upgrade = req.headers.get('upgrade') || ''
if (upgrade.toLowerCase() != 'websocket') {
return new Response("request isn't trying to upgrade to WebSocket.", { status: 400 })
return Response.json(
{ error: "request isn't trying to upgrade to WebSocket." },
{ status: 400 }
)
}
// Sec-WebScoket-Protocol may return multiple protocol values `jwt-TOKEN, value1, value 2`
@@ -219,18 +238,18 @@ export default {
if (!jwt) {
console.error('Auth token not provided')
return new Response('Auth token not provided', { status: 403 })
return Response.json({ error: 'Auth token not provided' }, { status: 403 })
}
const { error, data } = await supabase.auth.getUser(jwt)
if (error) {
console.error(error)
return new Response('Invalid token provided', { status: 403 })
return Response.json({ error: 'Invalid token provided' }, { status: 403 })
}
if (!data.user) {
console.error('user is not authenticated')
return new Response('User is not authenticated', { status: 403 })
return Response.json({ error: 'User is not authenticated' }, { status: 403 })
}
const { socket, response } = Deno.upgradeWebSocket(req)
@@ -254,17 +273,24 @@ export default {
<Admonition type="caution">
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The Function will shutdown when it reaches one of these [limits](/docs/guides/functions/limits).
The maximum duration is capped based on the wall-clock, CPU, and memory limits. The
Function will shutdown when it reaches one of these
[limits](/docs/guides/functions/limits).
</Admonition>
When using WebSockets, keep in mind that the HTTP request is considered complete after `Deno.upgradeWebSocket(req)` returns the response. To prevent early worker retirement while the socket is still open, keep an unresolved `EdgeRuntime.waitUntil()` promise that resolves in `socket.onclose`.
When using WebSockets, keep in mind that the HTTP request is considered complete after
`Deno.upgradeWebSocket(req)` returns the response. To prevent early worker retirement
while the socket is still open, keep an unresolved `EdgeRuntime.waitUntil()` promise
that resolves in `socket.onclose`.
---
## Testing WebSockets locally
When testing Edge Functions locally with Supabase CLI, the instances are terminated automatically after a request is completed. This will prevent keeping WebSocket connections open.
When testing Edge Functions locally with Supabase CLI, the instances are terminated
automatically after a request is completed. This will prevent keeping WebSocket
connections open.
To prevent that, you can update the `supabase/config.toml` with the following settings:
@@ -275,6 +301,7 @@ policy = "per_worker"
<Admonition type="caution">
When running with `per_worker` policy, Function won't auto-reload on edits. You will need to manually restart it by running `supabase functions serve`.
When running with `per_worker` policy, Function won't auto-reload on edits. You will
need to manually restart it by running `supabase functions serve`.
</Admonition>
@@ -1,5 +1,5 @@
import { drizzle } from 'drizzle-orm/postgres-js'
import postgres from 'postgres'
import { drizzle } from 'npm:drizzle-orm@^0/postgres-js'
import postgres from 'npm:postgres@^3'
import { countries } from '../_shared/schema.ts'
@@ -1,4 +1,4 @@
import { Pool } from 'https://deno.land/x/postgres@v0.17.0/mod.ts'
import { Pool } from 'jsr:@db/postgres@^0'
// Create a database pool with one connection.
const pool = new Pool(
@@ -24,16 +24,16 @@ export default {
const result = await connection.queryObject`SELECT * FROM animals`
const animals = result.rows // [{ id: 1, name: "Lion" }, ...]
// Encode the result as pretty printed JSON
const body = JSON.stringify(
animals,
(_key, value) => (typeof value === 'bigint' ? value.toString() : value),
2
const data = animals.map((animal) =>
Object.fromEntries(
Object.entries(animal).map(([key, value]) => [
key,
typeof value === 'bigint' ? value.toString() : value,
])
)
)
// Return the response with the correct content type header
return new Response(body, {
status: 200,
return Response.json(data, {
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
@@ -44,7 +44,7 @@ export default {
}
} catch (err) {
console.error(err)
return new Response(String(err?.message ?? err), { status: 500 })
return Response.json({ error: String(err?.message ?? err) }, { status: 500 })
}
},
}
@@ -3,7 +3,7 @@
// This enables autocomplete, go to definition, etc.
import { withSupabase } from 'npm:@supabase/server@^1'
import type { SupabaseClient } from 'npm:@supabase/supabase-js@2'
import type { SupabaseClient } from 'npm:@supabase/supabase-js@^2'
interface Task {
name: string
+1 -1
View File
@@ -142,7 +142,7 @@ server.listen(9999)
### Using npm packages in Functions
```ts
import express from 'npm:express@4.18.2'
import express from 'npm:express@^5'
const app = express()