Make the MongoDB docs better! We value your opinion. Share your feedback for a chance to win $100.
Click here >
Menu Docs
Página inicial do Docs
/ /

Aplicação web CRUD com Foguete

Neste tutorial, você pode aprender a criar um aplicação web Rust usando a estrutura web Rocket. O driver Rust permite que você aproveite recursos como gerenciamento de memória, tempo de vida e pooling de banco de dados para melhorar o desempenho do seu aplicativo.

Depois de concluir este tutorial, você terá um aplicação da web com rotas para executar operações CRUD.

Dica

Aplicativo completo

Para visualizar o aplicativo de amostra completo deste tutorial, consulte a pasta web-app-tutorial no GitHub.

Verifique se você tem o Rust 1.74 ou posterior e o Cargo, o gerenciador de pacote do Rust, instalado em seu ambiente de desenvolvimento.

Para obter informações sobre como instalar o Rust and Cargo, consulte o guia oficial do Rust sobre como baixar e instalar o Rust.

Você também deve configurar um cluster MongoDB Atlas . Para saber como criar um cluster, consulte a etapa Criar uma implementação do MongoDB do guia Início rápido. Salve sua string de conexão em um local seguro para usar mais tarde no tutorial.

1

Execute os seguintes comandos para criar e inserir o diretório de projeto do rust-crud-app:

cargo new rust-crud-app
cd rust-crud-app
2

Execute o seguinte comando para adicionar o driver Rust:

cargo add mongodb

Verifique se o arquivo Cargo.toml contém a seguinte entrada para o driver Rust:

[dependencies]
mongodb = "3.6.0"
3

Ao usar o driver Rust, você deve selecionar o tempo de execução síncrono ou assíncrono. Este tutorial usa o tempo de execução assíncrono, que é mais adequado para criar APIs.

O driver é executado com o tempo de execução assíncrono tokio por padrão.

Para saber mais sobre os tempos de execução disponíveis, consulte o guia de APIs assíncronas e síncronas.

4

Acesse a UI do Atlas e selecione a aba Collections nas configurações do cluster. Selecione o botão + Create Database. No modal, crie um banco de dados chamado bread e, dentro dele, uma coleção chamada recipes.

5

Selecione o botão INSERT DOCUMENT e cole o conteúdo do arquivo Bread_data.json no aplicativo de amostra.

Após inserir os dados, você pode visualizar os documentos de amostra na coleção recipes.

6

Abra seu IDE e insira seu diretório de projeto . Execute o seguinte comando a partir da raiz do seu projeto para instalar a estrutura web doRocket:

cargo add -F json rocket

Verifique se a lista de dependências em seu arquivo Cargo.toml contém uma entrada para rocket.

Você também deve adicionar uma crate desenvolvida pela Spark que permite usar um wrapper para gerenciar um pool de coleta para as conexões assíncronas feitas pelo cliente MongoDB . Esta crate permite parametrizar seus bancos de dados e coleções MongoDB e fazer com que cada função de aplicativo receba sua própria conexão para uso.

Execute o seguinte comando para adicionar a crate rocket_db_pools :

cargo add -F mongodb rocket_db_pools

Verifique se a lista de dependências em seu arquivo Cargo.toml contém uma entrada para rocket_db_pools que contém um sinalizador de recurso para mongodb.

7

Para configurar o Foguete para usar o banco de dados do bread, crie um arquivo chamado Rocket.toml na raiz do seu projeto . O Foguete procura este arquivo para ler as definições de configuração. Você também pode armazenar sua string de conexão do MongoDB neste arquivo.

Cole a seguinte configuração no arquivo Rocket.toml:

[default.databases.db]
url = "<connection string>"

Para saber mais sobre como configurar o Rocket, consulte Configuração na documentação do Rocket.

8

Antes de começar a escrever a API, saiba mais sobre a estrutura de um aplicativo Foguete simples e crie os arquivos correspondentes em seu aplicação.

O diagrama a seguir demonstra a estrutura de arquivos que seu aplicativo Foguete deve ter e explica a função de cada arquivo:

.
├── 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

Crie o diretório src e os arquivos que ele contém, de acordo com o diagrama anterior. Neste ponto, os arquivos podem estar vazios.

9

Cole o código abaixo no arquivo db.rs :

src/db.rs
use rocket_db_pools::{mongodb::Client, Database};
#[derive(Database)]
#[database("db")]
pub struct MainDatabase(Client);

Você também deve anexar a estrutura do banco de dados à sua instância do Foguete. Copie o seguinte código de modelo no seu arquivo main.rs:

src/main.rs
mod db;
mod models;
mod routes;
use rocket::{launch, routes};
use rocket_db_pools::Database;
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(db::MainDatabase::init())
// Paste mount() call below
}

Seu IDE pode gerar um erro informando que o corpo da função está incompleto. Você pode ignorar este erro, porque você adicionará a chamada mount() em uma etapa posterior.

10

Definir estruturas consistentes e úteis para representar seus dados é importante para manter a segurança do tipo e reduzir erros de tempo de execução.

Em seu arquivo models.rs, defina uma estrutura Recipe que represente uma receita para assar pão:

src/models.rs
use mongodb::bson::oid::ObjectId;
use rocket::serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct Recipe {
#[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
pub id: Option<ObjectId>,
pub title: String,
pub ingredients: Vec<String>,
pub temperature: u32,
pub bake_time: u32,
}
11

O roteamento permite que o programa direcione a solicitação para o endpoint apropriado para enviar ou receber os dados. O arquivo routes.rs armazena todas as rotas definidas na API.

Copie o seguinte código de modelo no seu arquivo routes.rs:

src/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

Cole a seguinte rota index sob o comentário // Paste index route below:

#[get("/")]
pub fn index() -> Json<Value> {
Json(json!({"status": "It is time to make some bread!!!"}))
}

Antes de escrever as rotas restantes, adicione as rotas à principal função de lançamento do Foguete.

No seu arquivo main.rs, cole o seguinte código abaixo do comentário // Paste mount() call below:

.mount(
"/",
routes![
routes::index,
routes::get_recipes,
routes::create_recipe,
routes::get_recipe,
routes::update_recipe,
routes::delete_recipe,
],
)
12

Em seu aplicativo, você deve implementar o tratamento de erros e respostas personalizadas para lidar com resultados inesperados de suas operações CRUD.

Instale a crate serde_json executando o seguinte comando:

cargo add serde_json

Esta crate inclui o enumeração Value que representa um valor JSON.

Você pode especificar que suas rotas retornem códigos de status HTTP usando as estruturas status::Custom do Vector, que permitem especificar o código de status HTTP e quaisquer dados personalizados a serem retornados. A etapa a seguir descreve como escrever rotas que retornam o tipo status::Custom.

13

Quando você tenta criar dados no MongoDB, há duas saídas possíveis:

  • O documento foi criado com sucesso, portanto, seu aplicativo retorna HTTP 201.

  • Ocorreu um erro durante a inserção, portanto seu aplicativo retorna HTTP 400.

Cole a seguinte rota create_recipe() sob o comentário // Paste create_recipe route below no seu arquivo routes.rs:

src/routes.rs
#[post("/recipes", data = "<data>", format = "json")]
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"})),
)
}

Quando você tenta ler dados do MongoDB, há duas saídas possíveis:

  • Retorne o vetor de documentos correspondentes.

  • Retornar um vetor vazio, porque não há documentos correspondentes ou porque ocorreu um erro.

Devido a esses resultados esperados, cole a seguinte rota get_recipes() sob o comentário // Paste get_recipes route below em seu arquivo routes.rs:

src/routes.rs
#[get("/recipes", format = "json")]
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![]);
}

Cole a seguinte rota get_recipe() sob o comentário // Paste get_recipe route below:

src/routes.rs
#[get("/recipes/<id>", format = "json")]
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 rota recupera um único documento pelo seu valor _id.

Cole a seguinte rota update_recipe() sob o comentário // Paste update_recipe route below:

src/routes.rs
#[put("/recipes/<id>", data = "<data>", format = "json")]
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 rota atualiza um único documento pelo seu valor de _id.

Cole a seguinte rota delete_recipe() após o comentário // Paste delete_recipe route below:

src/routes.rs
#[delete("/recipes/<id>")]
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 rota exclui um único documento pelo seu valor de _id.

14

Inicie seu aplicação executando o seguinte comando em seu terminal:

cargo run

Em outra janela do terminal, execute o seguinte comando para testar a rota 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"}

Execute o seguinte comando para testar a rota 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},...]

Execute o seguinte comando para testar a rota delete_recipe(). Substitua o espaço reservado <id> por um valor _id conhecido da sua coleção, que pode se assemelhar 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"}

Neste tutorial, você aprendera como construir um aplicação web simples com oRocket para executar operações CRUD.

Para saber mais sobre as operações CRUD, consulte os seguintes guias:

Voltar

Armazenar arquivos grandes