como comentario el shortcut de unwrap es “?”
.bind(("0.0.0.0", 9900))?.run()
Bienvenida al curso
Introducción a Rust para backend
Gestión de modelos en el backend con el ORM Diesel
¿Qué es un ORM?
Errores frecuentes en la instalación de Diesel
Conexión con la base de datos
Creación de modelos (tablas)
Creando los registros
Leyendo los registros
Editando los registros
Eliminando los registros
Actix web framework (controlador)
Actix Web framework
¡Hola mundo, Actix!
Match case
Endpoint GET para ver todos los BlogPosts
Endpoint POST para agregar nuevos Posts
Tera templates (vistas)
¿Qué es Tera?
Primer template para post
Template para el index y los posts
Añadiendo diseño al blog
Despliegue del proyecto
Heroku para el despliegue del proyecto
Release
Continúa tu aprendizaje
Siguientes pasos, Rustacean
No tienes acceso a esta clase
¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera
Es momento de realizar el primer ¡Hola Mundo! Desde una aplicación web con Rust y lograr renderizar texto a un navegador.
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.
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.
Aportes 11
Preguntas 2
como comentario el shortcut de unwrap es “?”
.bind(("0.0.0.0", 9900))?.run()
Hubo una actualización de Diesel, para la version 2 (he tenido muchos problemas con respecto a las versiones) y ahora fue con Diesel, estuve como 3 horas revisando y revisando (soy totalmente nuevo) logre armar una solución completa para esta clase, pero actualizado.
extern crate diesel;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use dotenvy::dotenv;
use std::env;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::Pool;
use diesel::r2d2::{self, ConnectionManager};
pub mod models;
pub mod schema;
#[get("/")]
async fn hello_world() -> impl Responder {
return format!("Hello world!");
}
#[actix_web::main] // or #[tokio::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DB url variable doesn't found");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder()
.build(manager)
.expect("No se pudo construir la pool");
HttpServer::new(move || App::new().service(hello_world).app_data(pool.clone()))
.bind(("localhost", 8080))?
.run()
.await
}
Para el archivo cargo se debe de agregar r2d2 como feature en Diesel ya que no viene activo por defecto.
// Cargo.toml
[package]
name = "blog-platzi"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = { version = "2.0.0", features = ["postgres", "r2d2"] }
dotenvy = "0.15"
actix-web = "4"
Como pueden observar, se debe de agregar de manera manual r2d2 y con esto funciona correctamente la importación del paquete.
Espero pueda apoyar a alguno de ustedes que este con el mismo problema
En lugar de 0.0.0.0
, conviene siempre utilizar localhost o 127.0.0.1
.
Actualmente .data() aparece como deprecado, una alternativa sugerido por Rust es cambiarlo por app_data()
En principio, no debería de cambiar nada relacionado con el curso
Diesel ha cambiado mucho, al dia de hoy 25/07/2023 pude hacer funcionar el programa.
Resulta que uno debe descargar el archivo gettext0.21-iconv1.16-shared-64 (buscar en Internet), renombrarlo y ponerlo en la carpeta src para que se pueda ejecutar el programa.
Como recomendacion, si algo no funciona vayan a la documentacion porque lo mas seguro es que han actualizado algo, tambien es bueno revisar las respuestas de
Si no comprendí mal, todo en Rust tiene un “dueño”. El concepto de Ownership indica que, en este ejemplo, el Pool de Conexión pertenece a main()
y si queremos utilizarlo en otra función debemos compartirlo con la palabra reservada move
para mover el hilo de ejecución principal a esa otra función, ya que Rust es mono-hilo. Similar a como funciona NodeJS que también lo es y necesita de la asincronisidad para procesar múltiples solicitudes a la vez.
Hector dice que: “ya sabemos que significa cada cosa”, frase que me acaba de generar bastante incomodidad porque habla como si lo hubiese explicado todo, cuando en realidad no ha sido así. Muchos huecos conceptuales, parase ser que presuntamente el curso es improvisado, como si estuviera tratando de explicar el ejemplo de inicio de alguna documentación. Creo que sale mas eficaz leer personalmente la documentación y generar apoyo con chatGPT.
Si alguien se siente perdido hasta este punto, pare, no continúe. Tome aire, respire profundo, auto regule la frustración que siente hasta el momento, evita sentir enojo y claro que te entiendo, el curso básico de Rust no tiene nada que ver con este curso. Por esa razón te sugiero que ve a leer los siguiente libros y en el proceso que comprendas un par de cosas mas, regresa y continua si ese es tu deseo. Quizá y aprendas un par de cosas mas viendo este tutorial, y no, no todos los cursos de Platzi son así, asi que no te rindas. Si, es verdad, te va a tomar un poquito mas de tiempo leer los libros pero créeme, valdrá el esfuerzo, recuerda que es importante construir bases solidas.
Libros sugeridos:
Para entender mejor la “move” keyword
Closures Rust book
O el link completo: https://doc.rust-lang.org/book/ch13-01-closures.html?highlight=closure#moving-captured-values-out-of-closures-and-the-fn-traits
let mut x = String::from("Hello");
let func = move || { // <--- Con la move keyword, la closure toma ownership de la variable 'x'
// Move es como decir: "hey pasa la variable x a esta function como valor y no como referencia"
x.push_str(" World!");
};
println!("{}", x); // Y al tomar ownership de 'x' no se puede usar aquí
en la version actix-web = “4.3.1” ya el pool de datos se implementa de una manera diferente
HttpServer::new( move || {
App::new()
//cambios por version
.app_data(web::Data::new(pool.clone()))
//
.service(hello_world)
})
use diesel::prelude:😗;
Esto al inicio del archivo de models me ayudó con los Queryable que me aparecia error
¿Quieres ver más aportes, preguntas y respuestas de la comunidad?