category

Web ApplicationDatabaseMachine learningKuberneteseCommerceCloud

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, et exp. Utiliser la récupération JWKS et mettre en cache par kid ; 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 basseExcellent choixCorrect avec discipline sur la boucle d’événements
Empreinte minimale par instanceExcellent choixPlus lourd ; mise à l’échelle via clustering
Vitesse et écosystème devMoyenne, en progrèsExcellente (middlewares, templates, outils)
Sécurité des types à la compilationExcellentePlus 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

Comparatif des bases de données serverless : Oracle, Azure, Redshift et AuroraOrchestration de Spark sur AWS EMR avec Apache Airflow — L’approche Low-OpsÉtude de cas : un système léger de détection d’intrusion avec OpenFaaS et PyTorchConstruire des clusters Kubernetes résilients avec Portworx Community EditionIntégrer Shopify dans une Application Web Next.js React