Overview
En esta guía, aprenderá a crear una aplicación web en Rust que utiliza Rocket como framework. Rocket es un framework para Rust que permite crear aplicaciones web seguras y con seguridad de tipos.
La aplicación de este tutorial consta de las siguientes capas:
Capa de base de datos: MongoDB proporciona almacenamiento y recuperación de datos.
Capa de aplicación: Rocket maneja solicitudes HTTP, enrutamiento y procesamiento lógico.
Capa de presentación: las plantillas HTML representan datos del restaurante en páginas web.
¿Por qué utilizar MongoDB con Rust y Rocket?
Al integrar MongoDB con Rust y Rocket, puedes aprovechar la seguridad de memoria y las características de rendimiento de Rust junto con el modelo orientado a documentos de MongoDB. El sistema de tipos de Rocket garantiza garantías en tiempo de compilación para tus endpoints de API, y el almacenamiento de documentos de MongoDB funciona de manera eficiente con las librerías de serialización de Rust, como Serde.
La combinación de Rust, Rocket y MongoDB es compatible con aplicaciones que requieren alto rendimiento, seguridad de tipos y escalabilidad. Por lo tanto, esta pila está bien diseñada para aplicaciones del mundo real, como API de alto rendimiento, microservicios o sistemas que requieren estrictas garantías de rendimiento.
Tutorial de inicio rápido
Este tutorial muestra cómo crear una aplicación web con Rust y Rocket. La aplicación accede a datos de muestra de restaurantes, los consulta y los muestra mediante plantillas HTML con estilos. El tutorial también incluye instrucciones para conectarse a un clúster de MongoDB alojado en MongoDB Atlas y acceder y mostrar datos de su base de datos.
Tip
Si prefiere conectarse a MongoDB utilizando el controlador Rust sin Rocket, consulte la Tutorialde inicio rápido del controlador Rust.
Configurar el proyecto
Siga los pasos de esta sección para instalar las dependencias del proyecto, crear un clúster Atlas y configurar la estructura de la aplicación.
Verificar los prerrequisitos
Para crear la aplicación de inicio rápido, instale el siguiente software en su entorno de desarrollo:
Requisito previo | notas |
|---|---|
Instale la última versión estable usando | |
Editor de código | Este tutorial utiliza Visual Studio Code con la extensión Rust, pero puedes usar el editor que prefieras. |
Aplicación de terminal y shell | Para usuarios de macOS, usen Terminal o una aplicación similar. Para usuarios de Windows, usen PowerShell. |
Crear un clúster de MongoDB Atlas
MongoDB Atlas es un servicio de base de datos en la nube totalmente administrado que aloja sus implementaciones de MongoDB. Si no tiene una implementación de MongoDB, puede crear un clúster de MongoDB gratis (sin necesidad de tarjeta de crédito) completando el tutorial de introducción a MongoDB. Este tutorial también muestra cómo cargar conjuntos de datos de muestra en su clúster, incluyendo la sample_restaurants base de datos utilizada en este tutorial.
Para conectarte a tu clúster de MongoDB, debes usar un URI de conexión. Para saber cómo recuperar tu URI de conexión, consulta la sección Agrega tu cadena de conexión del tutorial Primeros pasos de MongoDB.
Importante
Guarde su cadena de conexión en una ubicación segura.
Configurar las dependencias de su proyecto
Navegue hasta el archivo Cargo.toml y reemplace su contenido con el siguiente código:
[package] name = "rocket-quickstart" version = "0.1.0" edition = "2024" [dependencies] rocket = {version = "0.5.0", features = ["json"]} serde = "1.0.136" dotenv = "0.15.0" tokio = {version = "1.32.0", features = ["full"]} rocket_dyn_templates = { version = "0.1.0", features = ["handlebars"] } [dependencies.mongodb] version = "3.4.1"
Instale las dependencias ejecutando el siguiente comando desde su directorio rocket-quickstart:
cargo build
Este comando instala las siguientes dependencias:
Rocket, el framework web del proyecto con soporte JSON
Serde, para la serialización de datos
Controlador Rust de MongoDB para operaciones de MongoDB
Tokio, el entorno de ejecución asincrónico
rocket_dyn_templates, para la representación de plantillas de manillar
Crear la estructura del proyecto
Para crear los directorios y archivos necesarios para esta aplicación, ejecute los siguientes comandos desde su directorio rocket-quickstart:
mkdir -p src/models src/repository src/api templates static touch src/models/restaurant.rs src/repository/mongodb_repo.rs src/api/restaurant_api.rs touch src/models.rs src/repository.rs src/api.rs touch templates/index.hbs static/style.css
Estos comandos estructuran tu proyecto según el patrón de arquitectura modular de Rust, que agrupa los archivos en diferentes directorios según su propósito. Tras ejecutar los comandos, tendrás los siguientes directorios:
src/models:Contiene estructuras de datos para modelar datos de restaurantes.src/repository: Contiene lógica de acceso a la base de datossrc/api: Contiene manejadores de endpoints HTTPtemplates:Contiene un archivo de plantilla Handlebars para representar datosstatic:Contiene un archivo CSS para aplicar estilo.
Los comandos también crean archivos de declaración de módulos para cada directorio src/, que enumeran los módulos disponibles en sus directorios correspondientes y le permiten acceder a ellos en toda la aplicación.
Configurar el back end
Después de configurar la estructura y las dependencias del proyecto, siga los pasos de esta sección para conectarse a MongoDB y configurar sus modelos de datos.
Proporcione su URI de conexión
Establezca la variable de entorno MONGO_URI en su URI de conexión ejecutando el siguiente comando desde el directorio rocket-quickstart:
export MONGO_URI="<connection URI>"
Reemplace <connection URI> el marcador de posición con la URI de conexión que guardó en un paso anterior.
Crea tus modelos de datos
Navegue hasta el archivo src/models/restaurant.rs y pegue el siguiente código:
use serde::{Deserialize, Serialize}; use mongodb::bson::oid::ObjectId; pub struct Restaurant { pub id: Option<ObjectId>, pub name: Option<String>, pub borough: Option<String>, pub cuisine: Option<String>, pub address: Option<Address>, } pub struct Address { pub building: Option<String>, pub street: Option<String>, pub zipcode: Option<String>, }
Este archivo define los modelos Restaurant y Address, que representan los datos en la colección de muestra sample_restaurants.restaurants.
Para utilizar tus modelos en los archivos del proyecto, debes registrar el módulo restaurant, que corresponde al archivo restaurant.rs. Navegar al archivo src/models.rs y pegar el siguiente código:
pub mod restaurant; pub use restaurant::*;
Acceda a la colección de muestras de MongoDB
Navegue hasta el archivo src/repository/mongodb_repo.rs y pegue el siguiente código:
use std::env; use mongodb::{ bson::{extjson::de::Error, doc}, Client, Collection }; use crate::models::Restaurant; pub struct MongoRepo { col: Collection<Restaurant>, } impl MongoRepo { pub async fn init() -> Self { dotenv::dotenv().ok(); let uri = match env::var("MONGO_URI") { Ok(v) => v.to_string(), Err(_) => format!("Error loading env variable"), }; let client = Client::with_uri_str(uri).await.unwrap(); let db = client.database("sample_restaurants"); let col: Collection<Restaurant> = db.collection("restaurants"); MongoRepo { col } } pub async fn get_all_restaurants(&self) -> Result<Vec<Restaurant>, Error> { let mut cursor = self .col .find(doc! {}) .await .map_err(|e| Error::DeserializationError { message: e.to_string() })?; let mut restaurants: Vec<Restaurant> = Vec::new(); while cursor.advance().await.map_err(|e| Error::DeserializationError { message: e.to_string() })? { match cursor.deserialize_current() { Ok(restaurant) => restaurants.push(restaurant), Err(e) => { // Skips documents that can't be deserialized and logs the error eprintln!("Warning: Skipping document due to deserialization error: {}", e); continue; } } } Ok(restaurants) } pub async fn get_filtered_restaurants(&self) -> Result<Vec<Restaurant>, Error> { let filter = doc! { "borough": "Queens", "name": { "$regex": "Moon", "$options": "i" } }; let mut cursor = self .col .find(filter) .await .map_err(|e| Error::DeserializationError { message: e.to_string() })?; let mut restaurants: Vec<Restaurant> = Vec::new(); while cursor.advance().await.map_err(|e| Error::DeserializationError { message: e.to_string() })? { match cursor.deserialize_current() { Ok(restaurant) => restaurants.push(restaurant), Err(e) => { // Skips documents that can't be deserialized and logs the error eprintln!("Warning: Skipping document due to deserialization error: {}", e); continue; } } } Ok(restaurants) } }
Este archivo accede a la colección restaurants en la base de datos sample_restaurants. A continuación, define los siguientes métodos de consulta que recuperan los documentos de la colección:
get_all_restaurants():Recupera todos los documentos de la colecciónrestaurantsget_filtered_restaurants(): Recupera documentos en la colecciónrestaurantsque tienen un valorboroughde"Queens"y un valornameque contiene"Moon". El valor$optionsespecifica que estanamequery no distingue entre mayúsculas y minúsculas.
Este código también incluye lógica para omitir documentos que no se pueden deserializar. Esto evita errores cuando los documentos no contienen todos los campos definidos en el modelo Restaurant.
Luego, registre el módulo en src/repository.rs:
pub mod mongodb_repo; pub use mongodb_repo::*;
Configurar el front-end
Después de configurar la capa de datos, siga los pasos de esta sección para crear controladores y plantillas de API de Rocket para la interfaz de usuario.
Crear los controladores de API
Navegue hasta el archivo src/api/restaurant_api.rs y pegue el siguiente código:
use rocket::{State, serde::json::Json, get, http::Status}; use crate::{models::Restaurant, repository::MongoRepo}; pub async fn get_all_restaurants(db: &State<MongoRepo>) -> Result<Json<Vec<Restaurant>>, Status> { let restaurants = db.get_all_restaurants().await; match restaurants { Ok(restaurants) => Ok(Json(restaurants)), Err(_) => Err(Status::InternalServerError), } } pub async fn get_filtered_restaurants(db: &State<MongoRepo>) -> Result<Json<Vec<Restaurant>>, Status> { let restaurants = db.get_filtered_restaurants().await; match restaurants { Ok(restaurants) => Ok(Json(restaurants)), Err(_) => Err(Status::InternalServerError), } }
Este código crea los puntos finales /restaurants y /restaurants/browse. Estos puntos finales proporcionan datos JSON de los métodos de consulta que el código JavaScript del frontend consumirá para cargar y mostrar dinámicamente la información del restaurante.
Luego, registre el módulo API en src/api.rs:
pub mod restaurant_api; pub use restaurant_api::*;
Actualizar el archivo principal de la aplicación
Navegue hasta el archivo src/main.rs y pegue el siguiente código:
mod api; mod models; mod repository; extern crate rocket; use rocket::{get, fs::FileServer, response::content::RawHtml}; use rocket_dyn_templates::Template; use api::restaurant_api::{get_all_restaurants, get_filtered_restaurants}; use repository::MongoRepo; async fn index() -> RawHtml<String> { let template_content = std::fs::read_to_string("templates/index.hbs").unwrap_or_else(|_| { r#"<!DOCTYPE html> <html><head><title>Error</title></head> <body><h1>Template not found</h1></body></html>"#.to_string() }); RawHtml(template_content) } async fn browse() -> RawHtml<String> { let template_content = std::fs::read_to_string("templates/index.hbs").unwrap_or_else(|_| { r#"<!DOCTYPE html> <html><head><title>Error</title></head> <body><h1>Template not found</h1></body></html>"#.to_string() }); RawHtml(template_content) } async fn rocket() -> _ { let db = MongoRepo::init().await; rocket::build() .manage(db) .mount("/", routes![index, browse, get_all_restaurants, get_filtered_restaurants]) .mount("/static", FileServer::from("static/")) .attach(Template::fairing()) }
Este archivo configura el servidor web Rocket y configura las siguientes rutas:
/: Devuelve el código HTML que formatea la página de índice principal, que muestra todos los restaurantes/browse: Devuelve código HTML que da formato a la página de restaurantes filtrados
Crear la plantilla Handlebars
Navegue hasta el archivo templates/index.hbs y pegue el siguiente código:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Restaurant Explorer</title> <link rel="stylesheet" href="/static/style.css"> </head> <body> <div class="container"> <header> <h1>Restaurant Directory</h1> </header> <div class="controls"> <a href="/" class="btn" id="all-btn">All Restaurants</a> <a href="/browse" class="btn" id="browse-btn">Browse Filtered</a> </div> <div id="restaurants-container"> <div class="loading">Loading restaurants...</div> </div> </div> <script> // Sets the active button based on the current path const path = window.location.pathname; if (path === '/browse') { document.getElementById('browse-btn').classList.add('active'); } else { document.getElementById('all-btn').classList.add('active'); } // Loads the restaurant documents async function loadRestaurants() { try { const apiEndpoint = path === '/browse' ? '/restaurants/browse' : '/restaurants'; const response = await fetch(apiEndpoint); const restaurants = await response.json(); displayRestaurants(restaurants); } catch (error) { document.getElementById('restaurants-container').innerHTML = '<div class="error">Error loading restaurants. Please try again later.</div>'; } } // Displays restaurants in a grid format function displayRestaurants(restaurants) { const container = document.getElementById('restaurants-container'); if (restaurants.length === 0) { container.innerHTML = '<div class="error">No restaurants found.</div>'; return; } const restaurantsHTML = restaurants.map(function(restaurant) { return '<div class="restaurant-card">' + '<div class="restaurant-name">' + (restaurant.name || 'Unknown Name') + '</div>' + (restaurant.cuisine ? '<div class="cuisine-tag">' + restaurant.cuisine + '</div>' : '') + (restaurant.borough ? '<div class="restaurant-info"><strong>Borough:</strong> ' + restaurant.borough + '</div>' : '') + (restaurant.restaurant_id ? '<div class="restaurant-info"><strong>ID:</strong> ' + restaurant.restaurant_id + '</div>' : '') + (restaurant.address ? '<div class="address">' + (restaurant.address.building ? restaurant.address.building + ' ' : '') + (restaurant.address.street ? restaurant.address.street : '') + (restaurant.address.zipcode ? ', ' + restaurant.address.zipcode : '') + '</div>' : '') + '</div>'; }).join(''); container.innerHTML = '<div class="restaurants-grid">' + restaurantsHTML + '</div>'; } loadRestaurants(); </script> </body> </html>
Esta plantilla Handlebars utiliza JavaScript para crear una interfaz web adaptable que carga dinámicamente datos de restaurantes desde los puntos finales de su API y muestra los restaurantes en un diseño de cuadrícula interactivo.
Add CSS styling
Añade el siguiente código a static/style.css:
/* Modern, clean styling for the restaurant app */ * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; background: rgb(198, 240, 209); min-height: 100vh; } .container { max-width: 1200px; margin: 0 auto; padding: 20px; } header { text-align: center; margin-bottom: 40px; color: rgb(0, 0, 0); } h1 { font-size: 3rem; margin-bottom: 10px; } .subtitle { font-size: 1.2rem; opacity: 0.9; } .controls { display: flex; justify-content: center; gap: 20px; margin-bottom: 30px; } .btn { background: white; color: #0d8958; border: none; padding: 12px 24px; border-radius: 25px; font-weight: 600; text-decoration: none; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(0,0,0,0.2); } .btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0,0,0,0.3); } .btn.active { background: #0d8958; color: white; } .restaurants-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; margin-top: 20px; } .restaurant-card { background: white; border-radius: 15px; padding: 20px; box-shadow: 0 8px 25px rgba(0,0,0,0.1); transition: transform 0.3s ease, box-shadow 0.3s ease; } .restaurant-card:hover { transform: translateY(-5px); box-shadow: 0 12px 35px rgba(0,0,0,0.15); } .restaurant-name { font-size: 1.4rem; font-weight: 700; color: #000000; margin-bottom: 10px; } .restaurant-info { margin-bottom: 8px; } .restaurant-info strong { color: #555; } .cuisine-tag { display: inline-block; background: #0d8958; color: white; padding: 4px 12px; border-radius: 15px; font-size: 0.85rem; font-weight: 600; margin-top: 10px; } .address { color: #666; font-style: italic; margin-top: 8px; } .loading { text-align: center; color: white; font-size: 1.2rem; margin: 40px 0; } .error { text-align: center; color: #ff6b6b; background: white; padding: 20px; border-radius: 10px; margin: 20px 0; } @media (max-width: 768px) { .container { padding: 10px; } h1 { font-size: 2rem; } .controls { flex-direction: column; align-items: center; } .restaurants-grid { grid-template-columns: 1fr; } }
Este archivo CSS da estilo a los datos del restaurante que se muestran en las páginas web.
Ejecute su aplicación
Por último, siga los pasos de esta sección para ejecutar su aplicación web y explorar los datos del restaurante utilizando la interfaz del navegador.
Iniciar la aplicación Rocket
Navegue al directorio de su proyecto y ejecute el siguiente comando:
cargo run
Si la operación es exitosa, la salida del comando se asemeja al siguiente ejemplo:
Configured for debug. >> address: 127.0.0.1 >> port: 8000 ... Routes: >> (index) GET / >> (browse) GET /browse >> (get_all_restaurants) GET /restaurants >> (get_filtered_restaurants) GET /restaurants/browse >> (FileServer: static/) GET /static/<path..> [10] Fairings: >> Templating (ignite, liftoff, request) >> Shield (liftoff, response, singleton) Shield: >> X-Frame-Options: SAMEORIGIN >> Permissions-Policy: interest-cohort=() >> X-Content-Type-Options: nosniff Templating: >> directory: templates >> engines: ["hbs"] Rocket has launched from http://127.0.0.1:8000
Abra la aplicación web
Abra http://:127.0.0.1 8000 en su navegador web. La página de inicio muestra todos los restaurantes de la sample_restaurants.restaurants colección:

Luego, haga clic en el Browse Filtered Botón para ver restaurantes en Queens que tienen "Moon" en su nombre:

Probar los puntos finales de la API
También puedes probar los puntos finales de la API subyacente directamente ejecutando los siguientes comandos desde tu terminal:
curl http://127.0.0.1:8000/restaurants curl http://127.0.0.1:8000/restaurants/browse
Estos puntos finales devuelven los mismos datos que se muestran en la interfaz web, formateados como documentos JSON.
¡Felicitaciones por completar el tutorial de inicio rápido! Tras completar estos pasos, tendrá una aplicación web de Rust y Rocket que se conecta a su implementación de MongoDB, ejecuta consultas sobre datos de ejemplo de restaurantes y muestra los resultados en una interfaz web alojada localmente.
Recursos adicionales
Para obtener más información sobre Rust, Rocket y MongoDB, consulte los siguientes recursos:
Documentación delcontrolador Rust de MongoDB
Marco de serialización deSerde
Lenguaje de plantillas deHandlebars