45 - Construyendo una api rest en Rust - parte 2 persistencia con sqlx

45 - Construyendo una api rest en Rust - parte 2 persistencia con sqlx

¡Bienvenidos de nuevo a Rustaceo.es! En la primera parte de esta serie, construimos la estructura básica de nuestra API REST en Rust usando Actix-Web y almacenamos datos en memoria. Ahora, daremos el siguiente paso: integrar una base de datos con SQLx para manejar la persistencia de datos.


🚀 Objetivos de esta parte #

  1. Añadir persistencia con una base de datos PostgreSQL.
  2. Integrar SQLx para manejar consultas de forma eficiente y segura.
  3. Actualizar nuestras rutas para interactuar con la base de datos.

🏗 Configuración del proyecto con sqlx #

📌 1️⃣ Agregar dependencias #

Editamos Cargo.toml para incluir las dependencias necesarias:

[dependencies]
actix-web = "4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sqlx = { version = "0.6", features = ["postgres", "runtime-tokio-native-tls"] }
tokio = { version = "1", features = ["full"] }
dotenv = "0.15"

📌 2️⃣ Configurar postgresql #

Si no tienes PostgreSQL instalado, puedes instalarlo y crear una base de datos:

sudo apt install postgresql
sudo systemctl start postgresql
sudo -u postgres psql

Dentro de PostgreSQL, creamos una base de datos y usuario:

CREATE DATABASE pokedex;
CREATE USER rustaceo WITH ENCRYPTED PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE pokedex TO rustaceo;

Luego, configuramos nuestras variables de entorno en .env:

DATABASE_URL=postgres://rustaceo:password@localhost/pokedex

🌐 Conectando sqlx con actix-web #

📌 3️⃣ Configurar la conexión a la base de datos #

Modificamos main.rs para establecer la conexión con SQLx.

use actix_web::{web, App, HttpResponse, HttpServer, Responder};
use sqlx::PgPool;
use dotenv::dotenv;
use std::env;

async fn listar_elemento(pool: web::Data<PgPool>) -> impl Responder {
    let elementoes = sqlx::query!("SELECT id, nombre, tipo FROM elementoes")
        .fetch_all(pool.get_ref())
        .await;

    match elementoes {
        Ok(elementoes) => HttpResponse::Ok().json(elementoes),
        Err(_) => HttpResponse::InternalServerError().finish(),
    }
}

#[Actix_web::main]
async fn main() -> std::io::Result<()> {
    dotenv().ok();
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL no está configurado");
    let pool = PgPool::connect(&database_url).await.expect("No se pudo conectar a la BD");

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(pool.clone()))
            .route("/elementoes", web::get().to(listar_elemento))
    })
    .bind("127.0.0.1:8080")?
    .run()
    .await
}

PgPool::connect() establece la conexión a PostgreSQL.
sqlx::query!() ejecuta consultas SQL de manera segura.
dotenv() carga variables de entorno desde .env.


📥 Añadiendo pokémon a la base de datos #

📌 4️⃣ Crear una tabla para pokémon #

Ejecutamos la siguiente migración SQL para definir la estructura de nuestra tabla:

CREATE TABLE elementoes (
    id SERIAL PRIMARY KEY,
    nombre VARCHAR(50) NOT NULL,
    tipo VARCHAR(50) NOT NULL
);

📌 5️⃣ Agregar pokémon a la base de datos #

Añadimos una ruta para insertar nuevos Pokémon:

use serde::Deserialize;

#[Derive(deserialize)]
struct NuevoElemento {
    nombre: String,
    tipo: String,
}

async fn agregar_elemento(pool: web::Data<PgPool>, pokemon: web::Json<NuevoElemento>) -> impl Responder {
    let resultado = sqlx::query!(
        "INSERT INTO elementoes (nombre, tipo) VALUES ($1, $2)",
        pokemon.nombre, pokemon.tipo
    )
    .execute(pool.get_ref())
    .await;

    match resultado {
        Ok(_) => HttpResponse::Created().finish(),
        Err(_) => HttpResponse::InternalServerError().finish(),
    }
}

sqlx::query!() evita inyecciones SQL al usar parámetros seguros.
web::Json<NuevoElemento> recibe los datos del cuerpo de la solicitud.

Finalmente, agregamos la nueva ruta en main.rs:

.route("/elementoes", web::post().to(agregar_elemento))

📌 Conclusión #

Hemos integrado PostgreSQL y SQLx en nuestra API REST en Rust con Actix-Web.

🎯 Resumen de lo aprendido:
✔ Configuración de PostgreSQL y conexión con SQLx.
✔ Creación de tablas y migraciones.
✔ Manejo de datos con Actix-Web y SQLx.
✔ Rutas para listar y agregar Pokémon a la base de datos.

🔮 Próximo paso: En la tercera parte, implementaremos tests automatizados para validar nuestra API.

¡Nos vemos en la siguiente entrega, Rustaceos!