From 549c1fb6cab2c44dd5bfa6f00d2692ba97a56d29 Mon Sep 17 00:00:00 2001 From: Vaibhav <117663341+7ttp@users.noreply.github.com> Date: Tue, 23 Jun 2026 02:36:12 +0530 Subject: [PATCH] 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 ## 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. --- .../content/guides/functions/ai-models.mdx | 2 +- .../guides/functions/background-tasks.mdx | 6 +- .../guides/functions/connect-to-postgres.mdx | 2 +- apps/docs/content/guides/functions/cors.mdx | 6 +- .../amazon-bedrock-image-generator.mdx | 6 +- ...uth-send-email-hook-react-email-resend.mdx | 14 ++-- .../examples/cloudflare-turnstile.mdx | 4 +- .../elevenlabs-generate-speech-stream.mdx | 4 +- .../examples/elevenlabs-transcribe-speech.mdx | 8 +- .../guides/functions/examples/og-image.mdx | 4 +- .../functions/examples/push-notifications.mdx | 2 +- .../functions/examples/semantic-search.mdx | 6 +- .../functions/examples/sentry-monitoring.mdx | 6 +- .../functions/examples/slack-bot-mention.mdx | 4 +- .../functions/examples/upstash-redis.mdx | 2 +- .../guides/functions/kysely-postgres.mdx | 30 ++++---- .../docs/content/guides/functions/routing.mdx | 73 ++++++++++--------- .../content/guides/functions/websockets.mdx | 71 ++++++++++++------ .../supabase/functions/drizzle/index.ts | 4 +- .../functions/postgres-on-the-edge/index.ts | 20 ++--- .../supabase/functions/restful-tasks/index.ts | 2 +- examples/prompts/edge-functions.md | 2 +- 22 files changed, 153 insertions(+), 125 deletions(-) diff --git a/apps/docs/content/guides/functions/ai-models.mdx b/apps/docs/content/guides/functions/ai-models.mdx index b349cfe424f..816c715bfe6 100644 --- a/apps/docs/content/guides/functions/ai-models.mdx +++ b/apps/docs/content/guides/functions/ai-models.mdx @@ -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) => { diff --git a/apps/docs/content/guides/functions/background-tasks.mdx b/apps/docs/content/guides/functions/background-tasks.mdx index adde605ed48..c893c8227b6 100644 --- a/apps/docs/content/guides/functions/background-tasks.mdx +++ b/apps/docs/content/guides/functions/background-tasks.mdx @@ -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 }) }), } ``` diff --git a/apps/docs/content/guides/functions/connect-to-postgres.mdx b/apps/docs/content/guides/functions/connect-to-postgres.mdx index b3df92c5411..34869b62e5a 100644 --- a/apps/docs/content/guides/functions/connect-to-postgres.mdx +++ b/apps/docs/content/guides/functions/connect-to-postgres.mdx @@ -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 }) } }), } diff --git a/apps/docs/content/guides/functions/cors.mdx b/apps/docs/content/guides/functions/cors.mdx index 420aa5a74aa..eb0b2a1ed2f 100644 --- a/apps/docs/content/guides/functions/cors.mdx +++ b/apps/docs/content/guides/functions/cors.mdx @@ -31,10 +31,10 @@ If your function doesn't use `withSupabase`, add the headers yourself. See the [ -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 { diff --git a/apps/docs/content/guides/functions/examples/amazon-bedrock-image-generator.mdx b/apps/docs/content/guides/functions/examples/amazon-bedrock-image-generator.mdx index 04c258cb300..3ab5e0ab092 100644 --- a/apps/docs/content/guides/functions/examples/amazon-bedrock-image-generator.mdx +++ b/apps/docs/content/guides/functions/examples/amazon-bedrock-image-generator.mdx @@ -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) diff --git a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx index 7b10c3140c6..459f7b7ae87 100644 --- a/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx +++ b/apps/docs/content/guides/functions/examples/auth-send-email-hook-react-email-resend.mdx @@ -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 diff --git a/apps/docs/content/guides/functions/examples/cloudflare-turnstile.mdx b/apps/docs/content/guides/functions/examples/cloudflare-turnstile.mdx index f28412b1025..c845dc25ffe 100644 --- a/apps/docs/content/guides/functions/examples/cloudflare-turnstile.mdx +++ b/apps/docs/content/guides/functions/examples/cloudflare-turnstile.mdx @@ -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 }) }), } ``` diff --git a/apps/docs/content/guides/functions/examples/elevenlabs-generate-speech-stream.mdx b/apps/docs/content/guides/functions/examples/elevenlabs-generate-speech-stream.mdx index d6ef95ec477..c013b25d8fb 100644 --- a/apps/docs/content/guides/functions/examples/elevenlabs-generate-speech-stream.mdx +++ b/apps/docs/content/guides/functions/examples/elevenlabs-generate-speech-stream.mdx @@ -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'), diff --git a/apps/docs/content/guides/functions/examples/elevenlabs-transcribe-speech.mdx b/apps/docs/content/guides/functions/examples/elevenlabs-transcribe-speech.mdx index e4913a30afa..8ec31003e50 100644 --- a/apps/docs/content/guides/functions/examples/elevenlabs-transcribe-speech.mdx +++ b/apps/docs/content/guides/functions/examples/elevenlabs-transcribe-speech.mdx @@ -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 diff --git a/apps/docs/content/guides/functions/examples/og-image.mdx b/apps/docs/content/guides/functions/examples/og-image.mdx index 71cef4ad1ee..4a19c24f374 100644 --- a/apps/docs/content/guides/functions/examples/og-image.mdx +++ b/apps/docs/content/guides/functions/examples/og-image.mdx @@ -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( diff --git a/apps/docs/content/guides/functions/examples/push-notifications.mdx b/apps/docs/content/guides/functions/examples/push-notifications.mdx index dc9b26e33d3..f6a80ac2aea 100644 --- a/apps/docs/content/guides/functions/examples/push-notifications.mdx +++ b/apps/docs/content/guides/functions/examples/push-notifications.mdx @@ -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 { diff --git a/apps/docs/content/guides/functions/examples/semantic-search.mdx b/apps/docs/content/guides/functions/examples/semantic-search.mdx index 78d803f4b1d..b278fc12503 100644 --- a/apps/docs/content/guides/functions/examples/semantic-search.mdx +++ b/apps/docs/content/guides/functions/examples/semantic-search.mdx @@ -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 }) diff --git a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx index 9a691e28233..1bdb92f7b96 100644 --- a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx +++ b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx @@ -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 }) } }), } diff --git a/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx b/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx index 9f78d84aedd..1e8b854fdbe 100644 --- a/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx +++ b/apps/docs/content/guides/functions/examples/slack-bot-mention.mdx @@ -27,7 +27,7 @@ set SLACK_TOKEN= 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 }) diff --git a/apps/docs/content/guides/functions/examples/upstash-redis.mdx b/apps/docs/content/guides/functions/examples/upstash-redis.mdx index 630de49aca5..7cd80c8ddaf 100644 --- a/apps/docs/content/guides/functions/examples/upstash-redis.mdx +++ b/apps/docs/content/guides/functions/examples/upstash-redis.mdx @@ -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!`) diff --git a/apps/docs/content/guides/functions/kysely-postgres.mdx b/apps/docs/content/guides/functions/kysely-postgres.mdx index 0d61b084322..9b98eb9b869 100644 --- a/apps/docs/content/guides/functions/kysely-postgres.mdx +++ b/apps/docs/content/guides/functions/kysely-postgres.mdx @@ -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) @@ -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 }) } }), } diff --git a/apps/docs/content/guides/functions/routing.mdx b/apps/docs/content/guides/functions/routing.mdx index b6c084fe134..872f5d708d6 100644 --- a/apps/docs/content/guides/functions/routing.mdx +++ b/apps/docs/content/guides/functions/routing.mdx @@ -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 { ```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, () => { ```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 }) ```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 Promise>() async function getAllTasks(): Promise { - return new Response(JSON.stringify(tasks)) + return Response.json({ tasks }) } async function getTask(id: string): Promise { 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 { 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 { 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 { 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 { ```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) => { ```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 }) ```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 }) } }) diff --git a/apps/docs/content/guides/functions/websockets.mdx b/apps/docs/content/guides/functions/websockets.mdx index 8129b4e2af6..d6a47758e65 100644 --- a/apps/docs/content/guides/functions/websockets.mdx +++ b/apps/docs/content/guides/functions/websockets.mdx @@ -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. ```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 { ```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 { -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). -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" -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`. diff --git a/examples/edge-functions/supabase/functions/drizzle/index.ts b/examples/edge-functions/supabase/functions/drizzle/index.ts index 426347072b0..5c562f5c9ba 100644 --- a/examples/edge-functions/supabase/functions/drizzle/index.ts +++ b/examples/edge-functions/supabase/functions/drizzle/index.ts @@ -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' diff --git a/examples/edge-functions/supabase/functions/postgres-on-the-edge/index.ts b/examples/edge-functions/supabase/functions/postgres-on-the-edge/index.ts index d3d51f0b2e9..5950f81ad1b 100644 --- a/examples/edge-functions/supabase/functions/postgres-on-the-edge/index.ts +++ b/examples/edge-functions/supabase/functions/postgres-on-the-edge/index.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 }) } }, } diff --git a/examples/edge-functions/supabase/functions/restful-tasks/index.ts b/examples/edge-functions/supabase/functions/restful-tasks/index.ts index fac385bf65d..7d0d8288ba6 100644 --- a/examples/edge-functions/supabase/functions/restful-tasks/index.ts +++ b/examples/edge-functions/supabase/functions/restful-tasks/index.ts @@ -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 diff --git a/examples/prompts/edge-functions.md b/examples/prompts/edge-functions.md index 49aa1df01e3..596be06fdba 100644 --- a/examples/prompts/edge-functions.md +++ b/examples/prompts/edge-functions.md @@ -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()