Développement Web

Hooks Go Pocketbase : étendre votre backend en 2026

11 min de lecture

L’une des forces cachées de Pocketbase est qu’il n’est pas qu’un binaire pré-compilé : c’est aussi un framework Go que vous pouvez étendre avec votre propre code. Cela ouvre des possibilités énormes : intégration avec un service de paiement Mobile Money, calculs complexes, validation métier, logique transactionnelle, intégration WhatsApp Business, agrégations en temps réel. Voici le guide pour utiliser les hooks Go Pocketbase en 2026 (informations vérifiées en avril 2026, susceptibles d’évoluer).

Voir le guide pratique Pocketbase pour les bases.

Pourquoi étendre Pocketbase

  • Validation côté serveur impossible via API rules (calculs complexes, dépendance externe)
  • Side-effects après un événement : envoi WhatsApp, déclenchement Mobile Money, notification Slack
  • Endpoints custom HTTP qui ne correspondent pas à une collection
  • Intégration avec services tiers (Wave, Orange Money, OpenAI, etc.)
  • Agrégation de données ou requêtes complexes non exprimables en filter Pocketbase

Étape 1 — Setup projet Go

mkdir mon-pb && cd mon-pb
go mod init mon-pb
go get github.com/pocketbase/pocketbase

# Structure
mon-pb/
├── main.go
├── go.mod
├── go.sum
└── pb_data/   (créé au premier run)

Étape 2 — main.go basique

package main

import (
    "log"
    "github.com/pocketbase/pocketbase"
    "github.com/pocketbase/pocketbase/core"
)

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

    // Hook : avant la création d'un nouveau post
    app.OnRecordCreateRequest("posts").BindFunc(func(e *core.RecordRequestEvent) error {
        title := e.Record.GetString("title")
        if len(title) < 5 {
            return e.BadRequestError("Titre trop court", nil)
        }
        return e.Next()
    })

    // Hook : après création
    app.OnRecordAfterCreateSuccess("posts").BindFunc(func(e *core.RecordEvent) error {
        log.Printf("Post créé: %s", e.Record.GetString("title"))
        // Envoyer notification, indexer dans search, etc.
        return e.Next()
    })

    if err := app.Start(); err != nil {
        log.Fatal(err)
    }
}
# Build et run
go build -o pocketbase
./pocketbase serve --http=0.0.0.0:8090

Étape 3 — Hooks principaux disponibles

  • OnBootstrap : au démarrage de Pocketbase
  • OnRecordCreateRequest("collection"), OnRecordUpdateRequest, OnRecordDeleteRequest : avant validation côté API
  • OnRecordAfterCreateSuccess, OnRecordAfterUpdateSuccess : après écriture en base
  • OnMailerSend : intercepter envoi d’email
  • OnRealtimeMessageSend : intercepter messages WebSocket

Étape 4 — Endpoint HTTP custom

app.OnServe().BindFunc(func(e *core.ServeEvent) error {
    e.Router.GET("/api/stats/dashboard", func(c *core.RequestEvent) error {
        // Compter les posts publiés
        var count int
        err := app.DB().NewQuery("SELECT COUNT(*) FROM posts WHERE published = 1").Row(&count)
        if err != nil {
            return c.InternalServerError("erreur", err)
        }
        return c.JSON(200, map[string]int{"published_posts": count})
    })
    return e.Next()
})

Étape 5 — Cas concret : Mobile Money

Hook qui crée automatiquement une session de paiement Wave quand une commande est créée :

app.OnRecordAfterCreateSuccess("orders").BindFunc(func(e *core.RecordEvent) error {
    amount := e.Record.GetInt("amount")
    orderID := e.Record.Id

    sessionID, paymentURL, err := createWaveSession(amount, orderID)
    if err != nil {
        log.Printf("Wave session error: %v", err)
        return e.Next()
    }

    e.Record.Set("waveSessionId", sessionID)
    e.Record.Set("paymentUrl", paymentURL)
    if err := e.App.Save(e.Record); err != nil {
        return err
    }

    return e.Next()
})

// createWaveSession appelle l'API Wave et retourne sessionID + URL
func createWaveSession(amount int, orderID string) (string, string, error) {
    // Cf. notre tutoriel Wave : itskillscenter.io/api-wave-nodejs-integration-2026/
    // ...
}

