Design wechseln

Supabase Edge Functions in der Praxis: Deno-Runtime und TypeScript-Entwicklungsleitfaden

Easton editorial illustration: hardened server operations console

Das Handy vibriert ununterbrochen. Stripe-Webhooks melden 500-Fehler in Produktion – die Zahlung ist durch, die Bestellung wurde aber nicht angelegt.

Beim Log-Check zeigt sich das Problem: Die bisherige Serverless-Funktion hatte zu lange Cold-Start-Zeiten, Stripe hat abgebrochen. Noch ärgerlicher: Für Signaturprüfung und CORS musste zusätzlich ein API-Gateway aufgesetzt werden …

Seitdem habe ich mich intensiv mit Supabase Edge Functions beschäftigt. Beim Begriff „Deno-Runtime“ war ich zunächst skeptisch – nach Jahren mit Node.js bedeutet ein Runtime-Wechsel neue APIs. Aber Edge Functions folgen einem anderen Ansatz: kein „Migration“, sondern eine leichtere Option für Szenarien ohne schwere Abhängigkeiten.

Dieser Artikel teilt Fallstricke und Erkenntnisse: Architekturprinzip, Unterschiede Deno vs. Node.js, lokaler Entwicklungs- und Debug-Workflow sowie praktische Erfahrungen mit dem Hono-Framework für APIs.

Was sind Edge Functions – Architektur und Technologieauswahl

Zuerst klären wir, was Edge Functions sind und warum Supabase Deno statt Node.js gewählt hat.

Edge-Ausführung, nicht Cloud-Hosting

Edge Functions sind TypeScript-Funktionen an Edge-Knoten. Anders als klassische Lambda- oder Vercel Functions werden sie nicht in wenigen großen Regionen zentral betrieben, sondern auf Hunderten Edge-Knoten weltweit verteilt.

Was bedeutet das? Ein Nutzer in Shanghai sendet einen Request – die Funktion läuft möglicherweise am Edge-Knoten in Tokio, Latenz sinkt von Hunderten auf Dutzende Millisekunden.

Edge hat aber Grenzen – Funktionen dürfen nicht zu schwer sein. Jede Funktion läuft isoliert in einem V8-Isolate mit eigenem Memory-Heap und Ausführungsthread, Start im Millisekundenbereich, aber begrenztem Speicher und Laufzeit. Geeignet für kurzlebige Operationen: Webhook-Verarbeitung, OG-Bilder, Drittanbieter-API-Aufrufe, E-Mail-Versand.

Weniger geeignet: lang laufende Tasks, Bibliotheken mit schweren Node.js-Nativmodulen, Dateisystem-Zugriff.

Warum Deno

In Supabase GitHub Discussions finden sich dazu etwa folgende Gründe:

  1. Schneller Start: Deno packt Code im ESZip-Format – Cold Start 0–5 ms; Node.js-Lambda liegt typisch bei 100–500 ms.
  2. Sicherheitsmodell: Deno deaktiviert standardmäßig Dateisystem- und Netzwerkzugriff – explizite Freigabe nötig. In Multi-Tenant-Edge-Umgebungen wichtig.
  3. Native TypeScript-Unterstützung: Kein tsconfig, kein ts-node – .ts-Dateien laufen direkt.
  4. Portabilität: Deno lässt sich einbetten. Supabase nutzt den eigenen Fork deno_core, optimiert für Embedded-Szenarien.

Es gibt Trade-offs. Das Deno-Ökosystem ist kleiner als Node.js; manche npm-Pakete funktionieren nicht direkt. Mit npm specifiers (import { xxx } from 'npm:lodash') ist die Kompatibilität deutlich besser geworden.

Architektur im Überblick

Ungefährer Ablauf eines Requests:

Client → CDN/Edge-Gateway → JWT-Verifizierung → V8-Isolate führt Funktion aus → Antwort

Wichtig: die JWT-Verifizierung – Edge Functions prüfen standardmäßig den Authorization-Header. Für öffentlichen Zugriff beim Deploy --no-verify-jwt setzen.


Entwicklungsumgebung und CLI-Befehle

Konzept erklärt – jetzt praktisch.

Supabase CLI installieren

Unter macOS per Homebrew:

brew install supabase/tap/supabase

Für Linux und Windows gibt es entsprechende Installationswege in der offiziellen Dokumentation.

Danach anmelden:

supabase login

Der Browser öffnet sich zur Autorisierung des CLI-Zugriffs auf Ihr Supabase-Konto.

Projekt initialisieren

Im Projektverzeichnis:

supabase init

Das erstellt ein supabase/-Verzeichnis mit config.toml und ggf. automatisch angelegtem functions/-Unterverzeichnis.

Erste Edge Function anlegen

supabase functions new hello-world

Der Befehl legt supabase/functions/hello-world/ mit index.ts an:

