Hola Mundo con Actix Web y PostgreSQL

Resumen

Configurar Actix Web en Rust te permite levantar un servidor HTTP en minutos y conectarlo a una base de datos PostgreSQL usando un pool de conexiones. Aquí aprendes a estructurar el main.rs, definir rutas con macros y entender por qué Rust exige la palabra move al pasar datos entre hilos. Es una guía pensada para quien ya conoce Diesel y quiere dar el salto a la capa web.

¿Qué dependencia necesito para usar Actix Web en Rust?

Todo empieza en el Cargo.toml. Ahí declaras la versión estable de Actix Web, que al momento de esta clase es la 4.0 [01:00].

toml [dependencies] actix-web = "4.0"

Dentro del archivo main.rs importas los elementos que vas a usar a lo largo del servidor: get, post, web, App, HttpResponse, HttpServer y Responder.

rust use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

El trait Responder es el que permite que una función devuelva una respuesta HTTP válida sin que tengas que construirla manualmente cada vez.

¿Cómo se estructura el main asíncrono de Actix?

Actix exige que tu aplicación sea asíncrona, así que la función main se marca con async y con el macro #[actix_web::main]. La salida es un std::io::Result<()> porque el servidor puede fallar al enlazar el puerto [01:45].

rust #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(hello_world) }) .bind(("0.0.0.0", 9900))? .run() .await }

Fíjate en tres detalles que suelen romper a quienes empiezan:

  • La IP y el puerto van como tupla, no como argumentos separados.
  • bind devuelve un Result, así que necesitas ? o .unwrap().
  • No pones punto y coma al final de .await porque estás devolviendo el valor.

¿Qué hace el macro #[actix_web::main]? Convierte tu función main síncrona en un runtime asíncrono compatible con Actix. Sin él, no puedes usar async/await en main.

¿Cómo creo una ruta y devuelvo Hola Platzi en el navegador?

La función que responde en la raíz es muy directa. Le pones el macro #[get("/")] para asociarla a una URL y devuelves un HttpResponse::Ok con el cuerpo que quieras [03:30].

rust #[get("/")] async fn hello_world() -> impl Responder { HttpResponse::Ok().body("Hola, Platzi") }

Un error típico aquí es dejar el punto y coma después de HttpResponse::Ok().body(...). Si lo haces, la función devuelve () en vez de un Responder y el compilador se queja. Esa es la azúcar sintáctica de Rust: la última expresión sin punto y coma es el valor de retorno.

Corre el proyecto con cargo run, abre el navegador en 0.0.0.0:9900 y vas a ver el mensaje en pantalla. Cualquier cambio en el código requiere detener el servidor con Ctrl + C y volver a ejecutar.

¿Cómo conecto Actix Web con PostgreSQL usando r2d2?

Una sola conexión a base de datos no escala. Por eso Diesel ofrece r2d2, una librería que crea un pool de conexiones reutilizables [05:30]. Necesitas dos imports clave:

rust use diesel::r2d2::Pool; use diesel::r2d2::{self, ConnectionManager};

En el Cargo.toml activa la feature r2d2 de Diesel para que el compilador encuentre estos módulos.

Dentro de main, después de cargar las variables del .env, construyes el manager y el pool:

rust let connection = ConnectionManager::<PgConnection>::new(db_url); let pool = Pool::builder() .build(connection) .expect("No se pudo construir la pool");

El expect te da un mensaje claro si la conexión falla al arrancar, lo cual ahorra horas de debugging.

¿Cómo paso el pool a las rutas con web::Data?

Actix usa web::Data para compartir estado entre handlers. Después de registrar el servicio, agregas el pool clonado al App:

rust HttpServer::new(move || { App::new() .service(hello_world) .app_data(web::Data::new(pool.clone())) })

Desde cualquier ruta, ahora puedes pedir el pool como parámetro y ejecutar queries de Diesel sin abrir conexiones nuevas a mano.

¿Qué es un pool de conexiones? Es un grupo de conexiones a la base de datos que se mantienen abiertas y se reparten entre las peticiones. Evita el costo de abrir y cerrar conexiones en cada request.

¿Por qué Actix obliga a escribir move antes del closure?

La palabra move aparece justo antes de las llaves del HttpServer::new y resuelve un tema profundo de Rust: el ownership [08:00]. Cuando el servidor arranca cada worker en un hilo distinto, necesita ser dueño de los datos que va a usar, en este caso el pool.

Sin move, el closure intentaría tomar prestado el pool del main, pero el main ya no existirá cuando los hilos estén corriendo. Con move, transfieres la propiedad del valor desde el hilo principal hacia cada hilo del servidor.

  • move traslada la propiedad de las variables capturadas al closure.
  • pool.clone() crea una referencia contable nueva para que cada worker tenga la suya.
  • Sin estos dos elementos, el código no compila.

Es uno de los superpoderes de Rust: te obliga a pensar en quién posee qué dato antes de ejecutar, y eso elimina toda una familia de errores de concurrencia en tiempo de compilación.

¿Ya levantaste tu servidor en el puerto 9900? Cuéntame en los comentarios qué mensaje pusiste en tu primer body y si el compilador te marcó algún typo en Connection o ConnectionManager.