Étape 6 — Migrations versionnées

Pour gérer les changements de schéma de manière reproductible (équipe, environnements multiples), utilisez les migrations Pocketbase :

# Générer une migration depuis l'état actuel
./pocketbase migrate collections

# Créer une migration vide pour modifier
./pocketbase migrate create "add_publish_field"

# Appliquer
./pocketbase migrate up

Étape 7 — Compiler et déployer

# Compiler statiquement pour Linux x86_64
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o pocketbase

# Pousser sur Coolify via Git + Dockerfile
# Voir : itskillscenter.io/pocketbase-deployer-coolify-tutoriel/

Adaptation Afrique de l’Ouest

Pocketbase + hooks Go est particulièrement puissant pour les PME ouest-africaines : un seul backend qui gère auth, données métier, et logique de paiement Wave/Orange Money/Mixx by Yas (ex-Free Money). Coût d’hébergement minimum (4 €/mois Hetzner CX23), maintenance réduite, scaling vertical simple.

Erreurs fréquentes

ErreurCauseSolution
Build CGO errorSQLite C dépendancesCGO_ENABLED=0 (Pocketbase utilise modernc/sqlite Go pur)
Hook ne se déclenche pasMauvais nom de collectionVérifier exact case-sensitive
Panic dans hookPas de defer recoverToujours catch errors et returner
Migration casséeConflit entre dashboard et fichierPréférer migrate workflow strict

Pour explorer plus loin

Pourquoi ecrire les hooks PocketBase en Go plutot qu’en JavaScript

PocketBase expose deux moteurs d’extension : un runtime JavaScript embarque (Goja) accessible via le dossier pb_hooks, et la possibilite d’utiliser PocketBase comme framework Go en l’important dans votre propre binaire. Pour une PME tech a Dakar ou Abidjan qui mutualise un VPS a 5 EUR par mois (3280 FCFA au taux fixe 1 EUR = 655,957 FCFA), la version Go consomme moins de RAM, demarre en 30 ms et n’embarque pas l’interpreteur JS — utile sur un VPS Contabo VPS S a 1 Go de RAM.

Le tradeoff : il faut compiler un binaire a chaque modification, donc un poste de developpement avec Go 1.23 installe ou un pipeline CI Github Actions. Si votre equipe maitrise mieux JavaScript, restez sur pb_hooks — la difference de performance est marginale en dessous de 100 requetes/seconde.

Etape 1 : Initialiser le projet Go

Sur votre poste de travail, creez un dossier dedie et initialisez un module Go. La derniere version stable de PocketBase est la 0.23 (sortie debut 2026), compatible Go 1.23+.

mkdir mon-pb-app && cd mon-pb-app
go mod init github.com/votre-org/mon-pb-app
go get github.com/pocketbase/pocketbase@latest

La commande telecharge PocketBase et ses dependances dans go.sum. Si vous voyez une erreur de version, verifiez avec go version que vous etes sur 1.23 ou superieur. Sur Ubuntu 24.04, le paquet apt golang-go est encore en 1.22 : preferez le tarball officiel depuis go.dev/dl.

Etape 2 : Ecrire le main.go minimal

Creez un fichier main.go a la racine. Ce code reproduit le binaire pocketbase serve standard, mais en Go pour pouvoir greffer des hooks ensuite.

package main

import (
    "log"
    "github.com/pocketbase/pocketbase"
)

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

Compilez et lancez : go run main.go serve --http=127.0.0.1:8090. L’interface admin est accessible sur http://127.0.0.1:8090/_/. Au premier lancement, creez un compte super-admin (email + mot de passe). PocketBase cree automatiquement un dossier pb_data avec la base SQLite.

Etape 3 : Hook OnRecordCreateRequest pour valider un format

Premier hook concret : avant la creation d’un enregistrement dans la collection users, on verifie que le numero de telephone respecte le format E.164 (+221 pour le Senegal, +225 pour la Cote d’Ivoire). Modifiez main.go pour ajouter cette logique avant app.Start().

import (
    "regexp"
    "github.com/pocketbase/pocketbase/core"
)

