Retour au blog
Ingénierie & Architecture · Pilier du dossier Rust · 15 min de lecture

Le cœur Rust d’Aurabase : architecture d’un Backend-as-a-Service unifié

Affane Daylami · Fondateur· 24 août 2026

Aurabase est un Backend-as-a-Service écrit en Rust — pas seulement pour quelques services périphériques, mais pour l’intégralité de son cœur applicatif : passerelle, authentification, base de données, temps réel, stockage, notifications, IA, provisioning. Le workspace Cargo à la racine du dépôt liste 18 crates compilées ensemble par un seul cargo build --workspace, sans langage tiers caché derrière la logique produit.

Ce dossier documente cette architecture telle qu’elle existe réellement dans le code, vérifiée fichier par fichier au 23 août 2026 — schémas, chiffres et extraits inclus. C’est la page pilier du cluster « Ingénierie Rust » : elle donne la vue d’ensemble, et renvoie vers les analyses techniques approfondies (Axum, Cargo workspace, passerelle, PostgREST, pg_graphql, et le reste du dossier) publiées ou en cours de publication. Pour la comparaison produit complète face à un BaaS concurrent, voir notre comparatif Aurabase vs Supabase.

L’essentiel
  • Workspace Cargo unique : 18 crates (11 services métier, la CLI aura, 5 bibliothèques partagées, le SDK Rust) compilées ensemble par un seul cargo build --workspace.
  • Le code métier pèse environ 274 000 lignes de Rust (mesuré via find + wc -l, 23 août 2026), réparties sur ces 18 crates.
  • La passerelle (aura-gateway) sépare deux plans — données (SDK, port 8080) et gestion (Studio, port 8090) — chacun avec son propre empilement de middlewares et sa propre authentification.
  • Aurabase ne réécrit pas PostgREST : le vrai binaire amont (v12.2.8) tourne par tenant, orchestré par des services Rust — la valeur ajoutée est autour, pas à la place.
  • Seule entorse notable au Rust pur : le runtime par défaut des Edge Functions (mode deno) est un service TypeScript dédié à base de V8 Isolates ; un second chemin, natif et en Rust via Wasmtime, existe pour le mode wasm.
18
CRATES DU WORKSPACE
11 services + CLI + 5 libs + SDK Rust
~274k
LIGNES DE RUST
find + wc -l, 23 août 2026
2
PLANS DE LA PASSERELLE
données :8080 · gestion :8090
10/11
SERVICES SUR NATS
async-nats en dépendance directe
#
Positionnement

Ce qui distingue Aurabase d’un BaaS assemblé service par service

La plupart des BaaS Postgres open source assemblent leurs services dans plusieurs langages. Ce n’est pas un jugement de valeur — c’est un fait d’architecture qui a des conséquences concrètes : autant de chaînes de compilation, de conventions d’erreurs et de logiques d’auth à maintenir en synchronisation qu’il y a de langages en jeu.

Chez Aurabase, la couche produit qu’on écrit et maintient nous-mêmes — passerelle, auth, base de données, temps réel, stockage, notifications, IA, provisioning, gestion des comptes — est un seul workspace Cargo, un seul langage, une seule chaîne de build. C’est le choix que documente ce dossier.

Précision nécessaire
« Cœur unifié » ne veut pas dire que tout ce qui tourne en production est du Rust. Comme n’importe quel BaaS Postgres, Aurabase s’appuie aussi sur des briques open source qu’il n’a pas écrites : PostgreSQL lui-même, PostgREST, NATS. La différence structurelle avec un stack hétérogène ne porte pas sur ces briques partagées — elle porte sur la couche produit qui les orchestre. Les sections suivantes détaillent où passe exactement cette frontière, y compris la seule exception réelle qu’on a trouvée en auditant le code (les Edge Functions, section 09).
#
Le workspace

18 crates, une seule chaîne de compilation

Le Cargo.toml racine déclare un workspace Cargo en resolver v2 avec 18 membres : 11 services métier, la CLI aura-cli, 5 bibliothèques partagées, et le SDK aurabase-rs. Voici la liste réelle, telle qu’elle apparaît dans le dépôt.

Cargo.toml
TOML
[workspace]
resolver = "2"
members = [
# Services (11)
"services/aura-gateway", "services/aura-auth", "services/aura-db",
"services/aura-provisioner", "services/aura-realtime", "services/aura-storage",
"services/aura-functions", "services/aura-notifications", "services/aura-ai",
"services/aura-migrator", "services/aura-control",
# Tools
"aura-cli",
# Libs (5)
"libs/aura-core", "libs/aura-crypto", "libs/aura-db-adapters",
"libs/aura-migrations", "libs/aura-telemetry",
"aurabase-rs",
]

