Migrations PocketBase (JS & Go)

Migrations PocketBase (JS & Go) : Faire évoluer sa base sans casse – Tutoriel 2026

Dans le développement d'une application backend, votre base de données évolue constamment. Vous ajoutez de nouveaux champs pour stocker plus d'informations sur vos clients, modifiez les types de données pour mieux refléter vos besoins métier, ou supprimez des collections inutiles au fil des itérations. Sans un système pour gérer ces changements de manière contrôlée, vous risquez de perdre des données, de créer des incohérences entre les environnements de développement et de production, ou de passer des heures à reconstruire manuellement votre schéma. C'est là qu'interviennent les migrations de base de données.

PocketBase, en tant que backend open-source léger, intègre nativement un système de migrations simple et puissant. Que vous utilisiez JavaScript pour des scripts rapides ou Go pour une intégration plus robuste, ces outils vous permettent d'appliquer des modifications de schéma de façon versionnée et réversible. Dans ce tutoriel, nous allons explorer pourquoi les migrations sont essentielles, comparer les approches JS et Go, et vous guider pas à pas pour les créer, les exécuter et les annuler. À la fin, vous saurez comment les intégrer dans votre workflow quotidien, que ce soit pour un projet solo, une startup agile ou une petite entreprise qui gère ses propres données clients.

Ce guide s'adresse aux débutants en backend qui découvrent PocketBase, mais aussi aux équipes SMB qui cherchent à scaler sans complexité. Nous nous basons sur la documentation officielle de PocketBase (version actuelle au 30 novembre 2025), pour des informations précises et à jour. Prêt ? Allons-y.

1. Pourquoi les migrations ?

Imaginez que vous développez une app de gestion de tâches pour votre startup. Au début, une simple collection "tasks" avec un champ "title" et "completed" suffit. Mais au fil des semaines, vos utilisateurs demandent des priorités, des deadlines, et même des pièces jointes. Sans migrations, vous pourriez vous contenter de modifier directement la base via l'interface admin de PocketBase – pratique pour un prototype, mais risqué en production.

Les migrations résolvent ce problème en apportant une évolution contrôlée de votre schéma sans perdre de données. Voici les raisons clés de les adopter :

  • Versioning du schéma : Chaque changement est enregistré dans un fichier daté, comme un commit Git pour votre base de données. Vous pouvez tracer qui a ajouté quoi et quand, facilitant la collaboration en équipe.
  • Réversibilité : Si une mise à jour casse quelque chose (par exemple, un nouveau champ requis qui manque sur les anciens enregistrements), vous pouvez "rollback" facilement sans tout casser.
  • Déploiement sûr : En production, appliquez les migrations automatiquement au démarrage du serveur. Pas de scripts manuels oubliés qui pourraient diverger entre dev et prod.
  • Tâches one-off : Au-delà des schémas, exécutez du code pour initialiser des données par défaut, comme créer un superuser ou mettre à jour des enregistrements existants (ex. : définir un statut "pending" sur tous les articles inachevés).

Dans PocketBase, les migrations s'exécutent dans une transaction atomique, ce qui signifie que si une étape échoue, tout est annulé – zéro risque de base corrompue à moitié. Pour une startup, cela signifie déployer de nouvelles features sans downtime, et pour un SMB, cela évite les pannes coûteuses dues à des erreurs de migration manuelles.

Selon la doc officielle, les migrations gèrent tout : création/suppression de collections, ajout de champs et index, modifications de settings, ou même requêtes SQL brutes. Elles sont stockées dans un dossier dédié (pb_migrations pour JS, migrations pour Go) et trackées dans une table interne _migrations. C'est simple, mais scalable.

En résumé, si vous prévoyez d'itérer sur votre app plus de quelques semaines, les migrations ne sont pas un luxe – c'est une nécessité pour maintenir la cohérence et la fiabilité.

2. Migrations JS vs Go : Quelle approche choisir ?

PocketBase supporte deux langages pour les migrations : JavaScript (via JSVM) et Go. Le choix dépend de votre stack et de vos besoins en termes de performance et d'intégration.

Les migrations JavaScript : Pour la rapidité et la flexibilité

