Overview
En este tutorial, puedes aprender cómo crear una aplicación web en Rust utilizando el Rocket framework web. El Rust driver te permite aprovechar funcionalidades como la gestión de memoria, las vidas de objetos y el pooling de bases de datos para mejorar el rendimiento de tu aplicación.
Después de completar este tutorial, tendrás una aplicación web con rutas para realizar operaciones CRUD.
Tip
Aplicación completa
Para ver la aplicación de muestra completa de este tutorial, consulta la carpeta web-app-tutorial en GitHub.
Requisitos previos
Asegúrese de tener Rust 1.74 o posterior y Cargo, el gestor de paquetes de Rust, instalados en su entorno de desarrollo.
Para obtener información sobre cómo instalar Rust y Cargo, consulta la guía oficial de Rust en descargar e instalar Rust.
También debe configurar un clúster de MongoDB Atlas. Para aprender a crear un clúster, consulta el Crear una implementación de MongoDB paso de la guía de inicio rápido. Guarda tu cadena de conexión en una ubicación segura para usarla más adelante en el tutorial.
Pasos
Seleccione el entorno de ejecución asíncrono.
Cuando se utiliza el controlador Rust, se debe seleccionar el entorno de ejecución sincrónico o asincrónico. Este tutorial utiliza el entorno de ejecución asíncrona, que es más adecuado para construir APIs.
El driver se ejecuta con el entorno asíncrono tokio por defecto.
Para obtener más información sobre los tiempos de ejecución disponibles, consulta la guía API asincrónica y sincrónica.
Crea una base de datos y una colección en la interfaz de usuario de Atlas.
Accede a la interfaz de usuario de Atlas y luego selecciona la Collections pestaña en la configuración de tu clúster. Selecciona el botón + Create Database. En el modal, crea una base de datos llamada bread y, dentro de ella, una colección llamada recipes.
Insertar datos de muestra.
Selecciona el botón INSERT DOCUMENT y pega el contenido del archivo bread_data.json en la aplicación de muestra.
Después de insertar los datos, puedes ver los documentos de ejemplo en la colección recipes.
Instala Rocket.
Abre tu IDE y entra en tu directorio de proyecto. Ejecuta el siguiente comando desde la raíz del proyecto para instalar el framework web Rocket:
cargo add -F json rocket
Verifica que la lista de dependencias en tu archivo Cargo.toml contenga una entrada para rocket.
También debes añadir un crate desarrollado por Rocket que te permita utilizar un contenedor para administrar un pool de colecciones para las conexiones asíncronas realizadas por tu cliente de MongoDB. Esta caja permite parametrizar tus bases de datos y colecciones de MongoDB y hacer que cada función de la aplicación reciba su propia conexión para usar.
Ejecuta el siguiente comando para agregar la caja rocket_db_pools:
cargo add -F mongodb rocket_db_pools
Verifica que la lista de dependencias en tu archivo Cargo.toml contenga una entrada para rocket_db_pools que incluya una bandera de funcionalidad para mongodb.
Configura Rocket.
Para configurar Rocket para usar la base de datos bread, crea un archivo llamado Rocket.toml en la raíz del proyecto. Rocket busca este archivo para leer la configuración. También puedes almacenar tu cadena de conexión de MongoDB en este archivo.
Pega la siguiente configuración en el archivo Rocket.toml:
[default.databases.db] url = "<connection string>"
Para obtener más información sobre la configuración de Rocket, consulta Configuración en la documentación de Rocket.
Aprende sobre la estructura de la aplicación.
Antes de que empieces a escribir la API, aprende sobre la estructura de una aplicación Rocket simple y crea los archivos correspondientes en tu aplicación.
El siguiente diagrama demuestra la estructura de archivos que debe tener tu aplicación Rocket y explica la función de cada archivo:
. ├── Cargo.lock # Dependency info ├── Cargo.toml # Project and dependency info ├── Rocket.toml # Rocket configuration └── src # Directory for all app code ├── db.rs # Establishes database connection ├── main.rs # Starts the web app ├── models.rs # Organizes data └── routes.rs # Stores API routes
Cree el directorio src y los archivos que contiene, de acuerdo con el diagrama anterior. En este punto, los archivos pueden estar vacíos.
Configura la conexión a la base de datos.
Pega el siguiente código en el archivo db.rs:
use rocket_db_pools::{mongodb::Client, Database}; pub struct MainDatabase(Client);
También debes adjuntar la estructura de la base de datos a tu instancia de Rocket. Copia el siguiente código de plantilla en tu archivo main.rs:
mod db; mod models; mod routes; use rocket::{launch, routes}; use rocket_db_pools::Database; fn rocket() -> _ { rocket::build() .attach(db::MainDatabase::init()) // Paste mount() call below }
Tu IDE podría mostrar un error que indica que el cuerpo de la función está incompleto. Puede ignorar este error porque añadirá la llamada mount() en un paso posterior.
Cree modelos de datos.
Definir structs coherentes y útiles para representar tus datos es importante para mantener la seguridad de tipos y reducir errores en tiempo de ejecución.
En tu archivo models.rs, define una estructura Recipe que represente una receta para hornear pan:
use mongodb::bson::oid::ObjectId; use rocket::serde::{Deserialize, Serialize}; pub struct Recipe { pub id: Option<ObjectId>, pub title: String, pub ingredients: Vec<String>, pub temperature: u32, pub bake_time: u32, }
Configura rutas de API.
El enrutamiento permite al programa dirigir la solicitud al punto final adecuado para enviar o recibir los datos. El archivo routes.rs almacena todas las rutas definidas en la API.
Copia el siguiente código de plantilla en tu archivo routes.rs:
use crate::db::MainDatabase; use crate::models::Recipe; use mongodb::bson::doc; use mongodb::bson::oid::ObjectId; use rocket::{ delete, futures::TryStreamExt, get, http::Status, post, put, response::status, serde::json::Json, }; use rocket_db_pools::Connection; use serde_json::{json, Value}; // Paste index route below // Paste get_recipes route below // Paste get_recipe route below // Paste update_recipe route below // Paste delete_recipe route below // Paste create_recipe route below
Pega la siguiente ruta de index bajo el comentario // Paste index route below:
pub fn index() -> Json<Value> { Json(json!({"status": "It is time to make some bread!!!"})) }
Antes de guardar las rutas restantes, añade las rutas a la función principal de lanzamiento de Rocket.
En tu archivo main.rs, pega el siguiente código bajo el comentario // Paste mount() call below:
.mount( "/", routes![ routes::index, routes::get_recipes, routes::create_recipe, routes::get_recipe, routes::update_recipe, routes::delete_recipe, ], )
Implementar el manejo de errores y respuestas.
En tu aplicación, debes implementar el manejo de errores y respuestas personalizadas para abordar resultados inesperados de tus operaciones CRUD.
Instala la caja serde_json ejecutando el siguiente comando:
cargo add serde_json
Esta caja incluye el enum Value que representa un valor JSON.
Puede especificar que sus rutas devuelvan códigos de estado HTTP utilizando las estructuras status::Custom de Rocket, que le permiten especificar el código de estado HTTP y cualquier dato personalizado que se deba devolver. El siguiente paso describe cómo escribir rutas que devuelvan el tipo status::Custom.
Guardar las rutas para las operaciones CRUD.
Crear
Al intentar crear datos en MongoDB, pueden darse dos resultados:
El documento se crea correctamente, por lo que la aplicación devuelve
HTTP 201.Se produjo un error durante la inserción, por lo que la aplicación devuelve
HTTP 400.
Pega la siguiente ruta create_recipe() bajo el comentario // Paste create_recipe route below en tu archivo routes.rs:
pub async fn create_recipe( db: Connection<MainDatabase>, data: Json<Recipe>, ) -> status::Custom<Json<Value>> { if let Ok(res) = db .database("bread") .collection::<Recipe>("recipes") .insert_one(data.into_inner(), None) .await { if let Some(id) = res.inserted_id.as_object_id() { return status::Custom( Status::Created, Json( json!({"status": "success", "message": format!("Recipe ({}) created successfully", id.to_string())}), ), ); } } status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe could not be created"})), ) }
Lea
Cuando intentes leer datos de MongoDB, hay dos posibles resultados:
Devuelve el vector de documentos coincidentes.
Devuelve un vector vacío porque no hay documentos coincidentes o porque se ha producido un error.
Debido a estos resultados esperados, pega la siguiente ruta get_recipes() bajo el comentario // Paste get_recipes route below en tu archivo routes.rs:
pub async fn get_recipes(db: Connection<MainDatabase>) -> Json<Vec<Recipe>> { let recipes = db .database("bread") .collection("recipes") .find(None, None) .await; if let Ok(r) = recipes { if let Ok(collected) = r.try_collect::<Vec<Recipe>>().await { return Json(collected); } } return Json(vec![]); }
Recuperar por ID
Pega la siguiente ruta de get_recipe() bajo el comentario // Paste get_recipe route below:
pub async fn get_recipe( db: Connection<MainDatabase>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if let Ok(Some(recipe)) = db .database("bread") .collection::<Recipe>("recipes") .find_one(doc! {"_id": b_id.unwrap()}, None) .await { return status::Custom( Status::Ok, Json(json!({"status": "success", "data": recipe})), ); } return status::Custom( Status::NotFound, Json(json!({"status": "success", "message": "Recipe not found"})), ); }
Esta ruta recupera un solo documento por su valor _id.
Update
Pega la siguiente ruta de update_recipe() bajo el comentario // Paste update_recipe route below:
pub async fn update_recipe( db: Connection<MainDatabase>, data: Json<Recipe>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if let Ok(_) = db .database("bread") .collection::<Recipe>("recipes") .update_one( doc! {"_id": b_id.as_ref().unwrap()}, doc! {"$set": mongodb::bson::to_document(&data.into_inner()).unwrap()}, None, ) .await { return status::Custom( Status::Created, Json( json!({"status": "success", "message": format!("Recipe ({}) updated successfully", b_id.unwrap())}), ), ); }; status::Custom( Status::BadRequest, Json( json!({"status": "success", "message": format!("Recipe ({}) could not be updated successfully", b_id.unwrap())}), ), ) }
Esta ruta actualiza un solo documento mediante su valor _id.
Borrar
Pega la siguiente Ruta delete_recipe() después del comentario // Paste delete_recipe route below:
pub async fn delete_recipe( db: Connection<MainDatabase>, id: &str, ) -> status::Custom<Json<Value>> { let b_id = ObjectId::parse_str(id); if b_id.is_err() { return status::Custom( Status::BadRequest, Json(json!({"status": "error", "message":"Recipe ID is invalid"})), ); } if db .database("bread") .collection::<Recipe>("recipes") .delete_one(doc! {"_id": b_id.as_ref().unwrap()}, None) .await .is_err() { return status::Custom( Status::BadRequest, Json( json!({"status": "error", "message":format!("Recipe ({}) could not be deleted", b_id.unwrap())}), ), ); }; status::Custom( Status::Accepted, Json( json!({"status": "", "message": format!("Recipe ({}) successfully deleted", b_id.unwrap())}), ), ) }
Esta ruta elimina un único documento por su valor _id.
Rutas de prueba para realizar operaciones CRUD.
Inicie su aplicación ejecutando el siguiente comando en su terminal:
cargo run
En otra ventana de terminal, ejecuta el siguiente comando para probar la ruta create_recipe():
curl -v --header "Content-Type: application/json" --request POST --data '{"title":"simple bread recipe","ingredients":["water, flour"], "temperature": 250, "bake_time": 120}' http://127.0.0.1:8000/recipes
{"status":"success","message":"Recipe (684c4245f5a3ca09efa92593) created successfully"}
Ejecuta el siguiente comando para probar la ruta get_recipes():
curl -v --header "Content-Type: application/json" --header "Accept: application/json" http://127.0.0.1:8000/recipes/
[{"_id":...,"title":"artisan","ingredients":["salt","flour","water","yeast"],"temperature":404,"bake_time":5}, {"_id":...,"title":"rye","ingredients":["salt"],"temperature":481,"bake_time":28},...]
Ejecuta el siguiente comando para probar la ruta delete_recipe(). Sustituya el marcador de posición <id> con un valor _id conocido de su colección, que podría parecerse a 68484d020f561e78c03c7800:
curl -v --header "Content-Type: application/json" --header "Accept: application/json" --request DELETE http://127.0.0.1:8000/recipes/<id>
{"status":"","message":"Recipe (68484d020f561e78c03c7800) successfully deleted"}
Conclusión
En este tutorial, aprendiste cómo compilar una aplicación web simple con Rocket para realizar operaciones CRUD.
Recursos
Para obtener más información sobre las operaciones CRUD, consulte las siguientes guías: