ScaleRocket/Web

Edge Functions

Fonctions côté serveur pour les paiements, les e-mails et les endpoints API.

Vue d'ensemble

ScaleRocket utilise les Edge Functions Supabase pour toute la logique côté serveur. Elles s'exécutent sur Deno Deploy, à proximité des utilisateurs dans le monde entier, et gèrent les paiements, les e-mails, la validation de l'authentification et les endpoints API personnalisés.

ScaleRocket utilise les fonctions Convex pour toute la logique côté serveur. Elles s'exécutent sur la plateforme cloud Convex et gèrent les paiements, les e-mails, la validation de l'authentification et les endpoints API personnalisés. Convex fournit trois types de fonctions : queries (lecture), mutations (écriture) et actions (appels API externes).

Que sont-elles

Les Edge Functions sont des fonctions TypeScript qui s'exécutent sur le réseau edge de Supabase (Deno Deploy). Elles remplacent les routes API traditionnelles et les fonctions serverless :

  • Runtime : Deno (pas Node.js)
  • Emplacement : supabase/functions/
  • Pattern d'URL : https://<project>.supabase.co/functions/v1/<function-name>
  • Authentification : Vérification JWT intégrée

Les fonctions Convex sont des fonctions TypeScript qui s'exécutent sur la plateforme Convex. Elles remplacent les routes API traditionnelles et les fonctions serverless :

  • Runtime : Node.js (V8 isolate)
  • Emplacement : convex/
  • Accès : Hooks typés (useQuery, useMutation, useAction)
  • Authentification : Intégrée via @convex-dev/auth
  • Endpoints HTTP : convex/http.ts pour les webhooks et appels externes

Fonctions disponibles

FonctionObjectifDéclencheur
stripe-checkoutCrée une session Stripe CheckoutAppel client
stripe-portalCrée une session Stripe Billing PortalAppel client
stripe-webhookTraite les événements webhook StripeStripe
use-creditsDéduit des crédits de manière atomiqueAppel client
delete-accountSupprime le compte utilisateur et ses donnéesAppel client
FonctionTypeObjectifDéclencheur
stripe.createCheckoutSessionActionCrée une session Stripe CheckoutAppel client
stripe.createPortalSessionActionCrée une session Stripe Billing PortalAppel client
stripe.handleWebhookEndpoint HTTPTraite les événements webhook StripeStripe
credits.deductCreditsMutationDéduit des crédits de manière atomiqueAppel client
users.deleteAccountActionSupprime le compte utilisateur et ses donnéesAppel client

Structure des répertoires

supabase/functions/
├── _shared/                    # Shared utilities
│   └── supabase.ts             # corsHeaders helper and admin client
├── stripe-checkout/
│   └── index.ts
├── stripe-portal/
│   └── index.ts
├── stripe-webhook/
│   └── index.ts
├── use-credits/
│   └── index.ts
└── delete-account/
    └── index.ts
convex/
├── _generated/                 # Types auto-générés (ne pas modifier)
│   ├── api.d.ts
│   └── server.d.ts
├── schema.ts                   # Schéma de base de données
├── auth.config.ts              # Configuration d'authentification
├── http.ts                     # Endpoints HTTP (webhooks)
├── stripe.ts                   # Handlers checkout, portail, webhook Stripe
├── credits.ts                  # Solde et déduction de crédits
└── users.ts                    # Gestion des profils et comptes utilisateur

Utilitaires partagés

Le répertoire _shared/ contient du code réutilisable importé par toutes les fonctions. Il n'est pas déployé en tant que fonction lui-même. Tout se trouve dans un seul fichier supabase.ts.

_shared/supabase.ts

// supabase/functions/_shared/supabase.ts
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

export const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers":
    "authorization, x-client-info, apikey, content-type",
};

export const supabaseAdmin = createClient(
  Deno.env.get("SUPABASE_URL")!,
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
);

Chaque fonction commence par une vérification CORS et importe depuis _shared/supabase.ts :