Les dépendances partagées vivent dans [workspace.dependencies] : Axum 0.8 (avec WebSockets, multipart, macros), Tokio, Tower/Tower-HTTP, SQLx 0.8 (Postgres + MongoDB via mongodb pour le moteur NoSQL secondaire), async-nats 0.47, sqlparser 0.53 (validation SQL du NL2SQL), oauth2 5, jsonwebtoken 10, et deux libs de performance qu’on retrouve dans presque chaque service : mimalloc comme allocateur global et moka/dashmap pour le cache en mémoire.

Le profil de release documente un choix assumé : panic = "unwind" plutôt que "abort". Le commentaire du fichier est explicite — un panic dans un handler Axum/Tokio est isolé par le runtime (la requête concernée renvoie 500) au lieu de faire tomber tout le processus et couper les requêtes concurrentes. Le gain de perf d’abort (1 à 2 % de RPS environ) ne vaut pas la perte d’isolation, d’après cette même note. C’est un compromis fiabilité-vs-vitesse documenté dans le code, pas une affirmation marketing.

Un seul cargo build --workspace compile l’ensemble. Un seul cargo test --workspace fait tourner toute la suite de tests. Un seul cargo clippy --workspace --all-targets -- -D warnings lint tout le produit avec les mêmes règles. Le détail de cette structure — héritage des dépendances, graphe interne entre libs et services, pièges qu’on a rencontrés en la faisant grandir — fait l’objet d’un article dédié : architecture Cargo workspace, comment structurer un backend Rust multi-services.

Lignes de Rust par service (find services -name '*.rs' | xargs wc -l, 23 août 2026) :

aura-control
36 024
aura-db
30 255
aura-auth
28 431
aura-provisioner
28 271
aura-notifications
18 498
aura-ai
18 305
aura-gateway
16 938
aura-realtime
16 799
aura-storage
13 747
aura-functions
11 619
aura-migrator
538

Hors libs partagées (aura-db-adapters : 24 725 lignes, aura-core : 7 259, aura-migrations : 3 641, aura-crypto : 2 718, aura-telemetry : 201), la CLI (aura-cli : 9 845) et le SDK Rust (aurabase-rs : 6 363). aura-migrator, un job de migration à usage unique et non un serveur HTTP long-lived, reste volontairement le plus petit service du workspace.

#
Les services

11 services métier, chacun un serveur Axum autonome

Chaque service est un binaire Axum/Tokio indépendant, avec sa propre configuration et son propre port. Dix des onze exposent /health et /metrics et déclarent mimalloc comme allocateur global — la seule exception, aura-migrator, est un job à usage unique plutôt qu’un serveur qui tourne en continu.

aura-gatewayPasserelle à double plan (données :8080, gestion :8090) : proxy vers tous les autres services.
aura-authAuthentification : JWT, 15 fournisseurs OAuth nommés + OIDC générique par projet, sessions, MFA.
aura-dbAPI base de données : gestion/reload PostgREST par tenant, adapters Postgres et MongoDB, CDC.
aura-provisionerCycle de vie des projets : clusters CNPG dédiés ou partagés, rôles, PostgREST par tenant.
aura-realtimeWebSocket et SSE, diffusion du CDC, presence cross-instance via NATS JetStream KV.
aura-storageObjets compatibles S3 (MinIO), policies RLS transférables depuis Postgres.
aura-functionsEdge Functions : déploiement, jobs, cron, runtime Wasmtime natif (voir section 09).
aura-notificationsEmail, push, webhooks sortants.
aura-aiNL2SQL, RAG et passerelle LLM (OpenAI, Anthropic, Gemini natifs + tout endpoint compatible OpenAI).
aura-migratorMoteur de migrations : source unique du schéma tenant rejoué à chaque provisioning.
aura-controlPlan de gestion : comptes développeurs, organisations, facturation, API du Studio.

La CLI aura (≈ 9 800 lignes) parle aux mêmes API que les SDK — elle n’a pas de chemin privilégié. La référence complète de chaque service vit dans la documentation d’architecture et la référence CLI.

#
Les bibliothèques partagées

5 crates qui évitent la dérive inter-services

Dans un stack polyglotte, une règle de sécurité — le format d’erreur, la protection SSRF, le rate limiting — doit être réimplémentée dans chaque langage, et dérive presque toujours au fil des mois. Aurabase la code une fois, dans une lib du workspace, consommée par tous les services concernés.

aura-core7 259 l.Primitives partagées : erreurs, enveloppe de réponse API, claims JWT, auth interne service-à-service, helpers NATS, rate limiting, client HTTP protégé SSRF, circuit breaker, résolution de tenant, metering.
aura-db-adapters24 725 l.Trait d’adapter base de données unifié, implémentations Postgres et MongoDB utilisées par aura-db.
aura-crypto2 718 l.Hachage de mots de passe, génération de tokens, signature/validation JWT, chiffrement au niveau champ.
aura-migrations3 641 l.Moteur de migrations — source unique du schéma tenant, rejouée par le provisioner (pas les dossiers migrations/ propres à chaque service).
aura-telemetry201 l.Configuration OpenTelemetry + tracing, partagée par les 11 services.

Conséquence directe : un correctif de sécurité dans aura-core — la protection SSRF, par exemple — se propage à chaque service consommateur au prochain cargo build, pas via cinq correctifs séparés dans cinq langages.

#
La passerelle

Double plan : trafic SDK contre trafic Studio

aura-gateway sépare deux surfaces qui n’ont ni les mêmes clients, ni le même modèle d’auth. Le plan de données (port 8080, variable GATEWAY_PORT) reçoit le trafic SDK/app, authentifié par clé API (apikey, X-API-Key ou ?apikey=). Le plan de gestion (port 8090, MANAGEMENT_PORT) reçoit le trafic Studio/admin, authentifié par un JWT console (Authorization: Bearer).

services/aura-gateway/src/routes/mod.rs (extrait, routes réelles)
RUST
// Plan de données — clé API
.route("/v1/auth/{*path}", any(auth_proxy))
.route("/v1/db/{*path}", any(db_proxy))
.route("/v1/realtime/ws", get(realtime_ws_proxy))
.route("/v1/realtime/sse", get(realtime_sse_proxy))
.route("/v1/storage/{*path}", any(storage_proxy))
.route("/v1/notifications/{*path}", any(notifications_proxy))
.route("/v1/ai/{*path}", any(ai_dispatcher))
.route("/v1/functions/{*path}", any(functions_proxy))
// Plan de gestion — JWT console
.route("/v1/control/{*path}", any(control_proxy))

Chaque plan porte son propre empilement de middlewares — identification de requête, journal d’accès, limite de débit, authentification (spécifique au plan), disjoncteur, puis proxy — implémentés dans des modules séparés (middleware/rate_limit.rs, middleware/circuit_breaker.rs, middleware/auth_data_plane.rs, middleware/auth_management_plane.rs) plutôt qu’une seule chaîne partagée par accident entre deux surfaces qui ne devraient pas se faire confiance de la même façon.

L’ordre exact des middlewares, le rate limiting par cible et le proxy NATS/HTTP font l’objet d’un article dédié : passerelle à double plan, concevoir un API gateway data-plane/management-plane en Rust.

#
La donnée

Pourquoi Aurabase ne réimplémente pas PostgREST en Rust

L’API REST auto-générée que consomme le SDK n’est pas un module Rust maison : c’est le vrai binaire amont PostgREST (postgrest/postgrest:v12.2.8), déployé en 2 réplicas par projet dédié (deploy/cnpg/tenant-postgrest.yaml), co-localisé avec l’instance CNPG du tenant. aura-provisioner crée ce déploiement, et aura-db sonde son endpoint OPTIONS après chaque reload de schéma pour confirmer que PostgREST a bien pris en compte le changement de DDL.

C’est un choix d’architecture assumé, pas un raccourci : PostgREST est un projet mature, largement adopté, dont réécrire le comportement au bit près en Rust n’apporterait rien. Le travail Rust d’Aurabase se concentre autour — routage multi-tenant, provisioning, isolation réseau par NetworkPolicy, auth partagée avec le gateway, reload de schéma orchestré — pas à l’intérieur du moteur de requêtes lui-même. Ce que cette compatibilité couvre vraiment, et où elle s’arrête, fait l’objet d’un article séparé : PostgREST, compatibilité réelle et alternatives.

Même logique côté GraphQL : l’extension Postgres pg_graphql (paquet .deb précompilé, v1.6.1) est installée dans l’image CNPG dédiée et activée à la demande par projet via l’endpoint POST /v1/control/projects/{project_id}/graphql/enable — pas un moteur GraphQL réécrit non plus. Détails et comparatif honnête face à Hasura et PostGraphile : API GraphQL native sur Postgres avec pg_graphql.

#
L’isolation

RLS et rôle authenticator, pas une couche applicative

L’isolation multi-tenant ne repose pas sur un filtre WHERE tenant_id = ? ajouté par un ORM applicatif : chaque requête passe par le rôle aura_authenticator, qui exécute un SET LOCAL ROLE tenant_<uuid> transaction-scoped avant d’exécuter la requête — exactement le modèle qu’attend PostgREST lui-même. Row-Level Security fait le reste, au niveau moteur, pas au niveau code métier.

Les projets ne partagent pas tous la même topologie Postgres. Le code de aura-provisioner expose un type ProjectInstanceKind avec au moins deux variantes réelles : FullyDedicated (instance CNPG entièrement dédiée au projet) et SharedClusterDedicated (schéma isolé par RLS sur un cluster CNPG mutualisé). Le plan souscrit détermine la topologie — ce n’est pas une promesse uniforme de « base dédiée pour tout le monde ».

Astuce
L’angle « pourquoi une base dédiée par projet plutôt qu’une isolation purement applicative » fait l’objet d’un article dédié : RLS et base dédiée par projet. La documentation Row-Level Security couvre l’implémentation pratique.
#
La messagerie

NATS cœur, pas JetStream : la messagerie asynchrone entre services

Dix des onze services métier déclarent async-nats en dépendance directe dans leur Cargo.toml — seul aura-migrator s’en passe. Ce que ce nom de crate ne dit pas : ces services utilisent presque partout l’API pub/sub cœur de NATS (Client::publish / publish_with_headers, livraison at-most-once sans persistance ni relecture), pas JetStream. C’est le cas vérifié dans le code pour les trois usages qui comptent le plus : la diffusion du CDC PostgreSQL vers aura-realtime, la notification de jobs des Edge Functions dans aura-functions (dont la DLQ elle-même vit en Postgres, pas dans NATS), et les événements de provisioning entre aura-provisioner et le reste de la flotte.

JetStream — le mode avec persistance et streams nommés de NATS — n’apparaît qu’à un seul endroit vérifié du monorepo : le KV Store de présence cross-instance dans aura-realtime (bucket aura_presence, stockage mémoire, max_age de 60 secondes), qui synchronise qui est connecté à quel canal entre plusieurs instances de ws-front — un état partagé cross-instance, pas un flux d’événements à rejouer. Aucun stream JetStream persistant n’a été trouvé ailleurs dans le workspace. Le détail du pipeline CDC — wal2json, élection d’un cdc-worker unique par Lease Kubernetes, fan-out NATS cœur vers les réplicas ws-front, revérification RLS par abonné — fait l’objet d’un article dédié : diffuser le CDC PostgreSQL avec NATS. La documentation Realtime couvre l’usage côté SDK.

#
L’exception assumée

Edge Functions : deux runtimes pour deux besoins

C’est la nuance la plus importante de ce dossier, et la seule véritable entorse au Rust pur dans le code produit d’Aurabase. Chaque fonction porte un champ runtime qui vaut "wasm" ou "deno". Le handler d’invocation choisit le chemin d’exécution en conséquence — extrait réel, commenté dans le code lui-même :

services/aura-functions/src/handlers/functions.rs (extrait)
RUST
// Dispatch selon le runtime : WASM (wasmtime) ou Deno (V8 Isolates via edge-runtime)
let resp = if func.runtime == "wasm" {
state.runtime.invoke(&wasm_bytes, &func.code_hash, req).await?
} else {
// Proxy vers aura-edge-runtime — V8 Isolates (supabase/edge-runtime, MIT)
proxy_to_edge_runtime(&state.config.edge_runtime_url, …).await?
};

Le mode wasm est natif : aura-functions dépend directement de Wasmtime (version 43, fonctionnalités async et cranelift) et exécute le module dans le même process Rust, avec comptage de fuel, interruption par epoch et bornage mémoire via StoreLimits. Le mode deno — celui utilisé par défaut dans l’éditeur du Studio, pour une compatibilité Deno.serve() quasi directe avec du code Supabase existant — délègue l’exécution à aura-edge-runtime, un service TypeScript distinct d’environ 550 lignes, explicitement modelé — le commentaire d’en-tête du fichier source le cite lui-même — sur supabase/edge-runtime (licence MIT), qui isole chaque invocation dans son propre V8 Isolate.

