¡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 #
- Añadir persistencia con una base de datos PostgreSQL.
- Integrar SQLx para manejar consultas de forma eficiente y segura.
- 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!