Deno.serve(async (req: Request) => {
  const { name } = await req.json()
  const data = {
    message: `Hello ${name}!`,
  }

  return new Response(JSON.stringify(data), {
    headers: {
      'Content-Type': 'application/json',
      'Connection': 'keep-alive',
    },
  })
})

So einfach. Deno.serve() ist die native Deno-API; Request und Response sind Standard-Web-APIs wie beim Browser-fetch.

Lokaler Entwicklungsserver

supabase functions serve --env-file supabase/.env.local

Startet einen lokalen Server unter http://localhost:54321. Die Funktion ist erreichbar unter http://localhost:54321/functions/v1/hello-world.

Beim ersten Mal habe ich vergessen, zuerst den lokalen Supabase-Stack (inkl. PostgreSQL) zu starten. Richtig:

# Zuerst lokalen Supabase-Stack starten
supabase start

# Dann Funktionsservice
supabase functions serve

Test-Request

Mit curl oder HTTPie:

curl -i --location --request POST 'http://localhost:54321/functions/v1/hello-world' \
  --header 'Authorization: Bearer <your-anon-key>' \
  --header 'Content-Type: application/json' \
  --data '{"name":"World"}'

Antwort:

{
  "message": "Hello World!"
}

Erfolg.

Hot Reload ist standardmäßig aktiv – Code speichern, sofort wirksam, kein Neustart nötig.

Umgebungsvariablen

Sensible Daten nicht im Code. Supabase unterstützt .env-Dateien:

# .env-Datei anlegen
echo "MY_SECRET=super_secret_value" > supabase/.env.local

# In der Funktion lesen
const mySecret = Deno.env.get('MY_SECRET')

In Produktion per supabase secrets set:

supabase secrets set MY_SECRET=super_secret_value

Praxis: RESTful API mit Hono

Native Deno.serve() reicht – bei komplexer Logik mit Routing, Middleware und Validierung wird Handarbeit mühsam.

Hier hilft Hono.

Was ist Hono

Hono ist ein ultraleichtes Web-Framework für Edge-Runtimes. Es läuft auf Deno, Cloudflare Workers, Bun und mehr, mit starker Routing-Performance und exzellenter TypeScript-Unterstützung.

Offiziell: „small, simple, and ultrafast“ – stimmt in der Praxis.

Integration in Edge Functions

Neue Funktion anlegen:

supabase functions new user-api

index.ts anpassen:

import { Hono } from 'jsr:@hono/hono'
import { cors } from 'jsr:@hono/hono/cors'
import { logger } from 'jsr:@hono/hono/logger'

const app = new Hono().basePath('/api')

// Middleware
app.use('*', cors())
app.use('*', logger())

// Routen
app.get('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ user: { id, name: 'Demo User', email: '[email protected]' } })
})

app.post('/users', async (c) => {
  const body = await c.req.json<{ name: string; email: string }>()
  // Hier Supabase-Datenbank anbinden
  return c.json({ created: body }, 201)
})

app.put('/users/:id', async (c) => {
  const id = c.req.param('id')
  const body = await c.req.json<{ name?: string; email?: string }>()
  return c.json({ updated: { id, ...body } })
})

app.delete('/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ deleted: id })
})

// Service starten
Deno.serve(app.fetch)

Wichtige Punkte:

  1. jsr:@hono/hono ist das JSR-Paketformat von Deno, nicht npm.
  2. basePath('/api') setzt das Routen-Präfix auf /api.
  3. c ist Hono’s Context-Objekt mit Request, Response und Hilfsmethoden.
  4. c.json() setzt automatisch den Content-Type-Header und behandelt null/undefined.

Supabase-Datenbank anbinden

Hono ist nur das Web-Framework – für die Datenbank den Supabase-Client:

import { Hono } from 'jsr:@hono/hono'
import { createClient } from 'jsr:@supabase/supabase-js@2'

const app = new Hono().basePath('/api')

// Supabase-Client initialisieren
const supabaseUrl = Deno.env.get('SUPABASE_URL')!
const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!

const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    autoRefreshToken: false,
    persistSession: false,
  },
})

// GET /api/users - Liste
app.get('/users', async (c) => {
  const { data, error } = await supabase
    .from('users')
    .select('id, name, email, created_at')

  if (error) {
    return c.json({ error: error.message }, 500)
  }
  return c.json({ users: data })
})

// POST /api/users - Erstellen
app.post('/users', async (c) => {
  const body = await c.req.json<{ name: string; email: string }>()

  const { data, error } = await supabase
    .from('users')
    .insert(body)
    .select()
    .single()

  if (error) {
    return c.json({ error: error.message }, 400)
  }
  return c.json({ user: data }, 201)
})

Deno.serve(app.fetch)

