Rocket é o framework web em Rust com a API mais declarativa do ecossistema: rotas definidas por macros de atributo, dados de requisição validados por tipos, estado compartilhado gerenciado pelo próprio framework e middleware (fairings) anexado explicitamente. Desde a série 0.5, o Rocket é assíncrono por completo, roda sobre o Tokio e trata a maioria das decisões de uma API web — validação de formulários, cookies, sessões, templates e pools de banco — com módulos oficiais em vez de escolhas manuais de crates.
Se você pesquisou rocket rust, rocket framework ou rust web framework, esta página explica o que o Rocket é, como instalar e como escrever sua primeira API — com exemplos em português e o mesmo caminho progressivo usado nos guias de Axum e Actix Web.
# Começo mínimo
[dependencies]
rocket = "0.5"
serde = { version = "1", features = ["derive"] }
#[macro_use] extern crate rocket;
#[get("/")]
fn raiz() -> &'static str {
"Olá, mundo!"
}
#[rocket::main]
async fn main() {
let _ = rocket::build()
.mount("/", routes![raiz])
.launch()
.await
.expect("Falha ao iniciar o Rocket");
}
Comece aqui: do projeto vazio ao primeiro servidor Rocket
Com Rust e o Cargo instalados, crie um projeto de estudo e adicione as dependências:
cargo new minha-api-rocket
cd minha-api-rocket
cargo add rocket serde --features serde/derive
Substitua o src/main.rs pelo exemplo Hello World acima e rode:
cargo run
O servidor sobe em http://localhost:8000 (o endereço padrão do Rocket em ambiente debug) e o log do terminal mostra a tabela de rotas montadas — o framework imprime cada rota com o método, o caminho e o nome do handler. Não é necessário instalar banco de dados nem configurar templates para testar a primeira rota.
Antes de avançar, três diferenças de mentalidade em relação a Axum e Actix Web:
- Rotas são atributos dos handlers. O
#[get("/caminho")]declara rota e handler juntos; não existe uma tabela central de rotas. - Parâmetros são tipados na assinatura. O Rocket infere o que cada argumento significa pelo tipo:
&strem path, guard de query,State<T>etc. - Middleware é attach, não wrap. Comportamentos globais entram com
.attach(fairing)em vez de envolver cada rota.
Instalação
O Rocket 0.5 exige uma versão de Rust razoavelmente recente — recomenda-se manter a toolchain atualizada com rustup:
rustup update stable
cargo add rocket
Dependências comuns conforme o guia avança:
[dependencies]
rocket = { version = "0.5", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
A feature json habilita o suporte nativo a corpos JSON via Serde: o extractor Json<T> para entrada e o responder para saída. Para templates dinâmicos, banco de dados, cookies privados e arquivos estáticos existem crates oficiais (rocket_dyn_templates, rocket_db_pools), apresentadas nas seções seguintes.
Verifique a instalação com o build de produção, que também mostra avisos de código morto:
cargo build --release
Uso Básico
Hello World
O menor servidor Rocket útil:
#[macro_use] extern crate rocket;
#[get("/")]
fn raiz() -> &'static str {
"Olá, mundo!"
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![raiz])
}
A macro #[launch] é um atalho: ela gera o main assíncrono, sobe o servidor e gerencia o shutdown sozinha. Quando você precisa de controle fino (configurar endereço, aguardar shutdown graceful), troque para #[rocket::main] e chame .launch().await manualmente.
Rotas, paths dinâmicos e query strings
Rotas declaram parâmetros dinâmicos direto no atributo. O tipo do argumento define como o valor é lido e validado:
use rocket::response::Debug;
#[get("/ola/<nome>")]
fn saudacao(nome: &str) -> String {
format!("Olá, {}!", nome)
}
// Parâmetro tipado: um id inválido nem chega ao handler
#[get("/usuarios/<id>")]
fn obter_usuario(id: u32) -> String {
format!("Usuário {}", id)
}
// Parâmetros opcionais em query string: ?pagina=2&limite=10
#[get("/produtos?<pagina>&<limite>")]
fn listar_produtos(pagina: Option<u32>, limite: Option<u32>) -> String {
let pagina = pagina.unwrap_or(1);
let limite = limite.unwrap_or(20);
format!("Página {} com até {} produtos", pagina, limite)
}
#[launch]
fn rocket() -> _ {
rocket::build().mount("/", routes![saudacao, obter_usuario, listar_produtos])
}
Ponto central do design do Rocket: /usuarios/<id> com id: u32 não aceita /usuarios/abc. A requisição inválida é roteada para o próximo handler compatível (ou vira 404/422), sem uma linha de validação escrita por você. Um id negativo, uma string não numérica ou um overflow retornam erro antes do handler rodar — validação de entrada pela assinatura da função, não por ifs.
Ranking de rotas: quando dois handlers podem atender à mesma requisição, o Rocket usa a ordem declarada em routes![] como prioridade e o ranking automático de especificidade (rotas com menos parâmetros dinâmicos ganham de rotas com mais). Na prática, declare a rota mais específica primeiro e teste conflitos com o framework de testes.
JSON de entrada e saída
Com a feature json, o Rocket integra Serde de ponta a ponta:
use rocket::serde::{Deserialize, Serialize, json::Json};
#[derive(Deserialize)]
#[serde(crate = "rocket::serde")]
struct NovoProduto<'r> {
nome: &'r str,
preco_em_centavos: u64,
}
#[derive(Serialize)]
#[serde(crate = "rocket::serde")]
struct Produto {
id: u32,
nome: String,
preco_em_centavos: u64,
}
#[post("/produtos", data = "<novo>")]
fn criar_produto(novo: Json<NovoProduto>) -> Json<Produto> {
let produto = Produto {
id: 1,
nome: novo.nome.to_string(),
preco_em_centavos: novo.preco_em_centavos,
};
Json(produto)
}
O argumento data = "<novo>" liga o corpo da requisição ao extractor Json<T>: um corpo malformado ou que não casa com o schema devolve 422 Unprocessable Entity automaticamente. Devolver Json(produto) serializa a resposta com o Content-Type: application/json.
Para respostas com controle de status, use Result com um responder de erro:
use rocket::response::status;
use rocket::http::Status;
#[get("/produtos/<id>")]
fn obter_produto(id: u32) -> Result<Json<Produto>, status::NotFound<String>> {
if id == 1 {
Ok(Json(Produto { id, nome: "Café".into(), preco_em_centavos: 1500 }))
} else {
Err(status::NotFound(format!("Produto {} não encontrado", id)))
}
}
Managed state: compartilhando estado entre handlers
Estado global — pools de banco, clientes HTTP, configuração — entra no builder com .manage() e chega nos handlers como &State<T>:
use rocket::State;
use std::sync::atomic::{AtomicUsize, Ordering};
struct Contador {
visitas: AtomicUsize,
}
#[get("/visitas")]
fn visitas(contador: &State<Contador>) -> String {
let atual = contador.visitas.fetch_add(1, Ordering::SeqCst) + 1;
format!("Esta página foi vista {} vezes", atual)
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(Contador { visitas: AtomicUsize::new(0) })
.mount("/", routes![visitas])
}
Regras práticas do managed state:
- O Rocket exige que o tipo seja
Send + Sync + 'static; pools como o do SQLx já satisfazem isso. - Um único valor por tipo: para dois pools do mesmo tipo, encapsule em uma struct.
- O
Stateé uma referência barata — não clone o conteúdo só para passar adiante.
Formulários, cookies e sessões
O Rocket nasceu com suporte de primeira classe a HTML forms, o que o torna forte em aplicações web tradicionais, não só em APIs:
use rocket::form::Form;
#[derive(FromForm)]
struct Login<'r> {
usuario: &'r str,
senha: &'r str,
}
#[post("/login", data = "<form>")]
fn login(form: Form<Login>) -> String {
format!("Usuário {} autenticado", form.usuario)
}
Cookies vêm tipados com &CookieJar e o Rocket oferece cookies privados (assinados) para sessões leves:
use rocket::http::{Cookie, CookieJar};
#[post("/sessao")]
fn criar_sessao(jar: &CookieJar<'_>) -> &'static str {
jar.add_private(Cookie::new("usuario", "maria"));
"Sessão criada"
}
#[get("/sessao")]
fn ler_sessao(jar: &CookieJar<'_>) -> String {
match jar.get_private("usuario") {
Some(cookie) => format!("Sessão de {}", cookie.value()),
None => "Sem sessão".into(),
}
}
Recursos Avançados
Request guards: autenticação como tipo
O mecanismo mais elegante do Rocket é o guard: um tipo que decide se a requisição chega ao handler. Autenticação vira parte da assinatura:
use rocket::request::{self, Request, FromRequest};
use rocket::outcome::Outcome;
struct UsuarioAutenticado {
nome: String,
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for UsuarioAutenticado {
type Error = ();
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
match req.headers().get_one("Authorization") {
Some(token) if token == "Bearer token-secreto" => {
Outcome::Success(UsuarioAutenticado { nome: "maria".into() })
}
_ => Outcome::Error((rocket::http::Status::Unauthorized, ())),
}
}
}
#[get("/painel")]
fn painel(usuario: UsuarioAutenticado) -> String {
format!("Bem-vinda ao painel, {}", usuario.nome)
}
Sem token válido, /painel responde 401 Unauthorized sem o handler executar. O mesmo mecanismo serve para rate limits por IP, injeção de correlação de tracing, idioma preferido e qualquer decisão pré-handler. Guards compõem: um handler pode exigir UsuarioAutenticado e Admin como argumentos consecutivos.
Fairings: middleware explícito
Fairings são hooks globais anexados com .attach(). Um fairing de log simples:
use rocket::{Request, Data, fairing::{Fairing, Kind, Info}};
use rocket::futures;
struct LogRequisicoes;
#[rocket::async_trait]
impl Fairing for LogRequisicoes {
fn info(&self) -> Info {
Info { name: "Log de requisições", kind: Kind::Request }
}
async fn on_request(&self, req: &mut Request<'_>, _data: &mut Data<'_>) {
println!("{} {}", req.method(), req.uri());
}
}
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(LogRequisicoes)
.mount("/", routes![raiz])
}
Hooks disponíveis: on_request (antes do roteamento), on_response (antes de enviar a resposta — ideal para cabeçalhos de segurança), on_launch, on_shutdown e on_ignite. O Rocket já traz fairings prontos, como o de coleta de métricas. A diferença cultural para o ecossistema Tower: fairings são globais e explícitos; layers Tower envolvem serviços individualmente e são reutilizáveis entre frameworks.
Banco de dados com rocket_db_pools
A crate oficial rocket_db_pools integra pools de conexão ao managed state, com suporte a PostgreSQL via SQLx, SQLite, MySQL e MongoDb:
[dependencies]
rocket = { version = "0.5", features = ["json"] }
rocket_db_pools = { version = "0.2", features = ["sqlx_postgres"] }
sqlx = { version = "0.8", default-features = false, features = ["postgres", "macros", "runtime-tokio"] }
use rocket_db_pools::{Database, Connection};
use rocket::{Build, Rocket};
#[derive(Database)]
#[database("meubanco")]
struct MeuBanco(sqlx::PgPool);
#[get("/contagem")]
async fn contagem(db: &MeuBanco) -> Result<String, String> {
let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM produtos")
.fetch_one(&**db)
.await
.map_err(|e| e.to_string())?;
Ok(format!("{} produtos", row.0))
}
#[rocket::launch]
fn rocket() -> _ {
rocket::build()
.attach(MeuBanco::init())
.mount("/", rocket::routes![contagem])
}
A string meubanco aponta para a configuração em Rocket.toml:
[default.databases.meubanco]
url = "postgres://app:senha@localhost/app"
O pool é criado, gerenciado e encerrado junto com o ciclo de vida da aplicação. Para queries complexas e migrações, o fluxo do tutorial de SQLx aplica igualmente em serviços Rocket.
Templates dinâmicos com Tera
Para páginas HTML renderizadas no servidor, rocket_dyn_templates traz o motor Tera embutido:
use rocket_dyn_templates::{Template, context};
#[get("/pagina")]
fn pagina() -> Template {
Template::render("pagina", context! {
titulo: "Início",
itens: vec!["Café", "Chimarrão", "Pão de queijo"],
})
}
Com fairings e templates, o Rocket cobre o ciclo completo de uma aplicação web monolítica em uma única stack — um perfil diferente do backend de API puro.
Testes
O Rocket traz um framework de testes local que dispara requisições reais contra a aplicação em memória:
#[cfg(test)]
mod testes {
use super::*;
use rocket::local::asynchronous::Client;
use rocket::uri;
#[rocket::async_test]
async fn raiz_retorna_ola() {
let client = Client::tracked(rocket()).await.expect("cliente válido");
let resposta = client.get(uri!(raiz)).dispatch().await;
assert_eq!(resposta.into_string().await, Some("Olá, mundo!".into()));
}
}
Client::tracked mantém cookies entre requisições — perfeito para testar sessões e autenticação. A macro uri! gera a URI a partir da rota declarada, então renomear caminhos quebra o teste em tempo de compilação, não em produção.
Configuração por ambiente
Todo o comportamento de runtime vem do Rocket.toml (perfis default, debug, release) ou de variáveis de ambiente ROCKET_:
[default]
address = "0.0.0.0"
port = 8000
workers = 8
[release]
log_level = "warn"
ident = "minha-api"
Segredos não vão para o arquivo versionado: use ROCKET_... no ambiente do deploy ou leia via crate de configuração como a Config, integrando com o managed state.
Deploy em produção
O caminho de produção para um binário Rocket segue o padrão do ecossistema:
FROM rust:1 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=builder /app/target/release/minha-api-rocket /usr/local/bin/app
USER 1000
CMD ["./app"]
No VPS, systemd ou Docker Compose gerenciam o processo, com ROCKET_ADDRESS=0.0.0.0 e ROCKET_PORT definidos no ambiente. O guia de deploy de Rust em VPS com Docker e systemd cobre o fluxo completo, e o de builds Docker otimizados mostra como encurtar o build multi-stage.
Boas Práticas
- Valide pela assinatura. Prefira parâmetros tipados (
id: u32, guards de query,FromForm) a validar strings dentro do handler. Se a validação cabe num tipo, o Rocket faz antes do handler rodar. - Um tipo por estado, um estado por aplicação. Estruture todo o estado compartilhado numa única struct de aplicação e registre-a uma vez com
.manage(). - Autenticação como guard, não como if. Transforme políticas de acesso em tipos que implementam
FromRequeste componha guards em vez de repetir checagens. - Erros como tipos. Modele erros com Thiserror e implemente
Responderno tipo de erro da aplicação — handlers devolvemResult<Resposta, ErroApp>semunwrap. - Teste com
Client::trackedeuri!. O custo de escrever testes de integração no Rocket é próximo de zero; use-o para travar contratos de rota. - Observe com fairing + Tracing. Um fairing de
on_request/on_responseabre spans por requisição e integra com o resto da stack de observabilidade. - Não reinvente módulos oficiais. Antes de escolher crates de comunidade para templates, cookies, DB pools e WebSockets, verifique a família
rocket_*— a integração com o ciclo de vida da aplicação já vem pronta.
Exemplos Práticos
API REST completa
#[macro_use] extern crate rocket;
use rocket::serde::{Serialize, json::Json};
use rocket::response::status;
use rocket::{State,serde::Deserialize};
use std::sync::Mutex;
#[derive(Serialize, Deserialize, Clone)]
#[serde(crate = "rocket::serde")]
struct Tarefa {
id: u32,
titulo: String,
feita: bool,
}
struct Banco {
tarefas: Mutex<Vec<Tarefa>>,
proximo_id: std::sync::atomic::AtomicU32,
}
#[get("/tarefas")]
fn listar(banco: &State<Banco>) -> Json<Vec<Tarefa>> {
let tarefas = banco.tarefas.lock().unwrap().clone();
Json(tarefas)
}
#[post("/tarefas", data = "<nova>")]
fn criar(
nova: Json<std::collections::HashMap<String, String>>,
banco: &State<Banco>,
) -> status::Created<Json<Tarefa>> {
use std::sync::atomic::Ordering;
let id = banco.proximo_id.fetch_add(1, Ordering::SeqCst) + 1;
let tarefa = Tarefa {
id,
titulo: nova.get("titulo").cloned().unwrap_or_default(),
feita: false,
};
banco.tarefas.lock().unwrap().push(tarefa.clone());
status::Created::new("/tarefas").body(Json(tarefa))
}
#[delete("/tarefas/<id>")]
fn remover(id: u32, banco: &State<Banco>) -> status::NoContent {
banco.tarefas.lock().unwrap().retain(|t| t.id != id);
status::NoContent
}
#[launch]
fn rocket() -> _ {
rocket::build()
.manage(Banco {
tarefas: Mutex::new(vec![
Tarefa { id: 1, titulo: "Estudar ownership".into(), feita: false },
]),
proximo_id: std::sync::atomic::AtomicU32::new(1),
})
.mount("/", routes![listar, criar, remover])
}
Comparação com Alternativas
| Característica | Rocket | Axum | Actix Web |
|---|---|---|---|
| Performance | Boa | Excelente | Excelente (top benchmarks) |
| Maturidade | Madura (desde 2016) | Jovem (desde 2021) | Muito madura (desde 2017) |
| API style | Macros de atributo, declarativa | Funções + composição | Macros + config |
| Middleware | Fairings | Tower (padrão do ecossistema) | Próprio (Transform) |
| Estado | Managed state (&State<T>) | State extractor | web::Data (Arc) |
| Validação de entrada | Tipos na assinatura | Extractors | Extractors |
| Formulários e sessões | Oficiais, integrados | Via crates | Via crates |
| Templates | Tera integrado (rocket_dyn_templates) | Tera, Askama | Tera, Askama |
| Testes | Framework integrado com cookies | Via tower::ServiceExt | Framework integrado |
| Ecossistema | Moderado | Grande e crescendo | Grande |
O Rocket se destaca por:
- Ergonomia máxima: menos código por rota do que qualquer concorrente direto
- Validação por tipos na assinatura: erros de entrada respondidos antes do handler
- Batteries included: formulários, cookies privados, templates e DB pools oficiais
- Framework de testes integrado com
Client::trackedpara sessões - Ótimo para aplicações full-stack monolíticas renderizando HTML no servidor
Quando preferir as alternativas: Axum quando o time já vive no mundo Tower/Tokio ou quer a maior comunidade; Actix Web quando o teto de throughput importa. Os detalhes estão no comparativo Axum vs Actix e na visão geral de Rust para web.
Conclusão
Rocket é o framework web em Rust que mais reduz o código entre a ideia e a primeira API funcionando: rotas declarativas, entrada validada por tipos, estado gerenciado, fairings explícitos e módulos oficiais para o resto. Para aplicações full-stack com HTML, formulários e sessões, é a stack mais completa em uma única família de crates.
Pontos-chave para lembrar:
#[get],#[post]e companhia declaram rota e handler juntos- Parâmetros tipados na assinatura validam path e query antes do handler
.manage()+&State<T>para pools, clientes e configuração- Request guards transformam autenticação em tipo, com 401 automático
- Fairings com
.attach()para middleware global de log, headers e métricas rocket_dyn_templateserocket_db_poolspara HTML e banco sem caça a cratesClient::tracked+uri!para testes de integração baratos
Para aprofundar, consulte a documentação oficial do Rocket e os exemplos no GitHub.
Para comparar antes de decidir, veja os guias de Axum, Actix Web e o comparativo entre os dois, ou siga o tutorial de API REST com Axum quando quiser a experiência no estilo Tower.
Perguntas frequentes sobre Rocket
O que é o Rocket?
É um framework web em Rust focado em ergonomia, com rotas via macros de atributo, validação de entrada por tipos, managed state, fairings, templates Tera e framework de testes integrado — assíncrono sobre o Tokio desde a versão 0.5.
Como instalar o Rocket?
cargo add rocket (com a feature json para APIs) via Cargo, mais #[macro_use] extern crate rocket; no main.rs para habilitar as macros de rota.
Rocket ou Axum: qual escolher em 2026?
Rocket quando você quer convenções prontas e uma stack completa para aplicações web tradicionais; Axum quando o valor está na integração com Tower e na maior comunidade do ecossistema. O comparativo entre Axum e Actix Web está em Axum vs Actix.
Como compartilhar estado entre handlers no Rocket?
Com managed state: .manage(valor) no builder e &State<T> nos handlers. O tipo precisa ser Send + Sync, o que pools do SQLx e Arc<Mutex<T>> já satisfazem.
Como autenticar rotas no Rocket?
Implemente FromRequest para um tipo de usuário autenticado (lendo header, cookie privado ou token JWT) e receba-o como argumento nos handlers protegidos — o guard retorna 401 antes de o handler rodar. Para o padrão com Axum, veja autenticação JWT com Axum.
Rocket aparece em vagas no Brasil?
Menos que Axum e Actix Web, mas aparece em serviços internos e startups. O grosso das vagas de Rust pede Axum, Actix Web, Tokio e SQLx; conhecer Rocket acelera protótipos e soma como diferencial. Veja a trilha de carreira backend e os salários de Rust no Brasil.