re := regexp.MustCompile(`^\+(221|225|226|223|227)[0-9]{8,9}$`)

app.OnRecordCreateRequest("users").BindFunc(func(e *core.RecordRequestEvent) error {
    phone := e.Record.GetString("phone")
    if phone != "" && !re.MatchString(phone) {
        return e.BadRequestError("Format de telephone invalide", nil)
    }
    return e.Next()
})

Recompilez et testez via curl. Une requete POST avec phone=771234567 doit renvoyer 400 ; avec phone=+221771234567 elle reussit. Le e.Next() est crucial : sans lui, la chaine de hooks s’interrompt et l’enregistrement n’est jamais cree.

Etape 4 : Hook OnRecordAfterCreate pour declencher un webhook

Apres creation reussie d’une commande dans la collection orders, on notifie un service externe (Zapier, n8n auto-heberge ou un microservice de facturation). On utilise une goroutine pour ne pas bloquer la reponse HTTP au client.

import (
    "bytes"
    "encoding/json"
    "net/http"
)

app.OnRecordAfterCreateSuccess("orders").BindFunc(func(e *core.RecordEvent) error {
    go func() {
        payload, _ := json.Marshal(map[string]any{
            "id":    e.Record.Id,
            "total": e.Record.GetFloat("total"),
        })
        http.Post("https://hooks.exemple.io/orders", "application/json", bytes.NewReader(payload))
    }()
    return e.Next()
})

Le hook s’execute apres validation et ecriture en base, donc l’ID est garanti present. Si le webhook externe echoue, on perd la notification : pour un cas critique (paiement Wave ou Mixx by Yas confirme), persistez plutot l’evenement dans une collection events_outbox et faites un worker qui retry.

Etape 5 : Hook OnRecordAuthRequest pour bloquer une zone geo

Cas d’usage interne : interdire la connexion depuis certaines IP (anti-bot ou conformite). On greffe le hook avant l’authentification pour rejeter en amont.

app.OnRecordAuthRequest("users").BindFunc(func(e *core.RecordAuthRequestEvent) error {
    ip := e.RealIP()
    if isBlocked(ip) {
        return e.ForbiddenError("Acces refuse depuis votre region", nil)
    }
    return e.Next()
})

La fonction isBlocked peut interroger une base GeoLite2 de MaxMind (gratuite avec compte) ou une simple liste noire en RAM. Pour une PME, charger MaxMind au demarrage et garder en memoire suffit largement — la base pese 7 Mo.

Etape 6 : Migrations programmatique en Go

PocketBase supporte les migrations declaratives via JSON, mais en mode Go on peut les ecrire en code et les versionner avec git. Creez un dossier migrations/ a cote de main.go et un fichier 1_init_collections.go.

package migrations

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

func init() {
    m.Register(func(app core.App) error {
        coll := core.NewBaseCollection("invoices")
        coll.Fields.Add(&core.TextField{Name: "ref", Required: true})
        coll.Fields.Add(&core.NumberField{Name: "amount_xof"})
        return app.Save(coll)
    }, nil)
}

Importez le package dans main.go avec _ "github.com/votre-org/mon-pb-app/migrations". Lancez go run main.go migrate up pour appliquer. La table _migrations enregistre l’historique : impossible de rejouer deux fois la meme migration.

Etape 7 : Compiler pour le VPS et deployer

Pour un VPS Linux x86_64, compilez en cross-build depuis votre Mac ou Windows. Le binaire est statique, donc pas de dependance libc a gerer.

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o pb-app -ldflags="-s -w" .
scp pb-app user@vps:/srv/pocketbase/
ssh user@vps "cd /srv/pocketbase && ./pb-app serve --http=127.0.0.1:8090"

Le binaire fait environ 25 Mo. Les flags -s -w retirent les symboles de debug et reduisent la taille de 30 %. Sur le VPS, faites pointer un service systemd vers ce binaire pour qu’il redemarre apres reboot.

Etape 8 : Service systemd et reverse-proxy Caddy

Creez /etc/systemd/system/pocketbase.service avec un user dedie pocketbase (sans shell). Caddy ecoute en 443 et fait le TLS automatique via Let’s Encrypt.

[Unit]
Description=PocketBase
After=network.target