Hier wird SUPABASE_SERVICE_ROLE_KEY genutzt – volle DB-Berechtigung, umgeht RLS. In Produktion vorsichtig einsetzen.

Fehlerbehandlung und Validierung

Hono hat keinen eingebauten Validator, aber Zod passt gut:

import { z } from 'npm:zod'
import { zValidator } from 'jsr:@hono/zod-validator'

const userSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
})

app.post(
  '/users',
  zValidator('json', userSchema),
  async (c) => {
    const validated = c.req.valid('json')
    // validated ist bereits typsicher
    return c.json({ received: validated })
  }
)

Bei Validierungsfehlern antwortet Hono automatisch mit 400 und detaillierter Fehlermeldung.


Deployment und Best Practices in Produktion

Lokal läuft es – als Nächstes Produktion.

Deploy-Befehl

supabase functions deploy user-api

Beim ersten Deploy wählen Sie das Supabase-Projekt. Danach werden Code-Upload, Build und Deployment automatisch ausgeführt.

Die Funktions-URL:

https://[PROJECT_ID].supabase.co/functions/v1/user-api

Umgebungsvariablen und Secrets

In Produktion separat setzen:

supabase secrets set SUPABASE_URL=https://xxx.supabase.co
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=eyJxxx...

Secrets werden verschlüsselt gespeichert; zur Laufzeit per Deno.env.get() lesbar.

JWT-Verifizierungsstrategie

Edge Functions verifizieren standardmäßig JWT:

  • Nur Requests mit gültigem Authorization: Bearer <token> kommen durch
  • Nutzerinformationen aus dem Token lassen sich aus req.headers auslesen

Für öffentliche APIs (z. B. Drittanbieter-Webhooks) mit --no-verify-jwt deployen:

supabase functions deploy user-api --no-verify-jwt

Dann kann jeder die Funktion aufrufen – Validierung muss im Code erfolgen.

Cold-Start-Latenz reduzieren

Deno startet schnell, trotzdem lohnt sich Optimierung:

  1. Abhängigkeitsvolumen reduzieren: Deno-/JSR-Pakete bevorzugen, npm sparsam
  2. Lazy Loading: große Module per import() bei Bedarf
  3. Funktionen schlank halten: eine Funktion, eine Aufgabe – nicht das ganze Backend hineinpacken

Supabase empfiehlt maximal 2 Sekunden Ausführungszeit und 0–5 ms Cold Start. Diese Tipps helfen bei schnelleren Antworten:

Monitoring und Logs

Im Dashboard sehen Sie Aufruf-Logs und Fehlerberichte. Integration mit Sentry oder anderen Monitoring-Diensten ist möglich.

Mit EdgeRuntime.waitUntil() kann die Funktion nach der Antwort Hintergrundtasks ausführen:

EdgeRuntime.waitUntil(
  fetch('https://analytics.example.com/track', { method: 'POST', body: '...' })
)

return new Response('OK')

Der Client muss nicht auf Hintergrundtasks warten.


Fazit

Wofür eignen sich Edge Functions?

Geeignet:

  • Webhook-Verarbeitung (Stripe, GitHub, Slack)
  • OG-Bilder generieren
  • KI-Inferenz (LLM-API-Aufrufe)
  • E-Mail- und Nachrichtenbenachrichtigungen
  • Kurzlebige Datenverarbeitung

Weniger geeignet:

  • Lang laufende Tasks (z. B. Video-Transcoding)
  • Bibliotheken mit schweren Node.js-Nativmodulen
  • Dateisystem-Zugriff

Wer bereits Supabase-Datenbank und Auth nutzt, erweitert damit natürlich – kein extra Server, kein Ops-Aufwand, direkt Business-Logik schreiben.

Im Vergleich zu Cloudflare Workers oder Vercel Functions: Cloudflare Workers ist reifer mit größerem Ökosystem, Vercel Functions tiefer in Next.js integriert. Mit Supabase ist die Integration am besten – DB-Client, Auth und Storage sind sofort verfügbar.

Zum Einstieg empfiehlt sich die offizielle Beispielsammlung: github.com/supabase/supabase/tree/master/examples/edge-functions

Fragen gerne in den Kommentaren – oder im Supabase Discord in der Community.

Supabase Edge Functions: Entwicklung und Deployment

