category

Machine learningDatabaseCloudBase de DatosAplicación WebWeb ApplicationKuberneteseCommerce

Servicios REST con PostgreSQL: Rust (Axum) vs. Node.js (Express)

Las APIs modernas que sirven aplicaciones de una sola página necesitan tres cosas: CORS bien configurado, verificación JWT y una conexión confiable a PostgreSQL. A continuación, se presentan dos patrones mínimos —Rust y Node— que muestran cómo implementar un endpoint protegido que devuelve un conteo simple desde la base de datos. Después de los fragmentos de código, encontrarás una comparación pragmática sobre rendimiento, ergonomía y seguridad.


Rust (Axum) — patrón mínimo

Crates: axum, tower-http (CORS), jsonwebtoken, sqlx (Postgres).
¿Por qué sqlx? Asíncrono, con pool incluido y consultas parametrizadas por defecto.

# Cargo.toml (esenciales)
# 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();

    // Consulta
    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?;

    // Clave pública JWT (PEM)
    let jwk = jsonwebtoken::DecodingKey::from_rsa_pem(include_bytes!("../rsa_public.pem"))?.into_static();

    // CORS (restringido a tu 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) — patrón mínimo

Paquetes: express, cors, jsonwebtoken, pg (o 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 conexiones
const pool = new Pool({
  connectionString: 'postgres://app:secret@db:5432/mydb',
  max: 10
});

// Endpoint protegido
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'));

Rendimiento y ergonomía

Latencia y rendimiento

  • Rust suele ofrecer menor latencia de cola y un rendimiento más predecible bajo carga, gracias a sus abstracciones de costo cero, su runtime ligero y su IO asíncrono.
  • Node funciona bien en muchos escenarios; con código disciplinado (sin bloqueo en el event loop), escala de forma efectiva. La verificación JWT intensiva en CPU y grandes cuerpos JSON pueden presionar el bucle: conviene descargarlos en procesos/hilos separados.

Consumo de recursos

  • Los servicios en Rust tienden a usar menos memoria y CPU a igual rendimiento.
  • Node es más pesado por instancia, pero se escala fácilmente de manera horizontal con clustering.

Velocidad de desarrollo

  • Node ofrece iteración rápida y un ecosistema de middlewares muy amplio.
  • Rust es más exigente, pero brinda garantías sólidas en tiempo de compilación (tipos, lifetimes) y menos sorpresas en ejecución.

Notas de seguridad (JWT + CORS + Postgres)

  • CORS: permite únicamente el origen de tu SPA; restringe métodos y cabeceras. Evita * con credenciales.
  • JWT: fija algoritmos (RS256/ES256), valida iss, aud y exp. Usa JWKS y cachea por kid; no dejes un PEM codificado en duro en producción.
  • Secrets: carga credenciales de Postgres y claves JWT desde un gestor de secretos (Vault, AWS Secrets Manager, Secrets de Kubernetes).
  • Seguridad SQL: utiliza siempre consultas parametrizadas (como en los ejemplos).
  • Limitación y timeouts: protege contra fuerza bruta de tokens y agotamiento del pool.
  • TLS: exige HTTPS; usa sslmode=require para cifrar tráfico Postgres cuando sea posible.
  • Observabilidad: registra fallos de auth (con límite), saturación del pool y latencias p95/p99; nunca loguees JWT sin enmascarar.

Cuándo elegir cada uno

PrioridadRust (Axum)Node.js (Express)
Latencia más bajaMuy buen candidatoCorrecto con disciplina en el event loop
Huella mínima por instanciaMuy buen candidatoMás pesado; escalar con clustering
Velocidad y ecosistema devMedia, en crecimientoExcelente (middlewares, plantillas, tooling)
Seguridad de tipos en compilaciónExcelenteMás débil; depende de controles en runtime/TS

En resumen: Si buscas máximo rendimiento, baja huella y fuertes garantías de seguridad, Rust es convincente. Si tu equipo prioriza la velocidad de entrega y un ecosistema amplio, Node.js sigue siendo un buen punto de partida — siempre que seas riguroso con el event loop, la higiene de dependencias y la configuración estricta de JWT/CORS.


Table of Contents

  • Rust (Axum) — patrón mínimo
  • Node.js (Express) — patrón mínimo
  • Rendimiento y ergonomía
  • Notas de seguridad (JWT + CORS + Postgres)
  • Cuándo elegir cada uno

Trending

Orquestar Spark en AWS EMR con Apache Airflow — Enfoque Low-OpsEstudio de caso: Un sistema ligero de detección de intrusos con OpenFaaS y PyTorchCouchDB o AWS DynamoDBAirflow Migración y Limpieza de Datos de Bigtable a Snowflake con Airflow 2.9 en KubernetesApache Airflow 2.x en Kubernetes – Orquestación de datos lista para producción a escala Big Data