[Service]
Type=simple
User=pocketbase
WorkingDirectory=/srv/pocketbase
ExecStart=/srv/pocketbase/pb-app serve --http=127.0.0.1:8090
Restart=on-failure

[Install]
WantedBy=multi-user.target

Activez : sudo systemctl enable --now pocketbase. La sortie de systemctl status pocketbase doit afficher active (running). Cote Caddy, un Caddyfile minimal de 3 lignes (api.exemple.io { reverse_proxy 127.0.0.1:8090 }) suffit pour publier en HTTPS.

Voir aussi notre guide Supabase Auth OAuth et notre tutoriel Tailscale SSH bastion.

Etape 9 : Sauvegarde automatique de pb_data

La base SQLite et les fichiers uploades vivent dans /srv/pocketbase/pb_data. En cas de crash disque ou d’erreur humaine (DELETE FROM users sans WHERE), seule une sauvegarde permet la reprise. PocketBase fournit une API admin de backup et un cron interne pour automatiser.

# Snapshot a la demande via API
curl -X POST https://api.exemple.io/api/backups \
  -H "Authorization: Bearer " \
  -H "Content-Type: application/json" \
  -d '{"name":"backup-manuel.zip"}'

L’archive zip contient toute la base et les fichiers, telechargeable depuis l’interface admin section Backups. Pour automatiser, planifiez la creation toutes les 6 heures et l’export vers un bucket S3 compatible (Backblaze B2 a 6 USD/To/mois).

Etape 10 : Restauration et test de reprise

Une sauvegarde non testee n’existe pas. Une fois par mois, restaurez le dernier backup sur un VPS de staging et verifiez que l’app demarre, que les utilisateurs peuvent se connecter et que les hooks Go reagissent.

# Sur le VPS de staging
sudo systemctl stop pocketbase
rm -rf /srv/pocketbase/pb_data
unzip /tmp/backup-manuel.zip -d /srv/pocketbase/pb_data
sudo chown -R pocketbase:pocketbase /srv/pocketbase/pb_data
sudo systemctl start pocketbase

Apres redemarrage, connectez-vous a /_/ avec les credentials admin pre-existants. Si la connexion echoue, l’archive est corrompue ou le chown n’a pas ete fait correctement. Documentez le temps de restauration : pour une PME, viser moins de 30 minutes RTO.

FAQ : questions frequentes sur PocketBase en Go

Quelle difference entre OnRecordCreateRequest et OnRecordAfterCreate ?

Le premier s’execute avant la validation et l’ecriture en base : utilisez-le pour bloquer ou modifier la requete. Le second s’execute apres ecriture reussie : utilisez-le pour declencher des effets de bord (webhook, email, log). Confondre les deux mene a des hooks qui s’executent sur des records non valides.

PocketBase tient-il la charge pour 10 000 utilisateurs simultanes ?

SQLite supporte tres bien les lectures concurrentes (mode WAL active par defaut), mais sature en ecriture vers 1000 transactions/seconde. Pour une PME en demarrage avec moins de 1000 utilisateurs actifs simultanes, c’est largement suffisant. Au-dela, migrez vers Postgres en gardant l’API PocketBase via un fork comunautaire.

Comment versionner les hooks avec git proprement ?

Comme tout code Go, le projet PocketBase tient dans un depot git classique. Excluez pb_data du versionnement via .gitignore (la base evolue a chaque requete). Pour partager une base de demarrage avec l’equipe, exportez les schemas via la commande migrations collections puis commitez les fichiers JSON dans pb_migrations.

Peut-on mixer hooks Go et hooks JavaScript ?

Oui mais deconseille : la priorite d’execution n’est pas garantie entre les deux moteurs. Pour une equipe a Dakar ou Abidjan qui livre vite, choisissez un seul moteur des le depart. Migrer un hook JS vers Go prend 15 minutes, l’inverse aussi : la decision n’est jamais definitive.

Resume operationnel

En 10 etapes, vous passez d’un binaire pocketbase serve generique a une app Go custom avec hooks de validation, webhooks externes, migrations versionnees, sauvegardes automatisees et deploiement systemd. Comptez 4 heures pour un developpeur Go intermediaire, sauvegardes incluses.

Partager