Rust 2026-07-10·Ievgenii Svyryd

Rustango: llevando las baterías de Django a Rust

Un framework web con todo incluido que traslada la experiencia de desarrollo de Django -el ORM tipado, las migraciones, el admin, la autenticación y los comandos de gestión- a Rust asíncrono, sin renunciar a las garantías en tiempo de compilación.

Rustango: llevando las baterías de Django a Rust

Me encanta Django. Después de años lanzando productos con él, la filosofía del framework está grabada en mi forma de pensar: una capa de modelos tipada, migraciones que se escriben solas, un admin que obtienes gratis y una CLI de gestión que lo une todo. También me encanta Rust: el compilador que se niega a dejar que toda una clase de errores llegue a producción, y el runtime que no necesita un recolector de basura ni una niñera.

Durante mucho tiempo esos dos amores vivieron en mundos separados. Rustango es mi intento de unirlos: un framework web con todo incluido que traslada la experiencia de desarrollo de Django al Rust asíncrono, sin renunciar a las garantías en tiempo de compilación.

Un ORM tipado que se lee como Django

El corazón de cualquier framework al estilo Django es la capa de modelos. En Rustango derivas Model sobre un struct plano y lo anotas con la tabla, la clave primaria y las restricciones de columna. La macro de derivación genera un conjunto de columnas tipado y un QuerySet encadenable, de modo que el constructor de consultas lo verifica el compilador, no se descubre en tiempo de ejecución.

#[derive(Model, Default, Serialize, Deserialize)]
#[rustango(table = "blog_post", app = "blog")]
pub struct Post {
    #[rustango(primary_key)]
    pub id: Auto<i64>,
    #[rustango(max_length = 200)]
    pub title: String,
    pub published: bool,
    #[rustango(auto_now_add)]
    pub created_at: Auto<DateTime<Utc>>,
}

// Un QuerySet encadenable y verificado por tipos: los nombres de columna son símbolos reales.
let recent = Post::objects()
    .where_(Post::published.eq(true))
    .order_by(&[("created_at", false)])
    .fetch(pool)
    .await?;

No hay un DSL de consultas basado en cadenas en el que puedas cometer erratas. Post::published es un símbolo generado; escríbelo mal y la compilación falla. Los mismos metadatos de columna alimentan las inserciones, las actualizaciones y el diff de esquema que consume el motor de migraciones.

Migraciones, generadas y aplicadas

Los modelos de Rustango llevan un SCHEMA que el framework puede comparar con la base de datos. La CLI genera archivos de migración a partir de la diferencia y los aplica por base de datos, y como Rustango es multiinquilino desde su base, conoce la diferencia entre el esquema del registro compartido y el esquema de cada inquilino.

cargo run -- makemigrations      # compara modelos -> archivos de migración
cargo run -- migrate-registry    # aplica el esquema compartido/de registro
cargo run -- migrate-tenants     # aplica cada base de datos de inquilino

El panel de administración que no tuviste que escribir

Anota un modelo con un bloque admin(...) - columnas de la lista, ordenación, búsqueda - y Rustango renderiza un admin CRUD completo para él: vistas de lista con filtros y paginación, formularios de creación/edición generados a partir de los widgets de tus campos, y acciones en bloque. Es la única característica que hace que Django se sienta como un truco de trampa, y es la que más eché de menos en Rust.

#[rustango(
    table = "blog_post", app = "blog",
    admin(list_display = "title, published, created_at", ordering = "-created_at")
)]
pub struct Post { /* ... */ }

Baterías incluidas: autenticación, sesiones, formularios, CSRF, caché

Un framework se gana el calificativo de con las pilas incluidas por las cosas aburridas que trae ya listas para que nunca vuelvas a escribirlas. Rustango aporta sesiones firmadas, una pila de contraseñas/flujos de autenticación, una capa de formularios con protección CSRF, una caché (incluyendo caché de página completa), señales, URLs firmadas y una manage::Cli que integra el enrutamiento, la multitenencia, las migraciones y tus propios comandos en un único binario. El enrutamiento se asienta sobre Axum y Tokio, así que la historia asíncrona es la madura en la que el ecosistema de Rust ya confía.

La promesa de Django nunca fue la sintaxis: fue que el framework ya había tomado por ti las decisiones aburridas. Rustango mantiene esa promesa, y el compilador te mantiene honesto.

¿Valió la pena?

Portar un framework no es un proyecto de fin de semana, y Rust exige más de entrada que Python. Pero la recompensa es real: la misma definición de modelo impulsa la base de datos, el admin y la API, y el borrow checker convierte toda una categoría de incidentes de producción en errores de compilación. Si alguna vez has deseado que Django corriera a la velocidad de Rust con las garantías de Rust, Rustango es como se ve ese deseo hecho código.

Contacto →