Supabase Edge Functions en pratique : runtime Deno et guide TypeScript

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 :
- Démarrage rapide : Deno emballe le code en ESZip, cold start 0-5 ms ; Lambda Node.js plutôt 100-500 ms.
- 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.
- TypeScript natif : pas de tsconfig ni ts-node, un fichier
.tssuffit. Pour ceux qui font déjà du backend en TypeScript, moins de config. - 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 :
jsr:@hono/honoest le format JSR Deno, pas npm. JSR est le registre officiel Deno.basePath('/api')préfixe les routes par/api.cest le contexte Hono : requête, réponse, utilitaires.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 :
- Réduire le volume des dépendances : privilégier Deno/JSR, moins de paquets npm
- Chargement différé : gros modules via
import()à la demande - 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
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
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
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
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
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
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 ?
Edge Functions convient-il au traitement des webhooks ?
En quoi la gestion des paquets Deno diffère-t-elle de Node.js ?
Comment se connecter à la base Supabase depuis Edge Functions ?
Edge Functions a-t-il une limite de temps d'exécution ?
Comment déboguer Edge Functions ?
8 min de lecture · Publié le: 19 avr. 2026 · Mis à jour le: 27 juil. 2026
Supabase en pratique
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Supabase Storage en pratique : upload, CDN et contrôle d'accès
Guide complet Supabase Storage : trois modes de contrôle d'accès comparés, upload TUS par morceaux, optimisation Smart CDN, analyse des prix vs R2/S3. Exemples React et dépannage.
Partie 7 sur 10
Suivant
Supabase Auth : OAuth, SSO et contrôle des accès en profondeur
Configuration avancée de Supabase Auth : intégration OAuth multi-fournisseurs, authentification SAML SSO entreprise, isolation multi-tenant avec RLS — schéma complet du consumer au SaaS B2B.
Partie 9 sur 10



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire