Changer le thème

Supabase Edge Functions en pratique : runtime Deno et guide TypeScript

Easton editorial illustration: hardened server operations console

Le téléphone vibre sans arrêt. Les webhooks Stripe renvoient des 500 en production — le paiement est passé, la commande n’a pas été créée.

En creusant les logs, le coupable : l’ancienne fonction serverless — cold start trop long, Stripe timeout avant la réponse. Et en plus, il fallait encore monter une passerelle API pour les signatures et le CORS…

Cette nuit-là, j’ai sérieusement creusé Supabase Edge Functions. Au début, « runtime Deno » m’a un peu freiné — des années de Node.js, changer de runtime, c’est réapprendre des API. Mais en pratique, la logique est différente : ce n’est pas une « migration », c’est une option plus légère pour les scénarios sans dépendances lourdes.

Cet article partage les pièges et ce que j’ai appris : architecture d’Edge Functions, différences Deno/Node.js, flux de dev local, et comment écrire une API proprement avec Hono.

Qu’est-ce qu’Edge Functions — architecture et choix techniques

Commençons par ce que c’est, et pourquoi Supabase a choisi Deno plutôt que Node.js.

Exécution edge, pas hébergement cloud centralisé

Edge Functions, ce sont des fonctions TypeScript sur des nœuds edge. Contrairement à Lambda ou Vercel Functions, ce n’est pas déployé dans quelques grandes régions : c’est réparti sur des centaines de points edge dans le monde.

Conséquence : un utilisateur à Shanghai peut exécuter la fonction à Tokyo — la latence passe de centaines de millisecondes à quelques dizaines.

Mais l’edge a un prix — la fonction ne doit pas être trop lourde. Chaque fonction tourne dans un isolate V8, avec son propre tas mémoire et son thread d’exécution ; démarrage en millisecondes, mais mémoire et durée limitées. Idéal pour les opérations courtes : webhooks, images OG, appels API tiers, envoi d’e-mails.

Moins adapté : tâches longues, bibliothèques Node.js natives lourdes, accès au système de fichiers.

Pourquoi Deno

J’ai longtemps parcouru les GitHub Discussions Supabase. L’explication officielle, en gros :

  1. Démarrage rapide : Deno emballe le code en ESZip, cold start 0-5 ms ; Lambda Node.js plutôt 100-500 ms.
  2. Modèle de sécurité : accès fichiers et réseau désactivés par défaut, autorisation explicite. Important en multi-tenant edge — vous ne voulez pas qu’une fonction lise les données d’un autre tenant.
  3. TypeScript natif : pas de tsconfig ni ts-node, un fichier .ts suffit. Pour ceux qui font déjà du backend en TypeScript, moins de config.
  4. Portabilité : Deno s’intègre dans d’autres apps. Supabase utilise sa branche deno_core, optimisée pour l’embarqué.

Contrepartie : l’écosystème est plus petit que Node.js ; certains paquets npm ne passent pas. Deno supporte désormais les npm specifiers (import { xxx } from 'npm:lodash'), la compatibilité s’améliore.

Aperçu de l’architecture

Flux approximatif d’une requête :

Client → CDN/passerelle edge → validation JWT → isolate V8 exécute la fonction → réponse

Point clé : la validation JWT — Edge Functions vérifie par défaut l’en-tête Authorization. Pour un accès public, déployez avec --no-verify-jwt.


Environnement de développement et commandes CLI

Concepts posés, passons à la pratique.

Installer Supabase CLI

Sous macOS, Homebrew :

brew install supabase/tap/supabase

Linux et Windows ont leurs méthodes ; la doc officielle est claire, je ne les répète pas.

Puis connexion :

supabase login

Le navigateur s’ouvre pour autoriser le CLI sur votre compte Supabase.

Initialiser le projet

Dans le répertoire du projet :

supabase init

Cela crée supabase/ avec config.toml et le sous-répertoire functions/ si besoin.

Créer sa première Edge Function

supabase functions new hello-world

La commande crée supabase/functions/hello-world/ avec un index.ts :

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',
    },
  })
})

Voilà. Deno.serve() est l’API native Deno ; Request et Response sont les Web API standard, comme fetch dans le navigateur.

Serveur de développement local

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

Serveur local sur http://localhost:54321 ; la fonction est à http://localhost:54321/functions/v1/hello-world.

Premier piège que j’ai eu : oublier de lancer la stack Supabase locale (PostgreSQL inclus). Ordre correct :

# D'abord la stack locale
supabase start

# Puis les fonctions
supabase functions serve

Requête de test

Avec curl ou 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"}'

Réponse :

{
  "message": "Hello World!"
}

Ça marche.

Le rechargement à chaud est automatique : modifier le code suffit, pas besoin de redémarrer.

Variables d’environnement

Ne mettez pas les secrets dans le code. Supabase gère les variables via .env :

# Créer le fichier .env
echo "MY_SECRET=super_secret_value" > supabase/.env.local

# Lire dans la fonction
const mySecret = Deno.env.get('MY_SECRET')

En production, supabase secrets set :

supabase secrets set MY_SECRET=super_secret_value

En pratique : API RESTful avec Hono

Deno.serve() suffit pour du simple ; avec routage, middlewares et validation, à la main c’est pénible.

Entre Hono.

Qu’est-ce que Hono

Hono est un framework web ultra-léger pour runtimes edge. Deno, Cloudflare Workers, Bun… routage performant, TypeScript au premier plan.

Officiellement « small, simple, and ultrafast » — mon expérience va dans ce sens.

Intégration dans Edge Functions

Nouvelle fonction :

supabase functions new user-api

Puis modifier index.ts :

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')

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

// Routes
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 }>()
  // Ici : branchement base Supabase
  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 })
})

// Démarrage
Deno.serve(app.fetch)

Points importants :

  1. jsr:@hono/hono est le format JSR Deno, pas npm. JSR est le registre officiel Deno.
  2. basePath('/api') préfixe les routes par /api.
  3. c est le contexte Hono : requête, réponse, utilitaires.
  4. c.json() définit Content-Type et gère null/undefined.

Connexion à la base Supabase

Hono est le framework web ; pour la base, le client Supabase. Exemple complet :

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

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

// Client Supabase
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 — création
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)

J’utilise SUPABASE_SERVICE_ROLE_KEY : permissions complètes, contourne le RLS. À manier avec prudence en production.

Gestion d’erreurs et validation

Hono n’a pas de validateur intégré, mais Zod fonctionne bien :

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 est typé de façon sûre
    return c.json({ received: validated })
  }
)

En cas d’échec de validation : 400 avec le détail des erreurs.


Déploiement et bonnes pratiques en production

Ça tourne en local, place à la prod.

Commande de déploiement

supabase functions deploy user-api

Au premier déploiement, le CLI demande le projet Supabase à lier. Ensuite : upload, build, déploiement automatiques.

URL de la fonction :

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

Variables d’environnement et Secrets

En production, variables à configurer séparément :

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

Secrets chiffrés ; lecture via Deno.env.get() à l’exécution.

Stratégie de validation JWT

Edge Functions valide le JWT par défaut :

  • Seules les requêtes avec Authorization: Bearer <token> valide passent
  • Les infos utilisateur du token sont dans les en-têtes de req

Pour une API publique (webhook tiers), --no-verify-jwt :

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

Tout le monde peut alors appeler la fonction — à vous de valider dans le code.

Réduire la latence de cold start

Deno démarre vite, mais on peut aller plus loin :

  1. Réduire le volume des dépendances : privilégier Deno/JSR, moins de paquets npm
  2. Chargement différé : gros modules via import() à la demande
  3. Fonctions légères : une fonction, une responsabilité — pas tout le backend dedans

Supabase recommande moins de 2 secondes d’exécution et un cold start de 0-5 ms. Ces conseils accélèrent la réponse :

Monitoring et logs

Le Dashboard affiche logs d’appel et erreurs. Intégration possible avec Sentry ou autre.

L’API EdgeRuntime.waitUntil() permet de continuer en arrière-plan après la réponse :

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

return new Response('OK')

Le client reçoit la réponse sans attendre la tâche de fond.


Conclusion

Edge Functions, pour quels cas ?

Adapté :

  • Webhooks (Stripe, GitHub, Slack)
  • Génération d’images OG
  • Inférence IA (appels API LLM)
  • E-mails et notifications
  • Traitements de données à courte durée

Moins adapté :

  • Tâches longues (transcodage vidéo)
  • Bibliothèques Node.js natives lourdes
  • Accès au système de fichiers

Si vous utilisez déjà la base et l’auth Supabase, Edge Functions prolonge naturellement la stack — pas de serveur à monter, pas d’ops lourds, vous écrivez la logique métier.

