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 en 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 gestiona las solicitudes HTTP, la gestión de rutas y el procesamiento lógico.
Capa de presentación: las plantillas HTML representan datos del restaurante en páginas web.
¿Por qué usar 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 da soporte a aplicaciones que requieren alto rendimiento, seguridad de tipos y escalabilidad. Como resultado, esta pila está bien diseñada para aplicaciones del mundo real, tales como API de alto rendimiento, microservicios o sistemas que requieren estrictas garantías de rendimiento.
Tutorial de inicio rápido
Este tutorial te muestra cómo compilar una aplicación web que utilice Rust y Rocket. La aplicación accede a datos de restaurantes de muestra, query los datos y los muestra a través de plantillas HTML con estilos. El tutorial también incluye instrucciones para conectarse a un clúster de MongoDB alojado en MongoDB Atlas, acceder y mostrar datos de su base de datos.
Tip
Si prefiere conectarse a MongoDB utilizando el controlador Rust sin Rocket, consulte la Tutorial Guía rápida del controlador Rust.
Configurar el proyecto
Sigue los pasos de esta sección para instalar las dependencias del proyecto, crear un clúster de Atlas y configurar la estructura de la aplicación.
Verificar los prerrequisitos
Para crear la aplicación Quick Start, instala el siguiente software en tu entorno de desarrollo:
Requisito previo | notas |
|---|---|
Instala 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. |
Crea un clúster de MongoDB Atlas
MongoDB Atlas es un servicio de base de datos en la nube completamente gestionado que aloja tus implementaciones de MongoDB. Si no tienes una implementación de MongoDB, puedes crear un clúster de MongoDB de forma gratuita (no se requiere tarjeta de crédito) completando el tutorial Introducción a MongoDB. El tutorial para comenzar con MongoDB también muestra cómo cargar conjuntos de datos de muestra en tu clúster, incluyendo la base de datos sample_restaurants que se usa 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
Guarda tu cadena de conexión en una ubicación segura.
Configura las dependencias de tu proyecto
Navega al archivo Cargo.toml y reemplaza 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"
Instala las dependencias ejecutando el siguiente comando desde tu directorio rocket-quickstart:
cargo build
Este comando instala las siguientes dependencias:
Rocket, el marco web del proyecto con soporte para JSON
Serde, para la serialización de datos
Controlador Rust de MongoDB para operaciones de MongoDB
Tokio, el entorno de ejecución asíncrono
rocket_dyn_templates, para la representación de plantillas Handlebars
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 para seguir el patrón de arquitectura modular de Rust, que agrupa los archivos en diferentes directorios según su propósito. Después de ejecutar los comandos, tienes los siguientes directorios:
src/models: Contiene estructuras de datos para modelar los datos de los restaurantessrc/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ódulo para cada directorio src/, que listan los módulos disponibles en sus directorios correspondientes y 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.
Proporciona tu 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
Navega al archivo src/models/restaurant.rs y pega 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::*;
Acceder a la colección de muestra de MongoDB
Navega al archivo src/repository/mongodb_repo.rs y pega 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. Luego, define los siguientes métodos de query que recuperan documentos de la colección:
get_all_restaurants(): Recupera todos los documentos en 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 pueden ser deserializados. Esto evita errores cuando los documentos no contienen cada campo definido en el Restaurant modelo.
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 Rocket API para la interfaz de usuario.
Crea los manejadores de API
Navega al archivo src/api/restaurant_api.rs y pega 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 endpoints /restaurants y /restaurants/browse. Estos endpoints proporcionan datos JSON de tus métodos query que serán consumidos por el código JavaScript frontend para cargar y mostrar dinámicamente la información de los restaurantes.
Luego, registra el módulo de la API en src/api.rs:
pub mod restaurant_api; pub use restaurant_api::*;
Actualizar el archivo principal de la aplicación
Navega al archivo src/main.rs y pega 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 principal del índice, que muestra todos los restaurantes/browse: Devuelve código HTML que da formato a la página de restaurantes filtrados
Crea la plantilla Handlebars
Navega al archivo templates/index.hbs y pega 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 Handlebars plantilla utiliza JavaScript para crear una interfaz web responsiva que carga dinámicamente datos de restaurantes desde tus endpoints API y muestra los restaurantes en un diseño de cuadrícula interactivo.
Add CSS styling
Agrega 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 mostrados en las páginas web.
Ejecutar la aplicación
Por último, sigue los pasos de esta sección para ejecutar tú aplicación web y explorar los datos del restaurante mediante la interfaz del navegador.
Inicia 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
Abrir la aplicación web
Abre http://127.0.0.1:8000 en tu navegador web. La página de inicio muestra todos los restaurantes en la colección sample_restaurants.restaurants:

Luego, haga clic en el Browse Filtered botón para ver restaurantes en Queens que tengan "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 endpoints devuelven los mismos datos que se muestran en la interfaz web, formateados como documentos JSON.
¡Felicitaciones por completar el tutorial de Inicio rápido! Después de completar estos pasos, tienes una aplicación web de Rust y Rocket que se conecta a tu implementación de MongoDB, ejecuta consultas en datos de muestra 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 Rocket
Controlador Rust de MongoDB documentación
Serde framework de serialización
Lenguaje de plantillas Handlebars