Vollständige Anleitung von der Umgebung bis zum Produktions-Deployment

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Supabase CLI installieren und anmelden

    CLI per Homebrew installieren (macOS):

    ```bash
    brew install supabase/tap/supabase
    supabase login
    ```

    Nach dem Login öffnet sich der Browser zur Autorisierung des CLI-Zugriffs auf Ihr Supabase-Konto.
  2. 2

    Step 2: Projekt initialisieren und Funktion anlegen

    Im Projektverzeichnis initialisieren und die erste Funktion erstellen:

    ```bash
    supabase init
    supabase functions new hello-world
    ```

    Das erstellt eine Funktionsvorlage unter `supabase/functions/`.
  3. 3

    Step 3: Lokale Entwicklungsumgebung starten

    Zuerst den lokalen Supabase-Stack (inkl. PostgreSQL) starten, dann den Funktionsservice:

    ```bash
    supabase start
    supabase functions serve --env-file supabase/.env.local
    ```

    Lokale Funktions-URL: `http://localhost:54321/functions/v1/{function-name}`
  4. 4

    Step 4: API mit Hono-Framework bauen

    Hono installieren und RESTful API erstellen:

    ```typescript
    import { Hono } from 'jsr:@hono/hono'
    import { cors } from 'jsr:@hono/hono/cors'

    const app = new Hono().basePath('/api')
    app.use('*', cors())
    app.get('/users/:id', (c) => {
    return c.json({ user: { id: c.req.param('id') } })
    })
    Deno.serve(app.fetch)
    ```

    Hono unterstützt Routing, Middleware und Parameter-Validierung.
  5. 5

    Step 5: Umgebungsvariablen und Secrets konfigurieren

    Lokal `.env`-Datei, in Produktion Secrets:

    ```bash
    # Lokal
    echo "MY_SECRET=value" > supabase/.env.local

    # Produktion
    supabase secrets set MY_SECRET=value
    ```

    In der Funktion per `Deno.env.get('MY_SECRET')` lesen.
  6. 6

    Step 6: In Produktion deployen

    Funktion deployen und bei Bedarf öffentlich zugänglich machen:

    ```bash
    # Standard-Deployment (JWT-Verifizierung erforderlich)
    supabase functions deploy user-api

    # Öffentliche API (ohne JWT-Verifizierung)
    supabase functions deploy user-api --no-verify-jwt
    ```

    Produktions-URL: `https://[PROJECT_ID].supabase.co/functions/v1/user-api`

FAQ

Was ist der Unterschied zwischen Supabase Edge Functions und Cloudflare Workers?
Beide sind Edge-Computing-Plattformen, unterscheiden sich aber in einigen Punkten: (1) Edge Functions sind tief in Supabase-Datenbank, Auth und Storage integriert – out of the box; (2) Deno-Runtime vs. V8-Engine – Edge Functions unterstützen npm specifiers und JSR; (3) Cold Start beider Plattformen liegt im Millisekundenbereich, die Performance ist vergleichbar. Wer bereits Supabase nutzt, profitiert von der besseren Integration.
Eignen sich Edge Functions für Webhook-Verarbeitung?
Sehr gut. Webhook-Verarbeitung ist ein typisches Szenario: (1) schneller Cold Start – Stripe-, GitHub- und andere Webhooks laufen nicht in Timeouts; (2) Signaturprüfung, Business-Logik und Schreiben in die Supabase-Datenbank direkt möglich; (3) mit --no-verify-jwt als öffentliche API deployen.
Wie unterscheidet sich Paketmanagement bei Deno und Node.js?
Deno nutzt JSR (offizielles Deno-Paketrepository) und npm specifiers: (1) JSR-Pakete werden mit `jsr:@hono/hono` importiert; (2) npm-Pakete mit `npm:zod`; (3) kein package.json und node_modules nötig – Abhängigkeiten werden automatisch verwaltet. Die meisten gängigen Bibliotheken werden unterstützt.
Wie verbindet man sich in Edge Functions mit der Supabase-Datenbank?
Mit dem @supabase/supabase-js-Client: (1) `jsr:@supabase/supabase-js@2` importieren; (2) SUPABASE_URL und SUPABASE_SERVICE_ROLE_KEY aus Umgebungsvariablen lesen; (3) beim Client auth.autoRefreshToken: false setzen (in Edge-Umgebungen kein Refresh nötig); (4) SERVICE_ROLE_KEY umgeht RLS – in Produktion Berechtigungen sorgfältig steuern.
Gibt es ein Ausführungszeitlimit für Edge Functions?
Ja. Supabase empfiehlt, die Ausführungszeit pro Funktion auf maximal 2 Sekunden zu begrenzen; Cold Start liegt bei 0–5 ms. Geeignet für kurzlebige Operationen (Webhooks, KI-Inferenz, E-Mail-Versand), nicht für lang laufende Tasks (Video-Transcoding, große Datenverarbeitung).
Wie debuggt man Edge Functions?
Mehrere Wege: (1) lokal `supabase functions serve` mit Hot Reload – Codeänderungen wirken sofort; (2) `console.log()`-Ausgaben erscheinen in den Dashboard-Logs; (3) `supabase functions serve --env-file` lädt lokale Umgebungsvariablen; (4) Test-Requests mit curl oder Postman senden.

7 Min. Lesezeit · Veröffentlicht am: 19. Apr. 2026 · Aktualisiert am: 14. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog