ScaleRocket/Web

Appels API

Comment créer et appeler des fonctions backend — la couche API serverless de ScaleRocket.

Appels API

ScaleRocket utilise les Supabase Edge Functions comme couche API. Les Edge Functions sont des fonctions TypeScript serverless qui tournent sur Deno, proches de vos utilisateurs. Elles gèrent des tâches comme le traitement des paiements, la déduction de crédits et toute logique côté serveur.

ScaleRocket utilise les fonctions Convex comme couche API. Les fonctions Convex sont des fonctions TypeScript qui s'exécutent sur le backend Convex. Elles gèrent des tâches comme le traitement des paiements, la déduction de crédits et toute logique côté serveur. Convex fournit trois types de fonctions : queries (lecture), mutations (écriture) et actions (appels API externes).

Structure des répertoires

Les Edge Functions se trouvent dans supabase/functions/. Chaque dossier est une fonction séparée :

supabase/functions/
├── stripe-checkout/
│   └── index.ts       # Create Stripe checkout session
├── stripe-portal/
│   └── index.ts       # Create Stripe billing portal session
├── stripe-webhook/
│   └── index.ts       # Handle Stripe webhook events
├── use-credits/
│   └── index.ts       # Deduct credits from user balance
├── delete-account/
│   └── index.ts       # Delete user account and data
└── _shared/
    └── supabase.ts    # corsHeaders helper and admin client

Note : Les dossiers commençant par _ (comme _shared) ne sont pas déployés en tant que fonctions — ce sont des modules partagés que vous pouvez importer.

Les fonctions Convex se trouvent dans le répertoire convex/. Chaque fichier peut exporter plusieurs fonctions :

convex/
├── schema.ts          # Schéma de base de données
├── auth.config.ts     # Configuration d'authentification
├── http.ts            # Endpoints HTTP (webhooks)
├── stripe.ts          # Logique checkout, portail, webhook Stripe
├── credits.ts         # Solde et déduction de crédits
├── users.ts           # Gestion des profils et comptes utilisateur
└── _generated/
    ├── api.d.ts       # Types API auto-générés
    └── server.d.ts    # Types serveur auto-générés

Note : Le répertoire _generated/ est auto-généré par Convex et fournit des références API typées.

Créer une nouvelle fonction backend

1. Créer la structure de la fonction

npx supabase functions new my-function

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

2. Écrire la fonction

// supabase/functions/my-function/index.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: authError } = await supabaseAdmin.auth.getUser(token);
    if (authError || !user) {
      return new Response(
        JSON.stringify({ error: "Unauthorized" }),
        { status: 401, headers: { ...corsHeaders, "Content-Type": "application/json" } }
      );
    }

    // Parse request body
    const { someParam } = await req.json();

    // Your logic here
    const result = { message: `Hello ${user.email}`, someParam };

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

3. Tester en local

npx supabase functions serve my-function

La fonction est disponible sur http://localhost:54321/functions/v1/my-function.

4. Déployer

npx supabase functions deploy my-function

1. Créer un nouveau fichier ou ajouter à un fichier existant

Créez un fichier dans le répertoire convex/ (ex. convex/myFeature.ts).

2. Écrire la fonction

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

// Query : lecture de données (automatiquement réactive)
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();
  },
});

// Mutation : écriture de données (transactionnelle)
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,
    });
  },
});

// Action : appels API externes
export const callExternalApi = action({
  args: { someParam: v.string() },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    const response = await fetch("https://api.example.com", {
      method: "POST",
      body: JSON.stringify({ param: args.someParam }),
    });

    return await response.json();
  },
});

3. Tester en local

Les fonctions sont disponibles immédiatement en exécutant :

npx convex dev

4. Déployer

npx convex deploy

Appeler depuis le client

ScaleRocket fournit un utilitaire API qui encapsule supabase.functions.invoke() :

// apps/app/src/lib/api.ts
import { supabase } from "./supabase";

export async function callApi<T>(
  functionName: string,
  body?: Record<string, unknown>
): Promise<T> {
  const { data, error } = await supabase.functions.invoke(functionName, {
    body,
  });

  if (error) {
    throw new Error(error.message || "API call failed");
  }

  return data as T;
}

Utilisez-le dans vos composants :

// apps/app/src/features/dashboard/SomeComponent.tsx
import { callApi } from "../../lib/api";

async function handleAction() {
  try {
    const result = await callApi("my-function", {
      someParam: "value",
    });
    console.log(result);
  } catch (error) {
    console.error("API error:", error);
  }
}

Convex fournit des hooks typés pour appeler les fonctions directement :

// apps/app/src/features/dashboard/SomeComponent.tsx
import { useQuery, useMutation, useAction } from "convex/react";
import { api } from "../../convex/_generated/api";

function SomeComponent() {
  // Queries : réactives, auto-actualisées
  const data = useQuery(api.myFeature.getData);

  // Mutations : pour écrire des données
  const createItem = useMutation(api.myFeature.createItem);

  // Actions : pour les appels API externes
  const callExternal = useAction(api.myFeature.callExternalApi);

  async function handleAction() {
    try {
      await createItem({ content: "value" });
      // data se met à jour automatiquement -- pas besoin de refetch
    } catch (error) {
      console.error("API error:", error);
    }
  }
}

Pas besoin de wrapper API -- les hooks Convex sont entièrement typés et gèrent les états de chargement/erreur.

Authentification dans les fonctions backend

Le client Supabase transmet automatiquement le JWT de l'utilisateur aux Edge Functions. Côté serveur, vous le vérifiez :

// 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")!
);

Le client supabaseAdmin utilise la clé service role et contourne toutes les politiques Row Level Security. Utilisez-le pour les opérations côté serveur comme la mise à jour des données d'abonnement depuis les webhooks ou la vérification des tokens utilisateur.

Note : Le client admin est approprié dans les Edge Functions car elles s'exécutent côté serveur. N'exposez jamais la clé service role au code côté client.

Convex gère l'authentification automatiquement. Utilisez getAuthUserId pour obtenir l'utilisateur courant dans n'importe quelle fonction :

import { getAuthUserId } from "@convex-dev/auth/server";

// Dans n'importe quelle query, mutation ou action :
const userId = await getAuthUserId(ctx);
if (!userId) throw new Error("Unauthorized");

Aucune vérification manuelle de JWT n'est nécessaire -- Convex gère les tokens de session automatiquement.

Gestion des erreurs

ScaleRocket utilise des réponses d'erreur cohérentes dans toutes les fonctions backend :

// Success response
return new Response(
  JSON.stringify({ data: result }),
  { status: 200, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);

// Client error
return new Response(
  JSON.stringify({ error: "Invalid input", details: "Name is required" }),
  { status: 400, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);

// Server error
return new Response(
  JSON.stringify({ error: "Internal server error" }),
  { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
);

Côté client, l'utilitaire callApi lève une erreur en cas de problème, vous pouvez donc utiliser try/catch :

try {
  const data = await callApi("my-function", { input: "value" });
  // Handle success
} catch (error) {
  // error.message contains the error from the Edge Function
  toast.error(error.message);
}

Les fonctions Convex lèvent des erreurs directement. Les hooks côté client les gèrent automatiquement :

// Dans les fonctions Convex, il suffit de throw :
if (!userId) throw new Error("Unauthorized");
if (credits.balance < cost) throw new Error("Insufficient credits");

// Côté client :
try {
  await createItem({ content: "value" });
} catch (error) {
  toast.error(error.message);
}

Configuration CORS

Les Edge Functions ont besoin d'en-têtes CORS pour les requêtes depuis le navigateur :

// supabase/functions/_shared/supabase.ts (corsHeaders export)
export const corsHeaders = {
  "Access-Control-Allow-Origin": "*",
  "Access-Control-Allow-Headers":
    "authorization, x-client-info, apikey, content-type",
};

Note : En production, remplacez * par votre domaine réel pour une meilleure sécurité.

Convex gère le CORS automatiquement pour tous les appels de fonctions via le client Convex. Aucune configuration CORS manuelle n'est nécessaire.

Pour les endpoints HTTP (comme les webhooks), vous pouvez configurer les en-têtes CORS dans convex/http.ts si nécessaire.

Variables d'environnement

Les Edge Functions ont accès aux variables Supabase automatiquement (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY). Pour les secrets personnalisés :

# Set a secret for Edge Functions
npx supabase secrets set MY_SECRET=value

# List all secrets
npx supabase secrets list

Accédez-y dans votre fonction :

const mySecret = Deno.env.get("MY_SECRET");

Définissez les variables d'environnement pour les fonctions Convex :

# Définir une variable d'environnement
npx convex env set MY_SECRET value

# Lister toutes les variables d'environnement
npx convex env list

Accédez-y dans votre fonction :

const mySecret = process.env.MY_SECRET;

Prochaines étapes

Fini ? Marquez cette page comme terminée.

On this page