Extensions JavaScript dans PocketBase : JSVM, Hooks et TypeScript en 2026

Extensions JavaScript dans PocketBase : JSVM, Hooks et TypeScript en 2026

PocketBase est un backend open-source léger qui intègre une base de données SQLite, une API REST et des outils d'authentification, tout en un seul fichier exécutable. Pour les petites entreprises et startups qui cherchent à développer rapidement sans une équipe backend dédiée, c'est un outil idéal. Mais que faire quand les fonctionnalités par défaut ne suffisent pas ? C'est là qu'interviennent les extensions JavaScript via JSVM (JavaScript Virtual Machine).

Dans cet article, nous explorons comment étendre PocketBase avec du code JavaScript pur, sans avoir besoin de compiler en Go. Nous couvrons la configuration, les variables globales, les hooks disponibles, le support TypeScript et des exemples concrets. Si vous êtes débutant en backend ou si votre startup protège un MVP (Minimum Viable Product), ces extensions vous permettront d'ajouter de la logique métier personnalisée en quelques minutes. Tout est basé sur la documentation officielle de PocketBase, mise à jour en 2025.

1. Le dossier pb_hooks et la JSVM : Les bases pour démarrer

Pour activer les extensions JavaScript, commencez par créer un dossier spécifique. C'est simple et direct, sans configuration complexe.

Créez un dossier nommé pb_hooks à côté de votre exécutable PocketBase. Par exemple, si votre fichier pocketbase.exe (ou pocketbase sur Linux/Mac) est dans /mon-projet/, placez pb_hooks au même niveau : /mon-projet/pb_hooks/.

Dans ce dossier, ajoutez vos fichiers JavaScript avec l'extension .pb.js. Par exemple, main.pb.js pour le fichier principal. PocketBase charge ces fichiers dans l'ordre alphabétique des noms de fichiers. Cela signifie que si vous avez 01-init.pb.js et 02-hooks.pb.js, le premier s'exécutera avant le second.

La JSVM est intégrée à PocketBase depuis la version 0.17. Elle utilise l'engine Goja, compatible ES5, pour exécuter du JavaScript côté serveur. Pas besoin de Node.js ou d'un environnement externe : tout tourne dans l'exécutable PocketBase.

Pour lancer : redémarrez simplement votre serveur PocketBase. Sur les systèmes Unix (Linux, Mac), modifiez un fichier .pb.js et le serveur se recharge automatiquement – parfait pour le développement rapide en startup.

Un point important : chaque hook ou route s'exécute dans un contexte isolé. Les variables déclarées en dehors d'un handler (comme const maVar = "test";) ne sont pas visibles à l'intérieur. Pour partager du code, utilisez des modules CommonJS avec require().

Exemple basique pour tester :

Dans pb_hooks/test.pb.js :

console.log("Extensions JS activées !");

Redémarrez PocketBase et vérifiez les logs : vous verrez le message. C'est prêt en deux minutes.

Pour les performances, ajustez le pool de runtimes avec le flag --hooksPool=50 au démarrage (défaut : 15). Plus de runtimes pour plus de concurrence, mais surveillez la mémoire – utile si votre SMB gère plusieurs utilisateurs simultanés.

2. Les variables globales : $app, $http, $os et plus

Une fois les hooks activés, vous accédez à des objets globaux puissants. Ils exposent les fonctionnalités de PocketBase en JavaScript, sans réinventer la roue.

Voici les principaux :

  • __hooks : Le chemin absolu vers votre dossier pb_hooks. Utile pour charger des modules locaux, comme require(__hooks + "/utils.js").
  • $app : L'instance principale de l'application PocketBase. Vous pouvez y accéder pour des opérations comme trouver un record ($app.findRecordById("users", "id")), valider des données ou gérer les settings.
  • $apis : Des helpers pour créer des routes API personnalisées ou des middlewares. Par exemple, $apis.requireSuperuser() pour vérifier les droits admin.
  • $os : Des primitives système, comme supprimer un dossier ($os.removeAll("/tmp/fichiers")) ou exécuter une commande shell ($os.exec("ls", ["-la"])). Attention : limitez à des opérations sécurisées en production.
  • $security : Outils de sécurité low-level, comme générer un JWT ($security.makeJWT(secret, claims)), chiffrer en AES ou créer des strings aléatoires. Idéal pour des tokens custom sans dépendre de libs externes.