Face à Cloudflare Workers ou Vercel Functions ? Chacun a ses forces. Workers est plus mature, Vercel s’intègre à Next.js. Mais avec Supabase, Edge Functions offre la meilleure intégration — client base, auth, stockage prêts.

Pour commencer : github.com/supabase/supabase/tree/master/examples/edge-functions

Questions en commentaire, ou sur le Discord Supabase.

Workflow complet de développement et déploiement Supabase Edge Functions

Guide opérationnel complet, de la configuration de l'environnement au déploiement en production

⏱️ Estimated time: 45 min

  1. 1

    Step 1: Installer Supabase CLI et se connecter

    Installer le CLI avec Homebrew (macOS) :

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

    La connexion ouvre le navigateur pour autoriser le CLI à accéder à votre compte Supabase.
  2. 2

    Step 2: Initialiser le projet et créer une fonction

    Exécuter la commande d'initialisation dans le répertoire du projet, puis créer votre première fonction :

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

    Cela crée un modèle de fonction dans le répertoire `supabase/functions/`.
  3. 3

    Step 3: Démarrer l'environnement de développement local

    D'abord démarrer la stack Supabase locale (PostgreSQL inclus), puis le service de fonctions :

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

    Adresse locale : `http://localhost:54321/functions/v1/{function-name}`
  4. 4

    Step 4: Construire une API avec le framework Hono

    Installer Hono et créer une API RESTful :

    ```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 prend en charge le routage, les middlewares et la validation des paramètres.
  5. 5

    Step 5: Configurer les variables d'environnement et les Secrets

    Fichier `.env` en local, Secrets en production :

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

    # Production
    supabase secrets set MY_SECRET=value
    ```

    Lecture dans la fonction via `Deno.env.get('MY_SECRET')`.
  6. 6

    Step 6: Déployer en production

    Déployer la fonction et définir l'accès public si nécessaire :

    ```bash
    # Déploiement standard (validation JWT requise)
    supabase functions deploy user-api

    # API publique (sans validation JWT)
    supabase functions deploy user-api --no-verify-jwt
    ```

    Format de l'URL de production : `https://[PROJECT_ID].supabase.co/functions/v1/user-api`

FAQ

Quelle est la différence entre Supabase Edge Functions et Cloudflare Workers ?
Les deux sont des plateformes edge, avec quelques différences : (1) Edge Functions s'intègre profondément à la base Supabase, à l'authentification et au stockage, prêt à l'emploi ; (2) runtime Deno vs moteur V8 — Edge Functions prend en charge les npm specifiers et JSR ; (3) cold start au niveau de la milliseconde, performances comparables. Si vous utilisez déjà Supabase, l'expérience d'intégration d'Edge Functions est meilleure.
Edge Functions convient-il au traitement des webhooks ?
Très bien. Le webhook est un cas typique : (1) cold start rapide — Stripe, GitHub, etc. ne timeout pas ; (2) vérification de signature, logique métier et écriture en base Supabase directement ; (3) déploiement en API publique avec --no-verify-jwt.
En quoi la gestion des paquets Deno diffère-t-elle de Node.js ?
Deno utilise JSR (registre officiel Deno) et les npm specifiers : (1) paquets JSR importés au format `jsr:@hono/hono` ; (2) paquets npm au format `npm:zod` ; (3) pas de package.json ni node_modules, dépendances gérées automatiquement. La plupart des bibliothèques courantes sont prises en charge.
Comment se connecter à la base Supabase depuis Edge Functions ?
Via le client @supabase/supabase-js : (1) importer `jsr:@supabase/supabase-js@2` ; (2) lire SUPABASE_URL et SUPABASE_SERVICE_ROLE_KEY depuis les variables d'environnement ; (3) à la création du client, définir auth.autoRefreshToken: false (inutile en edge) ; (4) SERVICE_ROLE_KEY contourne le RLS — attention aux permissions en production.
Edge Functions a-t-il une limite de temps d'exécution ?
Oui. Supabase recommande de ne pas dépasser 2 secondes par fonction, cold start 0-5 ms. Convient aux opérations courtes (webhooks, inférence IA, envoi d'e-mails), pas aux tâches longues (transcodage vidéo, gros traitements de données).
Comment déboguer Edge Functions ?
Plusieurs approches : (1) `supabase functions serve` en local + rechargement à chaud ; (2) les `console.log()` apparaissent dans les logs du Dashboard ; (3) `supabase functions serve --env-file` pour charger les variables locales ; (4) requêtes de test avec curl ou Postman.

8 min de lecture · Publié le: 19 avr. 2026 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog