Aller au contenu

Fonctions Personnalisées

Les fonctions personnalisées vous permettent d’ajouter des routes d’API Hono arbitraires aux côtés des points de terminaison CRUD auto-générés de Rebase. Elles suivent le même modèle de découverte basée sur les fichiers que les collections et les tâches cron : déposez un fichier TypeScript dans votre répertoire functions/, et Rebase le monte automatiquement.

Utilisez les fonctions personnalisées pour :

  • Points de terminaison de logique métier — approbations, promotions, workflows personnalisés
  • Intégrations tierces — webhooks Stripe, commandes Slack, proxys d’API externes
  • Points de terminaison publics — formulaires de contact, capture de leads, vérifications de l’état de santé
  • Requêtes agrégées — statistiques de tableau de bord, rapports, analyses

Créez un fichier dans votre répertoire backend/functions/ qui exporte par défaut une application Hono :

// backend/functions/hello.ts
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => {
return c.json({ message: "Hello from custom function!" });
});
export default app;

Ceci est monté à /api/functions/hello. Le nom de fichier (sans extension) devient le préfixe de la route.

Activez les fonctions personnalisées en ajoutant functionsDir à votre configuration de backend :

import path from "path";
const instance = await initializeRebaseBackend({
// ... other config
functionsDir: path.resolve(__dirname, "../functions"),
});

Rebase va :

  1. Scanner le répertoire pour les fichiers .ts / .js
  2. Valider que chaque exportation par défaut est une application Hono (duck-typed via .fetch() + .routes)
  3. Monter chaque application à /api/functions/<filename>
  4. Appliquer le middleware d’authentification (voir Authentification ci-dessous)
FichierChemin de montage
functions/hello.ts/api/functions/hello/*
functions/send-invoice.ts/api/functions/send-invoice/*
functions/webhooks.ts/api/functions/webhooks/*

Fichiers qui sont ignorés :

  • index.ts / index.js — réservé
  • *.test.ts / *.test.js — fichiers de test
  • *.d.ts — déclarations de type

Le chargeur accepte deux formats d’exportation :

import { Hono } from "hono";
const app = new Hono();
app.get("/status", (c) => c.json({ ok: true }));
export default app;
import { Hono } from "hono";
export default function () {
const app = new Hono();
app.get("/status", (c) => c.json({ ok: true }));
return app;
}

Les deux sont détectés via le duck-typing — le chargeur vérifie les propriétés .fetch() et .routes, de sorte que toute instance compatible Hono fonctionnera quelle que soit la version de Hono installée.

Les fonctions personnalisées sont montées avec le même middleware d’authentification que les routes de données, mais avec requireAuth: false. Cela signifie :

  • Le JWT de l’utilisateur est analysé et injecté dans le contexte s’il est présent
  • Mais les requêtes ne sont pas rejetées si aucun JWT n’est fourni
  • Vous devez protéger explicitement les routes qui nécessitent une authentification

Utilisez les assistants d’authentification intégrés de Rebase :

import { Hono } from "hono";
const app = new Hono();
// Public endpoint — no auth required
app.get("/public", (c) => {
return c.json({ message: "Anyone can access this" });
});
// Protected endpoint — requires a valid JWT
app.post("/protected", async (c) => {
const user = c.get("user"); // Injected by Rebase middleware
if (!user) {
return c.json({ error: "Unauthorized" }, 401);
}
return c.json({ message: `Hello, ${user.uid}` });
});
// Admin-only endpoint
app.post("/admin-only", async (c) => {
const user = c.get("user");
const roles: string[] = user?.roles ?? [];
if (!roles.includes("admin")) {
return c.json({ error: "Admin access required" }, 403);
}
return c.json({ message: "Admin operation succeeded" });
});
export default app;

Le middleware JWT de Rebase est limité aux routes API intégrées (/api/data, /api/auth, etc.). Les routes de fonctions personnalisées obtiennent le contexte utilisateur analysé, mais vous devez appliquer le contrôle d’accès vous-même.

Les fonctions personnalisées s’exécutent aux côtés de Rebase, vous pouvez donc accéder à la base de données via deux approches :

Le package @rebasepro/server-core fournit un singleton rebase qui vous donne un accès de niveau administrateur à tous les services (données, authentification, stockage, e-mail) liés à l’application, depuis n’importe où dans votre backend.

// backend/functions/approve-job.ts
import { Hono } from "hono";
import { rebase } from "@rebasepro/server-core";
const app = new Hono();
app.post("/:id/approve", async (c) => {
const id = c.req.param("id");
// Use the admin-level data API (bypasses RLS)
await rebase.data.saveEntity("jobs", {
id,
status: "published",
approved_at: new Date().toISOString(),
});
return c.json({ success: true });
});
export default app;
// backend/functions/reports.ts
import { Hono } from "hono";
import { db } from "../src/db"; // Your Drizzle instance
import { sql } from "drizzle-orm";
const app = new Hono();
app.get("/stats", async (c) => {
const result = await db.execute(sql`
SELECT COUNT(*) as total FROM jobs WHERE status = 'published'
`);
return c.json({ totalJobs: result.rows[0]?.total });
});
export default app;

Les fonctions personnalisées sont chargées et montées après que initializeRebaseBackend() ait terminé la configuration principale. L’ordre d’initialisation est :

  1. Bootstrappers — Connexions à la base de données, tables d’authentification, services en temps réel
  2. Routes d’authentification/api/auth/*, /api/admin/*
  3. Routes de stockage/api/storage/*
  4. Routes de données/api/data/* (CRUD pour les collections)
  5. Fonctions personnalisées/api/functions/*
  6. Tâches Cron/api/cron/*
  7. WebSocket — Abonnements en temps réel

Cela signifie que vos fonctions personnalisées ont accès à tous les services initialisés. Enregistrez toutes les routes qui doivent s’exécuter avant Rebase directement sur l’application Hono, avant d’appeler initializeRebaseBackend() :

const app = new Hono();
// Ceci s'exécute AVANT les routes Rebase
app.get("/health", (c) => c.json({ status: "ok" }));
// Initialisation de Rebase — enregistre toutes les routes /api/*
const instance = await initializeRebaseBackend({ app, /* ... */ });
// backend/functions/stripe-webhook.ts
import { Hono } from "hono";
import Stripe from "stripe";
import { instance } from "../src/index";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const app = new Hono();
app.post("/", async (c) => {
const sig = c.req.header("stripe-signature")!;
const body = await c.req.text();
const event = stripe.webhooks.constructEvent(
body,
sig,
process.env.STRIPE_WEBHOOK_SECRET!
);
if (event.type === "checkout.session.completed") {
const session = event.data.object;
await instance.driver.data.subscriptions.create({
user_id: session.client_reference_id,
stripe_id: session.subscription,
status: "active",
});
}
return c.json({ received: true });
});
export default app;

Lorsqu’une fonction est chargée avec succès, vous verrez :

⚡ Loaded function route: hello

Si le chargement échoue, le chargeur fournit une sortie de diagnostic :

[functions] broken-function.ts: default export is not a Hono app or factory. Skipping.
export type: object (SomeClass)
prototype methods: constructor, someMethod
Hint: ensure the function exports a Hono app created with the same hono version as the server.