Tous les noms sont en camelCase (par exemple, une méthode Go App.FindRecordById devient $app.findRecordById). Si une opération échoue, une exception JavaScript est levée – gérez-la avec try/catch.

Ces globals sont disponibles dans tous les handlers. Pour les startups, c'est un gain de temps : au lieu d'écrire une API complète, modifiez une réponse existante avec $app.

Exemple : Logger l'IP d'un utilisateur connecté.

onRecordAuthRequest((e) => {
    console.log("Connexion depuis IP:", e.httpContext.clientIP());
    e.next();
});

3. Tous les hooks disponibles : Plus de 80 façons d'étendre votre backend

Les hooks sont les cœurs des extensions JS. Ce sont des fonctions qui interceptent des événements du cycle de vie de PocketBase : création de records, authentification, envoi d'emails, etc. En 2025, il y en a plus de 80, regroupés par catégorie.

Chaque hook suit la signature function(e) { ... e.next(); }. L'objet e contient le contexte (app, record, request). Appelez toujours e.next() pour continuer la chaîne ; sinon, l'exécution s'arrête. Vous pouvez scoped les hooks à une collection spécifique, comme onRecordAfterCreateSuccess((e) => { ... }, "users").

Hooks au niveau App

Ces hooks gèrent l'initialisation et la maintenance globale.

  • onBootstrap : Au démarrage, pour charger des ressources (ex. : config DB).
  • onSettingsReload : Quand les settings changent.
  • onBackupCreate et onBackupRestore : Avant/après backups.
  • onTerminate : À l'arrêt du serveur.
  • onMailerSend : Pour tout email envoyé.

Exemple pour customiser un email :

app.on("onMailerSend", (e) => {
    e.message.subject = "Votre mise à jour PocketBase";
    e.next();
});

Hooks Mailer spécifiques à l'auth

Pour personnaliser les emails d'authentification (alertes, resets, OTP).

  • onMailerRecordAuthAlertSend : Alerte de connexion.
  • onMailerRecordPasswordResetSend : Reset mot de passe.
  • onMailerRecordVerificationSend : Vérification email.
  • onMailerRecordEmailChangeSend : Changement d'email.
  • onMailerRecordOTPSend : Envoi OTP.

Exemple pour ajouter un lien custom dans un reset :

app.on("onMailerRecordPasswordResetSend", (e) => {
    e.message.htmlBody += "<p>Cliquez ici pour reset : <a href='https://votre-site.com/reset'>Lien</a></p>";
    e.next();
});

Hooks Realtime

Pour les connexions SSE (Server-Sent Events) en temps réel.

  • onRealtimeConnectRequest : Nouvelle connexion client.
  • onRealtimeSubscribeRequest : Abonnement à un channel.
  • onRealtimeMessageSend : Envoi d'un message.

Exemple pour logger les abonnements :

app.on("onRealtimeSubscribeRequest", (e) => {
    console.log("Abonnement à:", e.subscriptions);
    e.next();
});

Hooks pour Records et Collections

Les plus utilisés pour CRUD. Il y en a des dizaines : avant/après validation, exécution, succès/erreur.

Pour Records (CRUD sur un enregistrement) :

  • Création : onRecordCreate, onRecordCreateExecute, onRecordAfterCreateSuccess, onRecordAfterCreateError.
  • Mise à jour : onRecordUpdate, onRecordUpdateExecute, onRecordAfterUpdateSuccess, etc.
  • Suppression : onRecordDelete, etc.
  • Spécial : onRecordValidate pour valider les données, onRecordEnrich pour enrichir une réponse (ajouter des champs calculés).

Pour Collections (gestion des schémas) :

  • Similaires : onCollectionCreate, onCollectionAfterUpdateSuccess, etc.

Exemple pour valider l'âge lors de la création d'un user :

app.on("onRecordCreateRequest", (e) => {
    if (e.record.get("age") < 18) {
        return e.invalid("Âge minimum : 18 ans");
    }
    e.next();
}, "users");

Hooks pour Requêtes API

