PocketBase : un backend complet dans un seul fichier, ça tient vraiment ?
Base de données, authentification, fichiers, temps réel et interface d'admin dans un exécutable de quelques dizaines de Mo : installer PocketBase, l'utiliser depuis le front, l'étendre et savoir quand s'arrêter.
Un side project démarre toujours de la même façon. On a l'idée, on ouvre l'éditeur, et on passe le premier week-end à écrire tout sauf l'idée : une table users, un hash de mot de passe, des routes de connexion, un upload d'images, une API pour lister les éléments. Le lundi, rien de visible n'existe encore.
PocketBase supprime ce week-end. C'est un backend open source (licence MIT) livré sous la forme d'un seul exécutable écrit en Go. À l'intérieur : une base SQLite, une API REST générée à partir de vos tables, l'authentification, le stockage de fichiers, des abonnements temps réel et une interface d'administration web. On le télécharge, on le lance, et l'API répond.
Ce que contient le binaire
| Brique | Ce qu'elle fait |
|---|---|
| Base SQLite | Stockage des données, en mode WAL pour lire pendant qu'on écrit |
| API REST | CRUD automatique sur chaque collection, avec filtres, tri et pagination |
| Authentification | Email et mot de passe, OAuth2 (Google, GitHub…), codes à usage unique, MFA |
| Fichiers | Upload en local ou sur S3, miniatures générées à la volée |
| Temps réel | Notifications de création, modification et suppression poussées au client |
| Dashboard | Interface web pour créer les tables, gérer les données et les réglages |
| Extensions | Hooks en JavaScript, ou utilisation comme librairie Go |
L'idée est la même que Firebase ou Supabase, sans le cloud de quelqu'un d'autre et sans infrastructure. Pas de Postgres à installer, pas de Redis, pas de conteneurs à orchestrer. Un fichier.
Installation en deux minutes
On récupère l'archive correspondant à sa plateforme sur la page des releases GitHub. Au moment où j'écris, la dernière version est la 0.40.4.
wget https://github.com/pocketbase/pocketbase/releases/download/v0.40.4/pocketbase_0.40.4_linux_amd64.zip
unzip pocketbase_0.40.4_linux_amd64.zip -d pb
cd pb
./pocketbase serveAu premier lancement, PocketBase ouvre un lien d'installation pour créer le compte superutilisateur. Sur un serveur sans navigateur, on le crée en ligne de commande :
./pocketbase superuser create admin@mondomaine.fr 'un-mot-de-passe-solide'Trois adresses sont alors disponibles :
http://127.0.0.1:8090/ → fichiers statiques (dossier pb_public)
http://127.0.0.1:8090/_/ → dashboard d'administration
http://127.0.0.1:8090/api/ → API RESTLe dossier pb_data contient la base et les fichiers uploadés : on l'exclut de Git et on le sauvegarde. Le dossier pb_migrations contient des fichiers JavaScript générés à chaque modification de schéma dans le dashboard : on le versionne. C'est ce qui permet de reproduire la structure de la base en production sans recliquer partout.
Collections et règles d'accès
PocketBase appelle ses tables des collections. Il en existe trois types :
- base : une table classique (articles, commandes, commentaires) ;
- auth : une table d'utilisateurs avec mot de passe, vérification d'email et tokens ;
- view : une vue en lecture seule définie par une requête SQL.
Dans le dashboard, on crée une collection posts avec un champ title (texte), content (éditeur riche), cover (fichier), published (booléen) et author (relation vers users). L'API existe immédiatement.
Toute la sécurité repose ensuite sur les règles d'API. Chaque collection en a cinq : lister, voir, créer, modifier, supprimer. Chacune accepte trois valeurs, et c'est là que se joue la différence entre une app saine et une fuite de données :
| Valeur de la règle | Qui peut faire l'action |
|---|---|
Verrouillée (null, par défaut) | Les superutilisateurs seulement |
Chaîne vide "" | Tout le monde, y compris les visiteurs anonymes |
| Expression de filtre | Uniquement les requêtes qui satisfont l'expression |
Pour un blog multi-auteurs, ça donne :
listRule published = true || author = @request.auth.id
viewRule published = true || author = @request.auth.id
createRule @request.auth.id != "" && @request.body.author = @request.auth.id
updateRule author = @request.auth.id
deleteRule author = @request.auth.idLes règles servent aussi de filtre. Un visiteur qui liste les posts ne reçoit que les articles publiés, sans erreur. Un auteur connecté reçoit en plus ses brouillons.
Le piège classique : mettre une règle à "" pour « débloquer » un test, et l'oublier. La collection devient alors lisible, voire modifiable, par n'importe qui sur Internet. Je relis toujours les cinq règles de chaque collection avant une mise en ligne.
Côté client : le SDK JavaScript
npm install pocketbaseimport PocketBase from 'pocketbase'
export const pb = new PocketBase('https://api.mondomaine.fr')La connexion d'un utilisateur tient en une ligne. Le SDK conserve le token dans pb.authStore et l'ajoute à toutes les requêtes suivantes :
await pb.collection('users').authWithPassword('lea@example.com', 'motdepasse')
console.log(pb.authStore.isValid) // true
console.log(pb.authStore.record?.id)Ce token est un JWT signé par PocketBase. Si le principe d'un token sans session côté serveur vous échappe, le fonctionnement de l'authentification JWT est expliqué en détail ailleurs sur le site.
Lire des données, avec pagination et filtre :
const page = await pb.collection('posts').getList(1, 20, {
filter: pb.filter('published = true && title ~ {:q}', { q: recherche }),
sort: '-created',
expand: 'author',
})pb.filter() échappe les valeurs saisies par l'utilisateur. Construire le filtre par concaténation de chaînes, c'est ouvrir la porte à une injection dans le langage de filtre. expand: 'author' charge la relation en même temps, comme une jointure.
Créer un enregistrement avec un fichier :
const post = await pb.collection('posts').create({
title: 'Mon premier article',
content: '<p>Bonjour</p>',
published: false,
author: pb.authStore.record?.id,
cover: fichierImage, // un objet File issu d'un <input type="file">
})
const url = pb.files.getURL(post, post.cover, { thumb: '400x300' })Le temps réel
pb.collection('posts').subscribe('*', (e) => {
console.log(e.action, e.record.title) // create, update ou delete
})
// à la fermeture de la page
pb.collection('posts').unsubscribe('*')Sous le capot, ce ne sont pas des WebSockets mais des Server-Sent Events : une connexion HTTP longue sur /api/realtime où le serveur pousse les événements. Les règles d'accès s'appliquent aussi aux abonnements, un client ne reçoit que ce qu'il a le droit de voir. Pour comprendre pourquoi SSE suffit ici, la comparaison WebSocket, SSE et polling détaille les trois approches.
Une précaution côté serveur : Node.js n'a pas d'EventSource natif. Pour s'abonner depuis un script Node, il faut charger le paquet eventsource comme polyfill.
Étendre PocketBase
Une API CRUD ne suffit jamais longtemps. Il faut forcer l'auteur d'un post, envoyer un email après une commande, exposer une route de statistiques. PocketBase propose deux façons de le faire.
Avec des hooks JavaScript
On crée un dossier pb_hooks à côté de l'exécutable et on y place des fichiers *.pb.js. Pas de compilation, PocketBase les recharge automatiquement.
// Forcer l'auteur à l'utilisateur connecté, quoi qu'envoie le client
onRecordCreateRequest((e) => {
if (e.auth) {
e.record.set("author", e.auth.id)
}
e.next()
}, "posts")
// Une route personnalisée
routerAdd("GET", "/api/posts/recent", (e) => {
const records = $app.findRecordsByFilter(
"posts", "published = true", "-created", 5, 0
)
return e.json(200, records)
})Deux règles à connaître. D'abord, il faut appeler e.next() : un hook qui l'oublie bloque l'opération. Ensuite, chaque handler s'exécute dans un contexte isolé. Une variable déclarée en haut du fichier n'est pas visible à l'intérieur du handler, il faut passer par require() pour partager du code.
Le moteur JavaScript embarqué n'est pas Node. Pas de setTimeout, pas de paquets npm qui dépendent des API Node, et des performances modestes pour les calculs lourds. Pour de la validation, des notifications et quelques routes, c'est largement suffisant.
Comme une librairie Go
Quand le projet grossit, on peut importer PocketBase dans un programme Go et compiler son propre exécutable :
package main
import (
"log"
"os"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
app.OnServe().BindFunc(func(se *core.ServeEvent) error {
se.Router.GET("/{path...}", apis.Static(os.DirFS("./pb_public"), false))
return se.Next()
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}go mod init monapp && go mod tidy
go run . serveOn garde toutes les fonctionnalités de PocketBase et on ajoute du Go typé, testable, avec tout l'écosystème de librairies. C'est la voie que je choisirais dès que la logique métier dépasse une centaine de lignes de hooks.
Mettre en production
Le déploiement le plus simple consiste à copier le dossier sur un serveur et à lancer l'exécutable avec un service systemd :
[Unit]
Description=PocketBase
After=network.target
[Service]
Type=simple
User=pocketbase
Group=pocketbase
WorkingDirectory=/srv/pocketbase
ExecStart=/srv/pocketbase/pocketbase serve --http=127.0.0.1:8090
LimitNOFILE=4096
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now pocketbasePocketBase sait obtenir un certificat Let's Encrypt tout seul (./pocketbase serve mondomaine.fr), mais il doit alors écouter sur les ports 80 et 443. Je préfère le laisser sur 127.0.0.1 derrière un reverse proxy. Avec Caddy :
api.mondomaine.fr {
request_body {
max_size 10MB
}
reverse_proxy 127.0.0.1:8090 {
transport http {
read_timeout 360s
}
}
}Le read_timeout allongé n'est pas décoratif. Les connexions temps réel restent ouvertes longtemps, et un proxy réglé avec un timeout court les coupe en boucle. LimitNOFILE dans le service systemd répond au même problème : chaque client abonné garde un descripteur de fichier ouvert.
En Docker, la documentation fournit un Dockerfile minimal basé sur Alpine. Il suffit de monter un volume sur /pb/pb_data.
Avant d'ouvrir au public, quatre réglages dans le dashboard :
- Un vrai serveur SMTP (Brevo, Amazon SES…) pour que les emails de vérification n'arrivent pas en spam.
- Le rate limiter, intégré depuis la 0.23, dans Settings > Application.
- La restriction du dashboard à certaines IP :
./pocketbase superuser ips 203.0.113.10. Un token superutilisateur volé devient inutilisable ailleurs. - Les sauvegardes automatiques dans Settings > Backups, vers un stockage S3. Une sauvegarde PocketBase, c'est une archive de
pb_data.
Les limites à connaître
PocketBase n'est pas un Supabase miniature. Il fait des choix forts, et certains deviennent des murs.
Un seul serveur. SQLite est un fichier sur un disque. Pas de réplication, pas de second nœud derrière un load balancer. Un VPS correct encaisse beaucoup plus de trafic qu'on ne l'imagine, mais le jour où une seule machine ne suffit plus, il n'y a pas d'option simple. Si la question SQL ou NoSQL, relationnel ou document, n'est pas encore tranchée pour votre projet, le comparatif SQL vs NoSQL aide à poser les critères.
Pas encore de version 1.0. La documentation le dit elle-même : PocketBase n'est pas recommandé pour des applications critiques, sauf si on accepte de lire le changelog et d'appliquer des migrations manuelles de temps en temps. La 0.23 a renommé une bonne partie de l'API des hooks, par exemple. Il faut épingler sa version et lire chaque note de release avant de mettre à jour.
Un mainteneur principal. Le projet repose en grande partie sur une seule personne. Le code est sous licence MIT et lisible, donc rien n'est perdu si le développement s'arrête. Mais il faut l'avoir en tête pour un projet qui doit vivre dix ans.
| PocketBase | Supabase | Firebase | Backend maison | |
|---|---|---|---|---|
| Base de données | SQLite | PostgreSQL | Firestore (NoSQL) | Au choix |
| Auto-hébergement | Un binaire | Une dizaine de conteneurs | Impossible | Oui |
| Mise à l'échelle horizontale | Non | Oui | Oui | Selon l'architecture |
| Logique serveur | Hooks JS ou Go | Edge Functions, SQL | Cloud Functions | Tout |
| Temps de mise en route | Minutes | Une heure | Minutes | Des jours |
| Stabilité de l'API | Pré-1.0 | Stable | Stable | Celle que vous écrivez |
Pour une application qui doit grandir avec une équipe et un schéma complexe, un backend Node avec un ORM comme TypeORM et ses migrations versionnées reste plus prévisible. Pour tout le reste (MVP, outil interne, application mobile d'une association, side project), PocketBase est difficile à battre.
Alors, un backend complet dans un seul fichier, ça tient ? Oui, à condition de savoir pour quoi on le prend. PocketBase ne remplace pas une architecture pensée pour des millions d'utilisateurs, et il ne prétend pas le faire. Il remplace le week-end perdu à recoder une authentification et un CRUD, et ça suffit à en faire l'outil que je sors en premier dès qu'une idée doit exister avant lundi. Si le projet décolle au point de dépasser un serveur, ce sera un bon problème à avoir.