import { corsHeaders, supabaseAdmin } from "../_shared/supabase.ts";

Deno.serve(async (req) => {
  // Handle CORS preflight
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  try {
    // Verify the user's JWT
    const authHeader = req.headers.get("Authorization");
    if (!authHeader) throw new Error("No authorization header");

    const token = authHeader.replace("Bearer ", "");
    const { data: { user }, error } = await supabaseAdmin.auth.getUser(token);
    if (error || !user) throw new Error("Invalid token");

    // ... function logic

    return new Response(JSON.stringify({ success: true }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 401,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});

Les fonctions Convex partagent les utilitaires via des imports TypeScript classiques. L'authentification est gérée via la bibliothèque @convex-dev/auth :

// convex/stripe.ts
import { action, internalMutation } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";
import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export const createCheckoutSession = action({
  args: { priceId: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    // ... logique de la fonction
  },
});

Pas besoin de gestion CORS -- Convex gère cela automatiquement pour les appels de fonctions client.

Créer une nouvelle fonction

1. Générer la fonction

pnpm supabase functions new my-function

Cela crée supabase/functions/my-function/index.ts.

2. Écrire le handler

// supabase/functions/my-function/index.ts
import { corsHeaders, supabaseAdmin } from "../_shared/supabase.ts";

Deno.serve(async (req) => {
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders });
  }

  try {
    const authHeader = req.headers.get("Authorization");
    if (!authHeader) throw new Error("No authorization header");

    const token = authHeader.replace("Bearer ", "");
    const { data: { user }, error: authError } = await supabaseAdmin.auth.getUser(token);
    if (authError || !user) throw new Error("Invalid token");

    const { someParam } = await req.json();

    // Your logic here
    const { data, error } = await supabaseAdmin
      .from("my_table")
      .select("*")
      .eq("user_id", user.id);

    return new Response(JSON.stringify({ data }), {
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 400,
      headers: { ...corsHeaders, "Content-Type": "application/json" },
    });
  }
});

3. Tester localement

pnpm supabase functions serve my-function

4. Appeler depuis le client

const { data, error } = await supabase.functions.invoke("my-function", {
  body: { someParam: "value" },
});

5. Déployer

pnpm supabase functions deploy my-function

1. Créer un fichier dans le répertoire convex/

Créez convex/myFeature.ts (ou ajoutez à un fichier existant).

2. Écrire la fonction

// convex/myFeature.ts
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getAuthUserId } from "@convex-dev/auth/server";

export const getData = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    return await ctx.db
      .query("myTable")
      .withIndex("by_userId", (q) => q.eq("userId", userId))
      .collect();
  },
});

export const createItem = mutation({
  args: { content: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    return await ctx.db.insert("myTable", {
      userId,
      content: args.content,
    });
  },
});

3. Tester localement

Les fonctions sont disponibles automatiquement en exécutant npx convex dev.

4. Appeler depuis le client

import { useQuery, useMutation } from "convex/react";
import { api } from "../convex/_generated/api";

const data = useQuery(api.myFeature.getData);
const createItem = useMutation(api.myFeature.createItem);

await createItem({ content: "value" });

5. Déployer

npx convex deploy

Variables d'environnement

Les Edge Functions accèdent aux secrets via Deno.env.get(). Définissez-les avec :

# Local development -- add to supabase/.env
STRIPE_SECRET_KEY=sk_test_xxx

# Production
pnpm supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx

SUPABASE_URL, SUPABASE_ANON_KEY et SUPABASE_SERVICE_ROLE_KEY sont disponibles automatiquement.

Les fonctions Convex accèdent aux variables d'environnement via process.env. Définissez-les avec :

# Développement local (utilise le déploiement dev)
npx convex env set STRIPE_SECRET_KEY sk_test_xxx

# Production
npx convex env set STRIPE_SECRET_KEY sk_live_xxx --prod

Listez toutes les variables d'environnement :

npx convex env list

Fini ? Marquez cette page comme terminée.

On this page