Interceptent les endpoints spécifiques.

  • List/View/Create/Update/Delete pour records et collections : onRecordsListRequest, onRecordViewRequest, etc.
  • Auth : onRecordAuthRequest, onRecordAuthWithPasswordRequest, onRecordAuthWithOAuth2Request, onRecordRequestPasswordResetRequest, etc.
  • Autres : onFileDownloadRequest, onBatchRequest, onSettingsUpdateRequest.

Exemple pour restreindre l'accès à un fichier :

app.on("onFileDownloadRequest", (e) => {
    if (!e.httpContext.authRecord()) {
        return e.error(401, "Authentification requise");
    }
    e.next();
});

Hooks pour Modèles Généraux

Appliqués à tout modèle DB (records, collections, logs) : onModelValidate, onModelCreate, etc. Utilisez-les pour du code générique, mais préférez les hooks spécifiques pour la sécurité des types.

Ces hooks couvrent 100% des interactions courantes. Pour une startup, commencez par les hooks records et auth – ils résolvent 80% des besoins custom sans toucher au code Go.

4. Support TypeScript : Développez avec autocomplétion et sécurité

PocketBase supporte TypeScript nativement depuis les versions récentes, sans bundler externe. C'est un atout pour les SMB qui veulent du code maintenable.

PocketBase fournit un fichier de déclarations ambient pb_data/types.d.ts (généré automatiquement dans votre dossier data). Ajoutez cette ligne au début de vos fichiers .pb.js :

/// <reference path="../pb_data/types.d.ts" />

Votre éditeur (VS Code, WebStorm) gagne alors l'autocomplétion, la documentation inline et la vérification des types. Par exemple, tapez $app.findRecordById et voyez les params et retours.

Si vous préférez, renommez en .pb.ts et compilez manuellement en JS (avec tsc), puis placez le .js résultant dans pb_hooks. L'engine exécute du JS pur, donc TypeScript est pour le dev seulement.

Exemple avec types :

/// <reference path="../pb_data/types.d.ts" />

app.on("onRecordAfterCreateSuccess", (e: RecordHookEvent) => {
    const email: string = e.record.getString("email");
    console.log(`Nouveau user : ${email}`);
    e.next();
}, "users");

C'est transparent et gratuit – idéal pour les juniors en équipe qui évitent les erreurs de typage.

5. Exemples puissants : Applications concrètes pour votre startup

Passons à la pratique. Ces exemples montrent comment valider des données, intégrer des APIs externes, customiser des réponses et gérer des workflows. Tous testables en local.

Exemple 1 : Validation avancée et envoi d'email custom

Pour une app de e-commerce, validez un ordre et envoyez une confirmation via une API tierce (comme SendGrid, mais ici simulé avec $http).

Dans pb_hooks/orders.pb.js :

/// <reference path="../pb_data/types.d.ts" />

app.on("onRecordCreateRequest", (e: RecordCreateEvent) => {
    const total = e.record.getFloat("total");
    if (total <= 0) {
        return e.invalid("Total doit être positif");
    }
    e.next();
}, "orders");

app.on("onRecordAfterCreateSuccess", (e: RecordAfterCreateEvent) => {
    const orderId = e.record.id;
    const email = e.record.getString("customerEmail");
    
    // Appel API externe pour email
    const response = $http.send({
        method: "POST",
        url: "https://api.sendgrid.com/v3/mail/send",
        headers: { Authorization: "Bearer VOTRE_API_KEY" },
        body: {
            to: email,
            subject: "Commande confirmée",
            body: `Votre ordre ${orderId} est validé.`
        }
    });
    
    if (response.statusCode !== 202) {
        console.error("Erreur envoi email:", response.body);
    }
    
    e.next();
}, "orders");

Ça intercepte la création, valide, et envoie un email sans alourdir l'API de base.

Exemple 2 : Intégration OAuth2 custom et enrichissement de records

Pour une startup SaaS, ajoutez un hook OAuth pour tracker les logins GitHub et enrichir le profil user avec des données GitHub.

Dans pb_hooks/auth.pb.js :

