Utiliser PocketBase comme Framework Full-Stack en 2026

Utiliser PocketBase comme Framework Full-Stack en 2026

PocketBase est un outil backend open-source qui se compile en un seul fichier exécutable. Il inclut une base de données SQLite, une API REST et WebSocket, un système d'authentification et une interface admin. Au-delà de ces fonctionnalités de base, PocketBase peut servir de framework pour étendre votre application. Cela signifie que vous pouvez ajouter des routes personnalisées, des middlewares et même servir des fichiers statiques ou une application single-page (SPA) directement depuis le même binaire.

Pour les débutants, les petites entreprises (SMB) ou les startups, cette approche est intéressante car elle simplifie le développement. Au lieu d'assembler plusieurs outils comme Express pour les routes API et Prisma pour la base de données, PocketBase centralise tout. Vous évitez les configurations complexes et réduisez les dépendances. Dans cet article, nous explorons comment transformer PocketBase en un framework full-stack. Nous couvrons les bases, les routes custom en Go et JavaScript, les middlewares, la gestion de sites statiques ou SPA, et des exemples concrets. Tout est expliqué étape par étape, avec du code simple à copier-coller.

1. Pourquoi l’utiliser comme framework ?

PocketBase est conçu pour être extensible dès le départ. Par défaut, il gère les collections de données, l'authentification et les APIs standards. Mais en l'utilisant comme framework, vous accédez à son noyau pour ajouter de la logique métier personnalisée. Voici les raisons principales pour les équipes petites ou en croissance :

  • Simplicité et portabilité : Tout se compile en un fichier unique (par exemple, pocketbase.exe sur Windows). Pas besoin de serveurs séparés pour l'API, la DB ou les assets statiques. Pour une startup, cela signifie un déploiement facile sur un VPS, Heroku ou un conteneur Docker.
  • Performances natives : Écrit en Go, PocketBase est rapide et léger. Ajouter des extensions en Go garde les performances élevées, sans overhead JavaScript côté serveur. Pour les SMB gérant des milliers d'utilisateurs, c'est un gain de ressources.
  • Extensibilité sans friction : Vous pouvez injecter du code Go ou JavaScript via des hooks d'événements ou des routes custom. Cela permet de personnaliser sans réécrire le backend. Par exemple, intégrez un paiement Stripe ou envoyez des notifications push sans outils externes.
  • Coût zéro et open-source : Pas de licences, et la communauté grandit. En 2025, avec les mises à jour récentes sur les hooks realtime et le support S3 pour les fichiers, c'est idéal pour prototyper vite et scaler plus tard.
  • Sécurité intégrée : Les routes custom héritent des règles d'accès et de l'authentification de PocketBase. Vous n'avez pas à implémenter CORS, JWT ou validation de base de zéro.

En résumé, pour un débutant, c'est une rampe de lancement : commencez par l'admin UI, puis étendez. Pour une startup, c'est scalable sans refactoring majeur. Comparé à Express + Prisma, PocketBase remplace les deux en un, avec moins de boilerplate. Mais attention : si votre app grandit énormément, évaluez si Go convient à votre équipe.

2. Routes custom en Go et JavaScript

Les routes custom permettent d'ajouter des endpoints API au-delà des standards (comme /api/collections). PocketBase expose un routeur HTTP que vous pouvez étendre en Go (pour la performance) ou en JavaScript (pour la rapidité de développement).

Routes custom en Go

Pour étendre en Go, vous modifiez le code source de PocketBase ou créez une app personnalisée. Téléchargez le repo GitHub (github.com/pocketbase/pocketbase) et importez-le dans GoLand ou VS Code.

Étapes de base :

  1. Installez Go (version 1.21+ recommandée en 2025).
  2. Clonez le repo : go get github.com/pocketbase/pocketbase.
  3. Dans main.go, après l'initialisation de l'app, ajoutez des routes via app.OnBeforeServe().Add(func(e *core.ServeEvent) error { ... }).

Exemple simple : une route GET /hello qui retourne "Hello World".

package main

import (
    "log"
    "net/http"

    "github.com/pocketbase/pocketbase"
    core "github.com/pocketbase/pocketbase/core"
)

func main() {
    app := pocketbase.New()

    // Route custom
    app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
        e.Router.AddRoute(echo.Route{
            Method: "GET",
            Path:   "/hello",
            Handler: func(c echo.Context) error {
                return c.String(http.StatusOK, "Hello World from PocketBase!")
            },
        })
        return nil
    })

    if err := app.Start(); err != nil {
        log.Fatal(err)
    }
}

Compilez avec go build. Lancez ./pocketbase. Testez avec curl http://127.0.0.1:8090/hello.

Pour des routes plus complexes, comme une POST /api/custom/user qui crée un utilisateur avec validation :

  • Utilisez app.Dao() pour interagir avec la DB.
  • Ajoutez des middlewares pour l'auth (voir section suivante).

Avantages pour SMB : Code natif, rapide. Inconvénient : Nécessite de recompiler à chaque changement.

Routes custom en JavaScript

Pour JS, utilisez le dossier pb_hooks à la racine de votre instance PocketBase. Créez un fichier pb_hooks/hello.js avec du code qui s'exécute au démarrage.

Exemple pour la même route /hello :

Dans pb_hooks/index.js (fichier principal) :

const pocketbase = require('pocketbase');

routerAdd("GET", "/hello", (c) => {
  return c.string(200, "Hello World from JS!");
});

PocketBase charge automatiquement les hooks JS via JSVM (JavaScript Virtual Machine). Redémarrez le serveur. La route est disponible.

Pour une route POST avec DB :

routerAdd("POST", "/api/custom/user", (c) => {
  const body = c.body();
  // Validation simple
  if (!body.name) {
    return c.json(400, { error: "Name required" });
  }
  // Sauvegarde en DB via $app.dao()
  const record = new Record("users", body);
  await $app.dao().saveRecord(record);
  return c.json(200, { id: record.id });
});

Avantages pour startups : Pas de compilation, éditez et redémarrez. Support TypeScript en 2025 via pb_hooks/tsconfig.json. Inconvénient : Plus lent que Go pour les tâches intensives.

Choisissez Go pour les cores critiques, JS pour les prototypes.

3. Middlewares

Les middlewares interceptent les requêtes avant qu'elles atteignent les handlers. Dans PocketBase, ils s'ajoutent globalement ou par route, pour logging, auth, rate limiting ou CORS.

Middlewares en Go

Utilisez Echo (le routeur sous-jacent de PocketBase). Ajoutez-les dans OnBeforeServe().

Exemple : Middleware d'auth pour vérifier un token JWT avant une route custom.

app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
    e.Router.Pre(middlewareFunc) // Global
    return nil
})

func middlewareFunc(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        token := c.Request().Header.Get("Authorization")
        if !validateToken(token) { // Fonction custom
            return c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token"})
        }
        return next(c)
    }
}

Appliquez à une route spécifique :

e.Router.AddRoute(echo.Route{
    Method:  "GET",
    Path:    "/protected",
    Handler: middlewareFunc(handlerFunc),
})

Pour du rate limiting, intégrez github.com/labstack/echo-contrib (importé dans votre app custom).

Middlewares en JS

En JSVM, les middlewares sont des fonctions routerAdd avec une logique de pré-filtrage.

Exemple d'auth middleware :

const authMiddleware = async (c) => {
  const token = c.header("Authorization");
  if (!token || !await validateToken(token, $app)) {
    return c.json(401, { error: "Unauthorized" });
  }
  return NEXT(); // Continue
};

routerAdd("GET", "/protected", authMiddleware, (c) => {
  return c.json(200, { message: "Protected data" });
});

NEXT() passe à la suite. Pour global, ajoutez dans pb_hooks/app/bootstrap.js.

Best practices :

  • Loggez toujours les erreurs avec $app.logger().
  • Gérez les exceptions pour éviter les crashes.
  • Pour les startups, commencez par des middlewares simples ; ajoutez du caching (Redis via Go) plus tard.

Ces middlewares héritent de la sécurité PocketBase, comme les règles sur les collections.

4. Servir un site statique ou SPA

PocketBase excelle pour servir des assets directement, sans serveur web séparé comme Nginx. Cela réduit la latence et simplifie les déploiements.

Servir des fichiers statiques

Placez vos fichiers (HTML, CSS, JS, images) dans un dossier ./pb_public (configurable via PB_PUBLICASSETS). PocketBase les sert sous /.

Étapes :

  1. Créez ./pb_public/index.html avec du contenu basique.
  2. Dans app.config, settez publicAssetsPath = "./pb_public".
  3. Accédez via http://localhost:8090/ – PocketBase mappe automatiquement.

Exemple pour un site statique simple :

  • pb_public/index.html : <h1>Mon Site SMB</h1><script src="app.js"></script>
  • pb_public/app.js : Code vanilla JS appelant l'API PocketBase (fetch('/api/collections/posts/records')).

Pour la production, activez la compression GZIP en Go :

app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
    e.Router.Pre(middleware.GzipWithConfig(middleware.GzipConfig{Level: 5}))
    return nil
})

Servir une SPA (React, Vue, Svelte)

Pour une SPA, build votre frontend (ex. npm run build pour React) dans ./pb_public. PocketBase sert le index.html pour toutes les routes non-API (fallback pour le routing client-side).

Configuration en Go :

app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
    // Servir SPA pour paths non trouvés (sauf API)
    e.Router.Any("/*", func(c echo.Context) error {
        if strings.HasPrefix(c.Path(), "/api/") {
            return echo.ErrNotFound
        }
        return c.File("pb_public/index.html")
    })
    return nil
})

En JS, similaire dans un hook :

$app.onBeforeServe((e) => {
  e.router.addFallbackRoute((c) => {
    if (c.path().startsWith("/api/")) return NEXT();
    return c.file("./pb_public/index.html");
  });
});

Intégrez l'API : Dans votre SPA, utilisez le SDK JS (npm i pocketbase) pour auth et queries. Exemple React :

import PocketBase from 'pocketbase';
const pb = new PocketBase('http://127.0.0.1:8090');

function App() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    pb.collection('users').authWithPassword(email, password).then(setUser);
  }, []);
}

Pour les startups : Cela permet un stack full-stack monolithique au début, puis split si besoin. Pitfalls : Assurez-vous que les builds SPA écrasent pas les assets PB (utilisez des paths distincts comme /static/).

En 2025, avec le support ESM natif en JSVM, les SPAs hybrides (SSR partiel) sont plus faciles.

5. Exemples complets

Passons à des cas pratiques pour consolider.

Exemple 1 : API custom pour un blog SMB (Go)

Créez une route /api/blog/posts qui liste des posts filtrés par user auth.

Dans main.go :

app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
    e.Router.AddRoute(echo.Route{
        Method: "GET",
        Path:   "/api/blog/posts",
        Handler: func(c echo.Context) error {
            user := e.App.AuthStore.Get(c)
            if user == nil {
                return c.JSON(401, map[string]string{"error": "Auth required"})
            }
            records, err := e.App.Dao().FindRecordsByFilter("posts", "user = ?", user.Id)
            if err != nil {
                return c.JSON(500, map[string]string{"error": err.Error()})
            }
            return c.JSON(200, records)
        },
    })
    return nil
})

Compilez et testez. Cela utilise la DB native, avec auth intégrée.

Exemple 2 : Hook JS pour notifications email (Startup use case)

Dans pb_hooks/email.js :

onRecordAfterCreateRequest((e) => {
  if (e.collection.name === "orders") {
    const userEmail = e.record.get("user_email");
    $app.mailer().send({
      to: userEmail,
      subject: "Nouvelle commande",
      body: `<p>Merci pour votre achat ! ID: ${e.record.id}</p>`,
    });
  }
  return e.next();
});

Redémarrez. Chaque création d'ordre envoie un email via le mailer intégré (configurez SMTP dans l'admin UI).

Exemple 3 : SPA Vue.js servie avec middleware rate limit

Build Vue en ./pb_public. Ajoutez un middleware JS pour limiter à 100 req/min par IP.

Dans pb_hooks/rate.js :

const rates = new Map(); // Simple store in-memory

const rateMiddleware = (c) => {
  const ip = c.realIP();
  const now = Date.now();
  const window = 60 * 1000; // 1 min
  const userRates = rates.get(ip) || [];
  const valid = userRates.filter(time => now - time < window);
  if (valid.length >= 100) {
    return c.json(429, { error: "Rate limit exceeded" });
  }
  valid.push(now);
  rates.set(ip, valid);
  return NEXT();
};

routerAdd("GET", "/api/highload/*", rateMiddleware, (c) => {
  // Handler
});

Pour une startup, cela protège contre les abus sans Redis externe.

Exemple 4 : Intégration paiement Stripe (Go + Static)

Ajoutez une route POST /pay qui crée un checkout Stripe.

Importez go.stripe/stripe-go dans votre app custom.

import "github.com/stripe/stripe-go/v72"

func handler(c echo.Context) error {
    params := stripe.CheckoutSessionParams{
        LineItems: []*stripe.CheckoutSessionLineItemParams{
            {Price: stripe.String("price_id"), Quantity: stripe.Int64(1)},
        },
        Mode: stripe.String("payment"),
        SuccessURL: stripe.String("http://localhost:8090/success"),
    }
    session, _ := session.New(&params)
    return c.JSON(200, map[string]string{"url": session.URL})
}

Servez la page succès comme statique. Testez en dev mode.

Ces exemples montrent comment PocketBase s'adapte : du blog simple au e-commerce basique.

Conclusion

Utiliser PocketBase comme framework full-stack est une option puissante pour les débutants, SMB et startups. Vous gagnez en simplicité, avec des routes custom, middlewares et serving de SPA intégrés. Commencez par JS pour tester, passez à Go pour scaler. En 2025, les outils comme JSVM et les hooks realtime facilitent les extensions. Téléchargez, expérimentez et déployez – votre backend sera prêt en heures, pas en jours.

Pour aller plus loin : Consultez la doc officielle et rejoignez le Discord PocketBase. Si vous avez des questions, commentez ci-dessous.

Lire la suite