Pourquoi c’est honnête de le dire ainsi
Le contrôle — déploiement, permissions, jobs, cron, quotas — reste intégralement en Rust dans aura-functions. Seule l’exécution du code utilisateur en mode deno sort du binaire Rust. C’est un compromis d’ingénierie défendable (les V8 Isolates sont ce que Deno fournit nativement pour ce niveau de sandboxing), pas une omission qu’on préfère taire. Le comparatif Wasmtime vs Wasmer et l’analyse cold start WebAssembly, à venir dans ce dossier, iront plus loin sur ce sujet.

Pour la doc pratique des deux chemins de déploiement, voir Edge Functions. Pour le playbook de migration Deno depuis Supabase, voir migrer un projet Supabase vers Aurabase, qui documentait déjà cette distinction avant ce dossier.

#
En pratique

Ce que ce cœur unifié change concrètement pour vous

Si vous ne faites qu’appeler l’API via un SDK, cette architecture est invisible — c’est le but. Elle compte surtout pour trois audiences : ceux qui évaluent la fiabilité opérationnelle d’un backend multi-tenant avant d’y migrer des données de production, ceux qui envisagent de contribuer au dépôt (MIT, monorepo unique), et ceux qui veulent comprendre pourquoi un correctif de sécurité côté Aurabase se propage vite plutôt que lentement.

Concrètement : une seule CI (cargo build --workspace, cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings) couvre 90 % et plus de la surface produit. Une revue de code sur aura-core touche potentiellement dix services d’un coup — pour le meilleur (un correctif ne se perd pas en route) comme pour le pire (un changement mal isolé se propage aussi vite). C’est un compromis, pas une solution magique — et c’est précisément pour ça qu’on documente l’architecture réelle plutôt qu’un résumé marketing.

#
Hub du dossier

La suite du dossier Ingénierie Rust

Cette page pilier renvoie vers les analyses techniques du cluster, au fil de leur publication. Statut réel au moment de la publication de cette page — les liens s’activent dès que l’article correspondant est en ligne.

Cluster A — Framework & architecture
Cluster B — API auto-générée sur Postgres
Cluster C — Temps réel & messaging
Cluster D — Edge Functions WebAssembly
Wasmtime vs Wasmer : quel runtime WebAssembly pour des Edge Functions en productionÀ venir
Edge Functions en Rust/WASM face à Cloudflare Workers et Vercel EdgeÀ venir
Cold start WebAssembly : ce que les benchmarks disent vraiment (et ce qu’on ne peut pas encore affirmer)À venir
Cluster E — Migration & alternatives
Auto-hébergement souverain : positionner Aurabase face aux BaaS Rust-natifsÀ venir
#
Questions fréquentes

FAQ

Le code d’Aurabase est-il open source ?+
Le dépôt est un monorepo unique : les 18 crates Rust du workspace, les SDK client (JavaScript, Rust, Python, Dart) et le Studio Next.js y vivent ensemble, publiés sous licence MIT sur GitHub.
Peut-on auto-héberger Aurabase ?+
Oui. Le dépôt fournit un docker-compose complet pour un environnement local (./start.sh) et un chart Helm (deploy/helm/aurabase/) pour un déploiement Kubernetes. Aurabase Cloud reste l’option managée si vous préférez ne pas opérer l’infrastructure vous-même.
Le SDK JavaScript utilise-t-il la même syntaxe que celui de Supabase ?+
Sur l’essentiel, oui : createClient(), le query builder chaîné .from().select().eq(), les flux d’authentification et les policies RLS visent une compatibilité quasi directe — c’est ce qui rend une migration Supabase vers Aurabase praticable sans réécriture complète.
Faut-il connaître Rust pour utiliser Aurabase ?+
Non. Rust est le langage du backend, pas celui que vous écrivez au quotidien : les SDK client existent en JavaScript/TypeScript, Rust, Python et Dart, et les Edge Functions s’écrivent par défaut en JavaScript/TypeScript (runtime Deno). Rust n’intervient que si vous choisissez explicitement le mode d’exécution WASM natif.
Les Edge Functions d’Aurabase tournent-elles vraiment en WebAssembly ?+
Cela dépend du mode choisi. Le mode par défaut (deno) exécute votre code JavaScript/TypeScript dans des V8 Isolates via un service dédié, pas dans un moteur WASM. Un second mode (wasm) existe et s’exécute nativement dans le service Rust aura-functions via Wasmtime — mais ce n’est pas le chemin par défaut.
VOIR LE CODE

Vérifiez cette architecture vous-même.

Le workspace complet est public. Ou créez un projet en quelques clics et regardez la passerelle, l’auth et la base tourner en direct.

Créer un projet Documentation d’architecture
Aucune carte bancaire requise · 500 MB gratuits · 50 000 MAU