app.on("onRecordAuthWithOAuth2Request", (e: RecordAuthWithOAuth2RequestEvent) => {
    if (e.oauth2User.provider === "github") {
        // Fetch infos GitHub via leur API
        const ghResponse = $http.send({
            url: `https://api.github.com/users/${e.oauth2User.userId}`,
            headers: { Authorization: "token VOTRE_GITHUB_TOKEN" }
        });
        
        if (ghResponse.ok) {
            const ghData = JSON.parse(ghResponse.body);
            e.record.set("githubAvatar", ghData.avatar_url);
            e.record.set("githubRepos", ghData.public_repos);
        }
    }
    e.next();
}, "users");

app.on("onRecordEnrich", (e: RecordEnrichEvent) => {
    if (e.record.collection().name === "users") {
        e.record.set("fullName", `${e.record.getString("firstName")} ${e.record.getString("lastName")}`);
    }
    e.next();
}, "users");

Résultat : les users ont un avatar GitHub et un nom complet calculé, sans modifier la collection.

Exemple 3 : Route API personnalisée et cleanup automatique

Créez une route /api/stats pour dashboard, et un hook pour nettoyer les logs anciens.

Dans pb_hooks/routes.pb.js :

// Route custom
routerAdd("GET", "/api/stats", (e: ApiRequest) => {
    const stats = $app.stats();
    return e.json(200, { users: stats.recordCount("users"), uptime: $os.hostname() });
});

// Cleanup logs toutes les heures (simulé via cron, mais hook sur terminate pour exemple)
app.on("onTerminate", (e: TerminateEvent) => {
    const oldLogs = $app.dao().findRecordsByFilter("logs", "created < datetime('now', '-7 days')");
    oldLogs.forEach(log => $app.dao().deleteRecord(log));
    console.log("Cleanup terminé.");
    e.next();
});

Pour les cron réels, utilisez un hook sur onBootstrap pour scheduler, mais PocketBase n'a pas de cron built-in – intégrez via $os.exec.

Exemple 4 : Sécurité renforcée avec MFA custom

Ajoutez une vérification extra pour les admins.

Dans pb_hooks/security.pb.js :

app.on("onRecordAuthRequest", (e: RecordAuthRequestEvent) => {
    const record = e.record;
    if (record && record.getBool("isAdmin")) {
        // Vérifiez MFA custom (simulé)
        const mfaToken = e.httpContext.formData().get("mfa_token");
        if (!mfaToken || mfaToken !== $security.randomString(6, record.id)) {  // Token stocké en DB
            return e.error(401, "MFA requis pour admins");
        }
    }
    e.next();
});

Ces exemples totalisent moins de 100 lignes, mais couvrent validation, intégrations, routes et sécurité.

Limites et bonnes pratiques pour une utilisation en production

JSVM est puissante, mais rappelez-vous les limites : pas d'APIs Node comme fs ou fetch (utilisez $http et $os). Pas de setTimeout pour la concurrence – tout est synchrone. Pour les calculs lourds (crypto), préférez $security.

Bonnes pratiques :

  • Chargez les modules avec require(__hooks + "/mon-module.js") pour l'isolation.
  • Utilisez TypeScript pour tout, même les petits scripts.
  • Testez en dev avec hot-reload, puis monitorez la mémoire en prod.
  • Pour ESM, bundlez avec Rollup avant require().
  • Évitez les mutations globales : chaque hook est isolé.

En 2025, JSVM reste ES5-compliant avec la plupart des ES6 features, mais vérifiez les quirks Goja (comme les maps Go en JS).

Conclusion : Étendez PocketBase sans effort pour booster votre startup

Les extensions JavaScript transforment PocketBase en un backend flexible, sans les overheads d'un framework full comme Express. Avec le dossier pb_hooks, les globals comme $app, plus de 80 hooks pour intercepter tout, et le support TypeScript, vous prototypez en heures ce qui prendrait des jours ailleurs.

Pour une SMB ou startup, c'est l'outil parfait : ajoutez de la validation, des emails custom, des intégrations APIs ou des routes sans rebuild. Commencez par un hook simple sur auth, et scalez. Téléchargez PocketBase, créez pb_hooks, et testez un exemple ci-dessus. Votre backend sera customisé en un après-midi.

Si vous avez des questions sur un hook spécifique, la doc officielle reste la référence. Prêt à étendre ? Lancez-vous ! 1win iniciar sesión

Lire la suite