Configuración de Actix y conexión con bases de datos en Rust

Clase 11 de 21Curso de Backend con Rust: Bases de Datos, Controladores y Templates

Resumen

Es momento de realizar el primer ¡Hola Mundo! Desde una aplicación web con Rust y lograr renderizar texto a un navegador.

Servidor HTTP con Rust

El primer paso para servir contenido a un navegador web, es establecer la conexión con un servidor web. Para esto, utilizaremos Actix y todo comienza agregando la dependencia en el archivo Cargo.toml:

[dependencies]
actix-web = "4"

A continuación, exponemos el servidor HTTP de la siguiente manera:

// Importamos lo necesario para levantar un servidor web y exponer endpoints
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

// Endpoint GET que devuelve texto, utiliamos el Macro GET para indicar el verbo HTTP
#[get("/")]
async fn hello_wold() -> impl Responder {
    HttpResponse::Ok().body("Hola Platzi!!!")
}

// Macro para establecer la aplicación del tipo Web, la misma debe ser asíncrona
#[actix_web::main]
async fn main() -> std::io::Result<()> {

    // Levantamos el servidor HTTP
    HttpServer::new(|| {
        // Exponemos los endpoints que indiquemos
        App::new().service(hello_wold)
    // Indicamos el host y el puerto donde escuchará el servidor
    }).bind(("127.0.0.1", 8080)).unwrap().run().await

}

Ahora, lanza tu servidor HTTP con cargo run y si todo ha ido bien, ingresa desde un navegador a la ruta 127.0.0.1:8080/ para visualizar el mensaje Hola Platzi!!! que devuelve el endpoint que hemos creado.

Cabe mencionar un error muy común. Te habrás dado cuenta de que Rust es un lenguaje sensible al punto y coma ;, es obligatorio su utilización. Sin embargo, en las operaciones asíncronas no es del todo necesario. Si quieres utilizarlo, solo agrega un return a la línea de código correspondiente:

// Sin ;
HttpResponse::Ok().body("Hola Platzi!!!")

// Con ;
return HttpResponse::Ok().body("Hola Platzi!!!");

Ambas expresiones son equivalentes.

Servidor HTTP y conexión a BBDD

Teniendo el servidor web corriendo, configuremos el resto de la aplicación para realizar consultas a la BBDD y devolver los datos a través de los endpoints.

Comienza agregando al Cargo.toml la dependencia de diesel llamada r2d2 (Si, como Star Wars).

[dependencies]
diesel = { version = "1.4.4", features = ["postgres", "r2d2"] }

r2d2 nos permitirá crear un Pool de Conexión a la BBDD. En otras palabras, establecer la conexión a la base una sola vez, y luego compartir esta conexión con el resto de la aplicación.

Ahora si, configuremos el resto de la aplicación para conectarnos a la base de datos y preparar nuestros endpoints para consultar los mismos.

#[macro_use]

extern crate diesel;

pub mod schema;
pub mod models;

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

use dotenv::dotenv;
use std::env;

use diesel::prelude::*;
use diesel::pg::PgConnection;

// Librerías para crear una conexión a la BBDD y compartirla en toda la aplicación
use diesel::r2d2::{self, ConnectionManager};
use diesel::r2d2::Pool;


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

#[actix_web::main]
async fn main() -> std::io::Result<()> {

    dotenv().ok();
    let db_url = env::var("DATABASE_URL").expect("La variable de entorno DATABASE_URL no existe.");

    let connection = ConnectionManager::<PgConnection>::new(db_url);

    // El POOL sirve para compartir la conexión con otros servicios
    let pool = Pool::builder().build(connection).expect("No se pudo construir el Pool");

    HttpServer::new(move || {
        // Compartimos el pool de conexión a cada endpoint
        App::new().service(hello_wold).app_data(web::Data::new(pool.clone()))       
    }).bind(("127.0.0.1", 8080)).unwrap().run().await

}

Observa que en el HttpServer estamos utilizando una palabra reservada llamada move. move viene de un concepto aún más grande en Rust denominado Ownership, en pocas palabras, todo en Rust tiene un “dueño” y si queremos compartir el Pool de Conexión con otra función o módulo, debemos indicarlo explícitamente con esta palabra reservada.

No deja de ser un concepto relacionado con el Scope de una pieza de código, aunque es un concepto que no existe en otros lenguajes.

Rust es mono-hilo y move mueve, justamente, el hijo de ejecución principal de la aplicación, a esa otra función que necesitamos emplear y compartir un dato. Por eso las tareas son asíncronas, para permitirle a Rust procesar múltiples solicitudes a la vez. Muy similar a como funciona Javascript. Si es que estás familiarizado con el lenguaje, lo comprenderás más fácilmente.

Vuelve a ejecutar tu aplicación con cargo run para corroborar que el servidor HTTP aún funciona.

Y todo listo, la app está lista para mostrar datos tomados de la BBDD en el navegador web.


Contribución creada por: Kevin Fiorentino.