Les migrations JS sont idéales si vous prototypez vite ou si votre équipe est plus à l'aise avec des scripts dynamiques. Elles s'exécutent dans la machine virtuelle JS intégrée de PocketBase, sans compilation.

  • Avantages :
    • Faciles à créer : Une commande génère un fichier .js prêt à l'emploi.
    • Pas de build : Modifiez, sauvegardez, et relancez le serveur – les changements s'appliquent au prochain démarrage.
    • Parfait pour des tâches légères comme des mises à jour de données ou des settings.
  • Inconvénients :
    • Moins performantes pour des opérations lourdes (ex. : gros volumes de données).
    • Limitées aux APIs JSVM (pas d'accès direct à des libs Go natives).

Exemple de structure basique d'un fichier JS :

// pb_migrations/1687801090_mon_exemple.js
migrate(
  (app) => {
    // Logique d'upgrade ici
  },
  (app) => {
    // Logique de downgrade optionnelle
  }
);

Elles sont auto-appliquées au démarrage avec pocketbase serve, et vous pouvez les gérer manuellement via pocketbase migrate up/down.

Les migrations Go : Pour la robustesse et l'intégration native

Si vous étendez PocketBase en Go (par exemple, pour des hooks personnalisés ou un executable custom), optez pour Go. Les migrations sont des fonctions Go embarquées dans votre binaire final.

  • Avantages :
    • Type-safe : Moins d'erreurs runtime grâce à la compilation.
    • Puissantes : Accès complet à l'API Go de PocketBase, y compris des builders de requêtes avancés.
    • Portables : Une fois compilées, elles font partie de votre app unique – idéal pour des déploiements en container.
  • Inconvénients :
    • Nécessite un build (go build) après chaque changement.
    • Plus verbeux pour des tâches simples.

Structure typique :

// migrations/1655834400_mon_exemple.go
package migrations

import (
    "github.com/pocketbase/pocketbase/core"
    m "github.com/pocketbase/pocketbase/migrations"
)

func init() {
    m.Register(
        func(app core.App) error {
            // Upgrade
            return nil
        },
        func(app core.App) error {
            // Downgrade
            return nil
        },
    )
}

Pour les activer, importez le package migrations dans votre main.go et enregistrez la commande migratecmd.

Comparaison rapide

Critère JS Go
Facilité d'entrée Haute (scripts simples) Moyenne (besoin de Go)
Performance Bonne pour petits volumes Excellente pour tout
Intégration Limité à JSVM Native au core PocketBase
Use case idéal Prototypage, startups agiles Apps scalables, SMB custom

Pour une startup, commencez par JS pour la vitesse ; passez à Go si vous ajoutez des features complexes. Dans les deux cas, les migrations trackent l'historique via la table _migrations, et supportent l'automigration (génération auto lors des changements via l'UI admin).

3. Création, exécution et rollback des migrations

Maintenant, passons à la pratique. Nous allons créer une migration, l'exécuter, et voir comment la révoquer. Ces étapes sont similaires en JS et Go, mais avec des commandes adaptées.

Étape 1 : Création d'une migration

Démarrez PocketBase dans un dossier vide pour tester (téléchargez le binaire depuis pocketbase.io).

En JavaScript :

  • Générez un squelette : pocketbase migrate create "ajout_champ_priorite". Cela crée pb_migrations/UN_TIMESTAMP_ajout_champ_priorite.js.
  • Éditez le fichier pour ajouter votre logique (voir exemples plus bas).
  • Option : Pour snapshot une collection entière : pocketbase migrate collections "snapshot_clients". Cela génère un fichier qui importe le schéma actuel via app.importCollectionsByMarshaledJSON(data, false) (le false préserve les champs non snapshotés ; mettez true pour les supprimer).

En Go :

  • Dans main.go, ajoutez l'import et l'enregistrement de migratecmd (voir code ci-dessus).
  • Générez : go run . migrate create "ajout_champ_priorite". Fichier créé dans migrations/.
  • Éditez, puis go mod tidy et relancez.

Les deux approches utilisent des fonctions up (upgrade) et down (downgrade optionnelle), recevant une instance app transactionnelle.

Étape 2 : Exécution

  • Automatique : Lancez pocketbase serve (ou go run main.go en Go). Les nouvelles migrations s'appliquent au démarrage.
  • Manuelle : pocketbase migrate up (ou go run . migrate up). Vérifiez l'historique : pocketbase migrate status.
  • Après exécution manuelle, redémarrez le serveur pour rafraîchir le cache des collections.

Étape 3 : Rollback

  • Révoquez la dernière : pocketbase migrate down (ou go run . migrate down).
  • Pour N migrations : pocketbase migrate down 2.
  • La fonction down s'exécute ; gérez les erreurs silencieusement (ex. : si un record à supprimer n'existe plus).

Astuce : En dev, activez --automigrate pour générer auto des fichiers lors des changements UI (désactivez en prod pour éviter le clutter).

4. Exemples concrets

Mettons ça en action avec des cas réels, adaptés à une app de gestion clients pour SMB.

Exemple 1 : Mise à jour de données existantes (SQL brute)

Vous avez une collection "articles" avec un champ "status" vide ; mettez-le à "pending".

JS :

// pb_migrations/1687801090_set_pending_status.js
migrate((app) => {
  app.db().newQuery("UPDATE articles SET status = 'pending' WHERE status = ''").execute();
}, null);  // Pas de down needed

Go :

// migrations/1687801090_set_pending_status.go
func init() {
  m.Register(
    func(app core.App) error {
      _, err := app.DB().NewQuery("UPDATE articles SET status = 'pending' WHERE status = ''").Execute()
      return err
    },
    nil,
  )
}

Exécutez migrate up : tous les status sont mis à jour atomiquement.

Exemple 2 : Initialisation de settings par défaut

Pour une nouvelle app, définissez un nom et une URL.

JS :

// pb_migrations/1687801090_initial_settings.js
migrate((app) => {
  let settings = app.settings();
  settings.meta.appName = "Gestion Clients SMB";
  settings.meta.appURL = "https://votreapp.pocketbase.fr";
  settings.logs.maxDays = 30;  // Gardez 30 jours de logs
  app.save(settings);
}, null);

Go (similaire, avec app.Settings() et app.Save()).

Exemple 3 : Création d'un superuser initial

Pour l'onboarding d'une startup.

JS :

// pb_migrations/1687801090_initial_superuser.js
migrate(
  (app) => {
    let superusers = app.findCollectionByNameOrId("_superusers");
    let record = new Record(superusers);
    record.set("email", "[email protected]");
    record.set("password", "motdepasse_securise123");
    app.save(record);
  },
  (app) => {
    try {
      let record = app.findAuthRecordByEmail("_superusers", "[email protected]");
      app.delete(record);
    } catch { /* ignore if not exists */ }
  }
);

Go : Utilisez core.NewRecord() et app.FindAuthRecordByEmail().

Exemple 4 : Création d'une collection "clients"

Ajoutez une collection auth pour vos clients, avec champs company et website.

JS :

// pb_migrations/1687801090_create_clients.js
migrate(
  (app) => {
    let collection = new Collection({
      type: "auth",
      name: "clients",
      listRule: "id = @request.auth.id",
      viewRule: "id = @request.auth.id",
      fields: [
        { type: "text", name: "company", required: true, max: 100 },
        { name: "website", type: "url", presentable: true }
      ],
      passwordAuth: { enabled: false },  // Utilisez OTP
      otp: { enabled: true },
      indexes: ["CREATE INDEX idx_clients_company ON clients (company)"]
    });
    app.save(collection);
  },
  (app) => {
    let collection = app.findCollectionByNameOrId("clients");
    app.delete(collection);
  }
);

Go : Utilisez core.NewAuthCollection("clients"), ajoutez des fields avec *core.TextField{}, et collection.AddIndex().

Pour un snapshot : migrate collections "clients_snapshot" – éditez pour true si vous voulez supprimer les absents.

Ces exemples montrent comment PocketBase rend les migrations accessibles : code simple, exécution sûre.

5. Bonnes pratiques pour le versioning

Pour que vos migrations restent un atout et non un fardeau, suivez ces règles tirées de la doc officielle.

  • Nettoyez en développement : L'automigration (--automigrate) génère des fichiers pour chaque changement UI. Une fois stable, supprimez les inutiles et synchronisez : pocketbase migrate history -sync. Cela nettoie la table _migrations sans fichier correspondant.
  • Committez tout : Les fichiers de migration sont safe pour Git. Partagez-les avec votre équipe – appliquez migrate up sur un nouveau clone pour synchroniser.
  • Gérez les modes snapshot : Par défaut, migrate collections est en "extend" (ajoute sans supprimer). Passez à "replace" (true) seulement si sûr.
  • Erreurs silencieuses en down : Utilisez try/catch (JS) ou checks nil (Go) pour éviter les paniques si un rollback cible n'existe plus.
  • Redémarrez après manuel : Toujours relancer serve post-up/down pour rafraîchir le cache.
  • Évitez en prod : Désactivez automigrate ; appliquez manuellement ou au démarrage.

Pour une startup, intégrez les migrations à votre CI/CD : un hook Git qui run migrate up sur deploy. Pour SMB, testez toujours en staging.

En appliquant ces pratiques, vos évolutions de base restent traçables et sans friction.

Conclusion : Migrez sereinement vers un backend évolutif

Les migrations JS et Go de PocketBase transforment la gestion de votre base en un processus fluide et sécurisé. Vous avez vu pourquoi elles évitent les pièges courants, comment les implémenter pas à pas, et des exemples concrets pour démarrer. Que vous lanciez une MVP en JS pour itérer vite ou un système custom en Go pour scaler, ces outils s'adaptent à votre rythme.

Testez-les dès maintenant sur un projet test : créez une migration simple, appliquez-la, et rollbackez. Vous verrez à quel point c'est straightforward. Si vous rencontrez des blocages, la doc officielle reste votre meilleur allié.

Prêt pour la suite ? Dans le prochain article, on plonge dans les collections. Dites-moi en commentaires ce que vous aimeriez approfondir !

Lire la suite