Tutorial de Rocket
- published
- reading time
- 6 minutes
Tutorial de Rocket
Introducción
Instalación de dependencias
$ sudo dnf install -y libpq rust cargo make
$ cargo install diesel_cli diesel_cli_ext
Creación de proyecto
$ cargo new posadev
$ cd posadev
$ cargo run
Creación de Makefile
El make
nos sirve para llevar el control de los elementos que hemos compilado y
para tener un flujo de trabajo para la compilación de la aplicación.
# Posadev
run:
cargo run
clean:
cargo clean
Probamos ejecución
$ make clean
$ make run
Adición de dependencias
$ cargo add diesel rocket dotenvy serde uuid jsonwebtoken lazy_static
Agregamos las siguientes dependecias a Cargo.toml
[dependencies.rocket_dyn_templates]
version = "0.2.0"
features = ["handlebars"]
[dependencies.rocket_sync_db_pools]
version = "0.1.0"
features = ["diesel_postgres_pool", "postgres_pool"]
Creación de rutas
Editamos main.rs para probar una ruta:
- Agregamos dependencias de rocket
extern crate rocket;
use rocket::fs::FileServer;
use rocket::response::Debug;
use rocket::{launch, routes};
use rocket_dyn_templates::Template;
use rocket::get;
//https://rocket.rs/guide/v0.5/requests/
- Creamos primera ruta:
La macro #[launch]
nos genera el código necesario para la ejecución de la
aplicación.
Aquí vemos una de las características mas importantes de Rocket: la generación de código.
Las macros de Rust nos permiten ahorrarnos mucho código boilerplate y condensarlo
en “decoradores” que le agregan código a las funciones en base a su contenido.
La macro routes!
genera el códig necesario para la creación de rutas.
#[launch]
fn rocket() -> _ {
rocket::build()
.mount("/", routes![hello])
}
La función build()
nos crea el servidor y la función mount(...)
agrega rutas para
ser servidas hacia los clientes.
Agregamos función hello
En main.rs
/// Just says hi
#[get("/")]
pub async fn hello() -> String {
String::from("Hola posadev!")
}
Base de datos
Migraciones
Rust cuenta con una biblioteca (de ahora en adelante les diremos crates o huacales) llamada diesel que nos permite interactuar con bases de datos. Diesel funciona de manera similar a los ORMs de frameworks como: Django, Rails o flask.
En este caso usamos la aplicación de línea de comando: diesel
para insertar el
esquema SQL a la base de datos.
Esto puede parecer poco ortodoxo ya que tradicionalmente los ORMs generan el SQL en base
a las estructuras de datos de la aplicación, pero desde el punto de vista de bases de datos
es mejor escribir nosotros el SQL y usar las herramientas de Diesel para generar las
estructuras de datos de la aplicación.
Esto nos permite controlar la creación de tablas, índices, triggers y demás elementos
de la base datos desde la perspectiva de PostgreSQL para tenerlos mas optimizados.
Preparamos ambiente para uso de diesel_cli
En la raíz del proyecto, agregamos ~/.cargo/bin/
a nuestra variable de
ambiente PATH
para poder ejecutar las herramientas de CLI de
Diesel:
$ export PATH=$PATH:~/.cargo/bin
Creamos el directorio donde se van a almacenar nuestras migraciones
$ mkdir migrations
Creamos migraciones de nuestras tablas.
- Eventos
$ diesel migration create events
Creating migrations/2024-12-05-234041_events/up.sql
Creating migrations/2024-12-05-234041_events/down.sql
Este comando nos crea el directorio a la migración correspondiente y dos archivos:
up.sql
para crear nuestras tablas y demaś elementos y down.sql
que revierte lo que
creamos en up.sql
. En estos archivos se escribe el código SQL tal y como se le
escribiría al motor de base de datos.
Agregamos la definición de la tabla events
a nuestro archivo up.sql
:
-- Events
CREATE TABLE IF NOT EXISTS Event (
Id UUID,
Name TEXT,
Venue TEXT,
Address TEXT,
ContactName VARCHAR(255),
Starts_at TIMESTAMP NOT NULL DEFAULT NOW(),
Ends_at TIMESTAMP NOT NULL DEFAULT NOW(),
PRIMARY KEY(Id)
)
Base de datos
Para evitar configurar localmente nuestra base de datos podemos usar un contenedor de PostgreSQL, agregamos su ejecución a nuestro Makefile:
Agregamos los targets
.env:
echo "DATABASE_URL=postgres://postgres:prueba123@127.0.0.1:9432/postgres" > .env
./www/static:
mkdir -p www/static
./data:
mkdir data
dirs: www/static data .env
db: data
podman run -d --replace --name=posadev_pg \
-e POSTGRES_PASSWORD=prueba123 \
-v ./data:/var/lib/postgresql/data:U,Z \
-p 127.0.0.1:9432:5432 ghcr.io/enterprisedb/postgresql:16
Incluímos también los targets para crear los directorios de datos, estático y el archivo
.env
que contiene las credenciales del usuario que se conecta a la base de datos.
El Makefile
completo es de la siguiente manera:
# Posadev
.env:
echo "DATABASE_URL=postgres://postgres:prueba123@127.0.0.1:9432/postgres" > .env
./www/static:
mkdir -p www/static
./data:
mkdir data
dirs: www/static data .env
db: data
podman run -d --replace --name=posadev_pg \
-e POSTGRES_PASSWORD=prueba123 \
-v ./data:/var/lib/postgresql/data:U,Z \
-p 127.0.0.1:9432:5432 ghcr.io/enterprisedb/postgresql:16
run: dirs db
cargo run
clean:
cargo clean
- Levantamos el servidor de base de datos:
$ make db
- Nos conectamos para confirmar que funciona correctamente:
$ source .env
$ psql $DATABASE_URL
psql (16.3, servidor 16.4)
Digite «help» para obtener ayuda.
postgres=# \q
- Ejecutamos las migraciones:
$ diesel migration run
- Confirmamos que la migración se ejecutó correctamente:
$ source .env
$ psql $DATABASE_URL
postgres=# \d
Listado de relaciones
Esquema | Nombre | Tipo | Dueño
---------+----------------------------+-------+----------
public | __diesel_schema_migrations | tabla | postgres
public | event | tabla | postgres
(2 filas)
postgres=# \d event
Tabla «public.event»
Columna | Tipo | Ordenamiento | Nulable | Por omisión
-------------+-----------------------------+--------------+----------+-------------
id | uuid | | not null |
name | text | | |
venue | text | | |
address | text | | |
contactname | character varying(255) | | |
starts_at | timestamp without time zone | | not null | now()
ends_at | timestamp without time zone | | not null | now()
Índices:
"event_pkey" PRIMARY KEY, btree (id)
Diesel necesita un archvivo llamado schemas.rs
que contiene referencias para traducir de Rust a PostgreSQL. Lo generamos con el comando diesel print-schema
:
$ diesel print-schema > src/schema.rs
Finalmente generamos el archivo src/models.rs
que es el que tiene las estructuras que usamos
dentro de nuestros módulos:
diesel_ext -t -s src/schema.rs -m \
-I "crate::schema::*" \
-I "rocket::serde::{ Deserialize, Serialize }" \
-d "Clone, Debug, Identifiable, Queryable, QueryableByName, Insertable, Serialize, Deserialize" > src/models.rs
Para realizar todo este merequetengue creamos un archivo llamado generate_models.sh
en la raíz
del proyecto:
#!/bin/bash
DERIVE_FLAGS="Clone, Debug, Identifiable, Queryable, QueryableByName, Insertable, Serialize, Deserialize"
echo "Running diesel migrations"
diesel migration run
echo "Gettting schema from database"
diesel print-schema > src/schema.rs
echo "Generating models"
diesel_ext -t -s src/schema.rs -m \
-I "crate::schema::*" \
-I "rocket::serde::{ Deserialize, Serialize }" \
-d "${DERIVE_FLAGS}" > src/models.rs
Este script se debe ejecutar cada vez que ejecutamos migraciones.
Creamos el módulo src/db.rs
use rocket_sync_db_pools::database;
use rocket_sync_db_pools::diesel;
#[database("postgres")]
pub struct EventDB(diesel::PgConnection);
- Configuramos la base de datos en
Rocket.toml
# TEST VALUES, set the proper values here
[global.databases.postgres]
url = "postgres://postgres:prueba123@127.0.0.1:9432/postgres"
timeout = 5
pool_size = 2
Creamos los endpoints para agregar y consultar eventos.
Creación de eventos:
Request Guards
Se implementa el trait FromRequest
el cual es ejecutado por rocket
.
Si este trait está implementado para una argumento de una función, se
ejecuta la función from_request
.
Revisar claims.rs
//Rocket request guard
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Claims {
type Error = AuthenticationError;
async fn from_request(request: &'r rocket::Request<'_>) -> Outcome<Self, Self::Error> {
match request.headers().get_one(AUTHORIZATION) {
Some(v) => match Claims::from_authorization(v) {
Ok(c) => Outcome::Success(c),
Err(e) => Outcome::Error((Status::Forbidden, e)),
},
None => Outcome::Error((Status::Forbidden, AuthenticationError::Missing)),
}
}
}
Pruebas