Services REST PostgreSQL : Rust (Axum) vs. Node.js (Express)
Les API modernes servant des applications monopage ont besoin de trois choses : un CORS bien configuré, une vérification JWT, et une connexion fiable à PostgreSQL. Ci-dessous, deux modèles minimaux — Rust et Node — montrent comment implémenter un endpoint protégé qui retourne un simple décompte depuis la base. Après les extraits de code, vous trouverez une comparaison pragmatique sur les performances, l’ergonomie et la sécurité.
Rust (Axum) — modèle minimal
Crates : axum
, tower-http
(CORS), jsonwebtoken
, sqlx
(Postgres).
Pourquoi sqlx
? Asynchrone, pool intégré, requêtes paramétrées par défaut.
# Cargo.toml (essentiels)
# axum = "0.7"
# tower-http = { version = "0.5", features = ["cors"] }
# jsonwebtoken = "9"
# sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }
# serde = { version = "1", features = ["derive"] }
# tokio = { version = "1", features = ["full"] }
use axum::{routing::get, extract::State, http::{HeaderMap, Method}, Json, Router};
use tower_http::cors::CorsLayer;
use jsonwebtoken::{decode, DecodingKey, Validation, Algorithm};
use sqlx::{Pool, Postgres};
use serde::Deserialize;
use std::{sync::Arc, net::SocketAddr};
#[derive(Clone)]
struct AppState {
pool: Arc<Pool<Postgres>>,
jwk: DecodingKey<'static>,
}
#[derive(Deserialize)]
struct Claims { sub: String, iss: String, aud: String, exp: usize }
async fn employees_count(State(s): State<AppState>, headers: HeaderMap) -> Json<serde_json::Value> {
// JWT : Authorization: Bearer <token>
let token = headers.get("authorization").and_then(|v| v.to_str().ok()).unwrap_or("");
let token = token.strip_prefix("Bearer ").unwrap_or("");
let _claims = decode::<Claims>(token, &s.jwk, &Validation::new(Algorithm::RS256)).unwrap();
// Requête
let (count,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM employees")
.fetch_one(&*s.pool)
.await
.unwrap();
Json(serde_json::json!({ "employees": count }))
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Pool
let pool = sqlx::postgres::PgPoolOptions::new()
.max_connections(10)
.connect("postgres://app:secret@db:5432/mydb")
.await?;
// Clé publique JWT (PEM)
let jwk = jsonwebtoken::DecodingKey::from_rsa_pem(include_bytes!("../rsa_public.pem"))?.into_static();
// CORS (limité à votre SPA)
let cors = CorsLayer::new()
.allow_origin("https://app.example.com".parse()?)
.allow_methods([Method::GET])
.allow_headers(["authorization".parse()?]);
let app = Router::new()
.route("/api/employees/count", get(employees_count))
.with_state(AppState { pool: Arc::new(pool), jwk })
.layer(cors);
let addr: SocketAddr = "0.0.0.0:8080".parse()?;
axum::Server::bind(&addr).serve(app.into_make_service()).await?;
Ok(())
}
Node.js (Express) — modèle minimal
Packages : express
, cors
, jsonwebtoken
, pg
(ou pg-pool
).
// npm i express cors jsonwebtoken pg
const express = require('express');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const { Pool } = require('pg');
const app = express();
app.use(cors({
origin: 'https://app.example.com',
methods: ['GET'],
allowedHeaders: ['Authorization']
}));
// Pool de connexions
const pool = new Pool({
connectionString: 'postgres://app:secret@db:5432/mydb',
max: 10
});
// Endpoint protégé
app.get('/api/employees/count', async (req, res) => {
try {
const token = (req.headers.authorization || '').replace(/^Bearer\s+/,'');
const claims = jwt.verify(token, process.env.RSA_PUBLIC_PEM, {
algorithms: ['RS256'],
audience: 'your-aud',
issuer: 'your-iss'
});
const { rows } = await pool.query('SELECT COUNT(*)::bigint AS c FROM employees');
res.json({ employees: Number(rows[0].c), user: claims.sub });
} catch (err) {
res.status(401).json({ error: 'unauthorized' });
}
});
app.listen(8080, () => console.log('listening on 8080'));
Performances et ergonomie
Latence et débit
- Rust offre généralement une latence plus basse et plus prévisible sous forte charge, grâce à ses abstractions zéro-coût, son runtime léger et son IO asynchrone.
- Node fonctionne bien pour de nombreux cas ; avec un code soigné (aucun blocage de la boucle d’événements), il s’adapte efficacement. La vérification JWT coûteuse en CPU et les gros JSON peuvent mettre la boucle sous pression — il faut déporter ces charges si nécessaire.
Empreinte en ressources
- Les services Rust consomment moins de mémoire et de CPU à débit équivalent.
- Node est plus lourd par instance, mais peut être mis à l’échelle horizontalement via le clustering.
Vélocité de développement
- Node offre une itération rapide et un vaste écosystème de middlewares.
- Rust est plus exigeant, mais procure des garanties fortes à la compilation (types, durées de vie) et moins de surprises à l’exécution.
Notes de sécurité (JWT + CORS + Postgres)
- CORS : autoriser exactement l’origine de votre SPA ; restreindre méthodes et en-têtes. Éviter
*
avec credentials. - JWT : fixer les algorithmes (RS256/ES256), valider
iss
,aud
, etexp
. Utiliser la récupération JWKS et mettre en cache parkid
; ne pas coder en dur une clé obsolète en production. - Secrets : charger les identifiants Postgres et les clés JWT depuis un gestionnaire de secrets (Vault, AWS Secrets Manager, Secrets Kubernetes).
- Sécurité SQL : utiliser des requêtes paramétrées (comme dans les extraits).
- Limitation et timeouts : protéger contre le brute force de tokens et l’épuisement du pool.
- TLS : imposer HTTPS ; utiliser
sslmode=require
pour chiffrer le trafic Postgres. - Observabilité : journaliser les échecs d’auth (avec limite), la saturation du pool, et les latences p95/p99 ; ne jamais consigner de JWT bruts.
Quand choisir quoi
Priorité | Rust (Axum) | Node.js (Express) |
---|---|---|
Latence la plus basse | Excellent choix | Correct avec discipline sur la boucle d’événements |
Empreinte minimale par instance | Excellent choix | Plus lourd ; mise à l’échelle via clustering |
Vitesse et écosystème dev | Moyenne, en progrès | Excellente (middlewares, templates, outils) |
Sécurité des types à la compilation | Excellente | Plus faible ; dépend des contrôles runtime/TypeScript |
En résumé : Si vous recherchez des performances serrées, une petite empreinte et des garanties fortes, Rust est convaincant. Si votre équipe privilégie la rapidité de livraison et l’écosystème, Node.js reste un choix pratique — à condition d’être rigoureux sur la boucle d’événements, l’hygiène des dépendances et la configuration stricte de JWT/CORS.
Table of Contents
- Rust (Axum) — modèle minimal
- Node.js (Express) — modèle minimal
- Performances et ergonomie
- Notes de sécurité (JWT + CORS + Postgres)
- Quand choisir quoi
Trending
Table of Contents
- Rust (Axum) — modèle minimal
- Node.js (Express) — modèle minimal
- Performances et ergonomie
- Notes de sécurité (JWT + CORS + Postgres)
- Quand choisir quoi