Si les interesa organizar de mejor forma su código les comparto un proyecto base para agregar endpoints en base a scopes y tener separados los handlers de los endpoints.
Pueden verlo como una base para arquitectura hexagonal.
Crear un endpoint POST en Rust te permite recibir datos en formato JSON desde el navegador o un cliente como Postman y guardarlos en la base de datos. Aquí aprendes a configurar Serde, definir un handler, encapsular la lógica en una función reutilizable y probar todo con Postman para popular blog posts dinámicamente.
¿Cómo instalar Postman para probar tu API en Rust?
Postman es la herramienta que vas a usar para enviar peticiones HTTP a tu servidor sin necesidad de un frontend. Descárgalo desde su página oficial eligiendo tu sistema operativo. En Mac, basta con descargar el .zip, descomprimirlo y darle doble clic al archivo para instalar.
Una vez abierto, creas una nueva request con el botón de más, eliges el método (GET, POST, etc.) y apuntas a tu servidor local en 127.0.0.1:8000. Si tu servidor está corriendo, te responderá con los datos esperados [00:55].
¿Qué es Postman? Es un cliente HTTP que te permite enviar peticiones GET, POST, PUT y DELETE a tu API para probar endpoints sin necesidad de construir un frontend.
¿Qué dependencias necesitas en Cargo para manejar JSON en Rust?
Para que Rust pueda serializar y deserializar JSON, necesitas tres crates clave en tu archivo Cargo.toml:
serde con la feature derive para usar los macros automáticamente.serde_json para manejar la conversión entre objetos y JSON.actix-web ya lo tienes configurado como servidor.Estos serializadores convierten tus structs en JSON y viceversa, lo que resulta útil cuando recibes datos desde el cliente [02:30].
¿Cómo preparar los modelos para JSON?
En tu archivo de modelos importas los macros con use serde::{Deserialize, Serialize}; y los agregas como derive junto a los que ya tenías como Queryable y Debug. Con esto, tus structs Post y NewPost se transforman en JSON sin esfuerzo.
¿Cómo defines un endpoint POST con Actix Web?
La estructura es muy parecida a un GET, pero usando el macro #[post("/new_post")] y una función async fn new_post. Adentro reutilizas la conexión a la base de datos del pool y el match que maneja errores con web::block.
La diferencia está en la query: en lugar de leer registros, usas diesel::insert_into(posts::table).values(&new_post).get_result::<Post>(conn) para insertar y devolver el registro creado [05:40].
No olvides registrar el servicio con .service(new_post) en tu App::new(), porque sin eso recibirás un error 404 al hacer la petición.
¿Cómo recibir datos JSON en el endpoint?
Necesitas un struct que actúe como serializador de entrada. En el módulo de modelos creas:
rust #[derive(Serialize, Deserialize, Debug, Clone)] pub struct NewPostHandler { pub title: String, pub body: String, }
Luego, en la firma del endpoint, agregas el parámetro item: web::Json<NewPostHandler> para que Actix sepa que debe esperar un JSON con esa forma. Accedes a los campos con item.title.clone() e item.body.clone() [09:15].
¿Qué hace
web::Jsonen Actix? Es un extractor que convierte automáticamente el cuerpo JSON de la petición en una struct de Rust deserializable.
¿Cómo probar el POST desde Postman?
En Postman seleccionas el método POST, apuntas a 127.0.0.1:8000/new_post, vas a la pestaña Body, eliges raw y JSON, y escribes:
{ "title": "este es un blog en JSON", "body": "y tiene un body" }
Al darle send, recibes el post creado con su id autogenerado.
¿Cómo encapsular la lógica de inserción en una función reutilizable?
Meter toda la lógica dentro del endpoint genera problemas de lifetimes y reduce la legibilidad. La solución es implementar una función asociada al modelo Post:
rust impl Post { pub fn create_post<'a>( conn: &mut PgConnection, post: &NewPostHandler, ) -> Result<Post, diesel::result::Error> { let slug = Post::slugify(post.title.clone()); let new_post = NewPost { title: &post.title, slug: &slug, body: &post.body, }; diesel::insert_into(posts::table) .values(&new_post) .get_result::<Post>(conn) } }
¿Cómo generar slugs automáticamente desde el título?
Un slug es la versión amigable de la URL. Como NewPostHandler no incluye slug, lo generas con una función auxiliar:
rust fn slugify(title: String) -> String { title.replace(" ", "-").to_lowercase() }
Esta función reemplaza espacios por guiones y convierte todo a minúsculas, dejando un slug limpio listo para usar en la URL [13:20].
¿Qué errores comunes vas a encontrar y cómo los resuelves?
Al compilar con cargo run aparecen errores típicos que conviene reconocer:
NewPost o NewPostHandler en el archivo del endpoint.Post con NewPost al llamar a insert_into.get_result, que se resuelve con get_result::<Post>(conn).use diesel::prelude::*;.Una vez resueltos, el servidor compila y al lanzar peticiones desde Postman ves cómo los registros aparecen en la lista al hacer GET. Si insertas un post con id 8, el siguiente GET ya lo muestra junto a los anteriores.
¿Te animas a extender este endpoint para aceptar más campos o validar el JSON antes de guardar? Cuéntame en los comentarios cómo lo implementarías.
Crear un endpoint POST en Rust te permite recibir datos en formato JSON desde el navegador o un cliente como Postman y guardarlos en la base de datos. Aquí aprendes a configurar Serde, definir un handler, encapsular la lógica en una función reutilizable y probar todo con Postman para popular blog posts dinámicamente.
Postman es la herramienta que vas a usar para enviar peticiones HTTP a tu servidor sin necesidad de un frontend. Descárgalo desde su página oficial eligiendo tu sistema operativo. En Mac, basta con descargar el .zip, descomprimirlo y darle doble clic al archivo para instalar.
Una vez abierto, creas una nueva request con el botón de más, eliges el método (GET, POST, etc.) y apuntas a tu servidor local en 127.0.0.1:8000. Si tu servidor está corriendo, te responderá con los datos esperados [00:55].
¿Qué es Postman? Es un cliente HTTP que te permite enviar peticiones GET, POST, PUT y DELETE a tu API para probar endpoints sin necesidad de construir un frontend.
Para que Rust pueda serializar y deserializar JSON, necesitas tres crates clave en tu archivo Cargo.toml:
serde con la feature derive para usar los macros automáticamente.serde_json para manejar la conversión entre objetos y JSON.actix-web ya lo tienes configurado como servidor.Estos serializadores convierten tus structs en JSON y viceversa, lo que resulta útil cuando recibes datos desde el cliente [02:30].
En tu archivo de modelos importas los macros con use serde::{Deserialize, Serialize}; y los agregas como derive junto a los que ya tenías como Queryable y Debug. Con esto, tus structs Post y NewPost se transforman en JSON sin esfuerzo.
La estructura es muy parecida a un GET, pero usando el macro #[post("/new_post")] y una función async fn new_post. Adentro reutilizas la conexión a la base de datos del pool y el match que maneja errores con web::block.
La diferencia está en la query: en lugar de leer registros, usas diesel::insert_into(posts::table).values(&new_post).get_result::<Post>(conn) para insertar y devolver el registro creado [05:40].
No olvides registrar el servicio con .service(new_post) en tu App::new(), porque sin eso recibirás un error 404 al hacer la petición.
Necesitas un struct que actúe como serializador de entrada. En el módulo de modelos creas:
rust #[derive(Serialize, Deserialize, Debug, Clone)] pub struct NewPostHandler { pub title: String, pub body: String, }
Luego, en la firma del endpoint, agregas el parámetro item: web::Json<NewPostHandler> para que Actix sepa que debe esperar un JSON con esa forma. Accedes a los campos con item.title.clone() e item.body.clone() [09:15].
¿Qué hace
web::Jsonen Actix? Es un extractor que convierte automáticamente el cuerpo JSON de la petición en una struct de Rust deserializable.
En Postman seleccionas el método POST, apuntas a 127.0.0.1:8000/new_post, vas a la pestaña Body, eliges raw y JSON, y escribes:
{ "title": "este es un blog en JSON", "body": "y tiene un body" }
Al darle send, recibes el post creado con su id autogenerado.
Meter toda la lógica dentro del endpoint genera problemas de lifetimes y reduce la legibilidad. La solución es implementar una función asociada al modelo Post:
rust impl Post { pub fn create_post<'a>( conn: &mut PgConnection, post: &NewPostHandler, ) -> Result<Post, diesel::result::Error> { let slug = Post::slugify(post.title.clone()); let new_post = NewPost { title: &post.title, slug: &slug, body: &post.body, }; diesel::insert_into(posts::table) .values(&new_post) .get_result::<Post>(conn) } }
Un slug es la versión amigable de la URL. Como NewPostHandler no incluye slug, lo generas con una función auxiliar:
rust fn slugify(title: String) -> String { title.replace(" ", "-").to_lowercase() }
Esta función reemplaza espacios por guiones y convierte todo a minúsculas, dejando un slug limpio listo para usar en la URL [13:20].
Al compilar con cargo run aparecen errores típicos que conviene reconocer:
NewPost o NewPostHandler en el archivo del endpoint.Post con NewPost al llamar a insert_into.get_result, que se resuelve con get_result::<Post>(conn).use diesel::prelude::*;.Una vez resueltos, el servidor compila y al lanzar peticiones desde Postman ves cómo los registros aparecen en la lista al hacer GET. Si insertas un post con id 8, el siguiente GET ya lo muestra junto a los anteriores.
¿Te animas a extender este endpoint para aceptar más campos o validar el JSON antes de guardar? Cuéntame en los comentarios cómo lo implementarías.
Si les interesa organizar de mejor forma su código les comparto un proyecto base para agregar endpoints en base a scopes y tener separados los handlers de los endpoints.
Pueden verlo como una base para arquitectura hexagonal.
Desperdicié buena parte de mi vida haciendo un tutorial muy cool sobre un error que obtuve, y cómo mi proceso dialéctico me llevó a encontrar por accidente otros errores, y cómo resolverlos me hizo resolver el primero y hacer que funcione. Pero esto me dijo que mi comentario contiene enlaces a sitios no seguros. Así que ahora solo haré esto: En caso de que tengan un error, que no puedan solucionar, quizá sea el mismo que tuve, y lo resolví haciendo que el código se vea así:
La función new post del archivo main

La función create post, del archivo models:

Igual ni quería hacer un tutorial.
Well, creo que eran ahora puedo
Primero obtuve este error:
.
A lo que entiendo, quiere que el &mut conn deje de ser mutable. Y al hacerlo pasa esto:
.
So: Who is right?
Solo por probar borre el & del PgConnection, para dejarlo así:
mut conn: PgConnection,
Ahora uno de los errores (porque salieron más) fue este:

Y algún proceso dialéctico de mi cerebro que no entiendo me hizo ir a buscar a la función new_post() de nuestro archivo main.
Y una vez ahí, el error fue este:

Así que borré el mut para dejarlo de la siguiente forma:
async fn new_post(pool: web::Data<DbPool>, item: web::Json<NewPostHandler>) -> impl Responder{
let conn= pool.get().expect("Problemas al traer la base de datos");
println!("{:?}", item);
match web::block(move || {Post::create_post(conn, &item)}).await{
Ok(data) => {
HttpResponse::Ok().body(format!("{:?}", data))
},
Err(err) => HttpResponse::Ok().body(format!("Error al recibir la data: {}", err))
}
}
Ahora el error es ese:

That means que si copiamos el el:
PooledConnection<ConnectionManager<PgConnection>>
Y lo pegamos en el archivo models para dejarlo de esta forma:
pub fn create_post<'a>(
mut conn: PooledConnection<ConnectionManager<PgConnection>>,
post: &NewPostHandler) -> Result<Post, diesel::result::Error> {
let slug = Post::slugify(&post.title.clone());
let new_post = NewPost{
title: &post.title,
slug: &slug,
body: &post.body
};
diesel::insert_into(posts::table).values(new_post).get_result::<Post>(&mut conn)
}
Nos pedirá importar algunas cosas. Solo hay que decirle que sí, y creo que queda así:
use diesel::{PgConnection, r2d2::{ConnectionManager, PooledConnection}};
Y en teoría, ahora funciona!
A lo largo de este curso me he enfocado en actualizar el código a Diesel 2.0, ya que diesel tuvo una actualización y cambiaron algunas cosas.
Ademas recomiendo hacer esta lectura sobre referencias y la adopcion el cual explica el símbolo &
Aclaro, no soy experto, apenas es mi primer semana con este lenguaje pero comparto mi código:
// Archivo main.rs
extern crate diesel;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};
use dotenvy::dotenv;
use std::env;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::r2d2::Pool;
use diesel::r2d2::{self, ConnectionManager};
pub mod models;
pub mod schema;
pub type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
use self::models::{NewPostHandler, Post};
use self::schema::posts::dsl::*;
#[get("/")]
async fn index(pool: web::Data<DbPool>) -> impl Responder {
let mut conn = pool.get().expect("It doesn't connect to SQL");
return match web::block(move || posts.load::<Post>(&mut conn)).await {
Ok(data) => HttpResponse::Ok().body(format!("{:?}", data.ok())),
Err(err) => HttpResponse::Ok().body("hubo un error"),
};
}
#[post["/new_post"]]
async fn insert(pool: web::Data<DbPool>, item: web::Json<NewPostHandler>) -> impl Responder {
let mut conn = pool.get().expect("It doesn't connect to SQL"); // ahora requiere ser una variable mutable, por lo cual a partir de aqui esta el primer cambio.
let new_post = NewPostHandler {
title: item.title.clone(),
body: item.body.clone(),
};
return match web::block(move || Post::create_post(&mut conn, &new_post)).await { // como se puede observar se pasa la variable como mutable a la funcion de create post
Ok(data) => HttpResponse::Ok().body(format!("{:?}", data.ok())),
Err(err) => HttpResponse::Ok().body("hubo un error"),
};
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DB url variable doesn't found");
let manager = ConnectionManager::<PgConnection>::new(database_url);
let pool = Pool::builder()
.build(manager)
.expect("No se pudo construir la pool");
HttpServer::new(move || {
App::new()
.service(index)
.service(insert) // agregamos el servicio de insert
.app_data(web::Data::new(pool.clone()))
})
.bind(("localhost", 8080))?
.run()
.await
}
Posterior a esto nos vamos al archivo models
use super::schema::posts;
use diesel::prelude::*;
use diesel::{Insertable, PgConnection, Queryable};
use serde::{Deserialize, Serialize};
#[derive(Queryable, Debug, Deserialize, Serialize)]
pub struct PostSimplified {
pub title: String,
pub slug: String,
}
#[derive(Queryable, Debug)]
pub struct Post {
pub id: i32,
pub title: String,
pub slug: String,
pub body: String,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct NewPostHandler {
pub title: String,
pub body: String,
}
#[derive(Insertable)]
#[diesel(table_name = posts)]
pub struct NewPost<'a> {
pub title: &'a str,
pub body: &'a str,
pub slug: &'a str,
}
impl Post {
pub fn slugify(title: &String) -> String {
return title.replace(" ", "-").to_lowercase();
}
pub fn create_post<'a>(
conn: &mut PgConnection, // aqui se utiliza conn coimo tipo mutable, debe de recibir este tipo para que la funcion get_result funcione
post: &NewPostHandler,
) -> Result<Post, diesel::result::Error> {
let slug = Post::slugify(&post.title.clone());
let new_post = NewPost {
title: &post.title,
slug: &slug,
body: &post.body,
};
diesel::insert_into(posts::table)
.values(new_post)
.get_result::<Post>(conn) //esta ya es mutable asi que no requiere volver a colocarle &mut
}
}
Luego tengo mi archivo cargo.toml
[package]
name = "blog-platzi"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = { version = "2.0.0", features = ["postgres", "r2d2"] }
dotenvy = "0.15"
actix-web = "4"
serde = {version = "1.0.145", features = ["derive"]}
serde_json = "1.0.1"
json = "0.12.4"
¿Cómo se instalan las dependencias de Rust sin internet? Porque Cargo las gestiona óptimamente pero, ¿Qué pasa si no hay internet para descargarlas? ¿Se pueden compilar los binarios manualmente?
Conforme hemos ido avanzado en el curso pudimos topar con diferentes errores en el codigo, hay 2 opciones:
1. Utilizar las mismas versiones que utilizar el profe en el curso de RUST y demas dependencias
2. Que para mi es la mas importantes y divertida pero larga que es trabajar con las ultimas versiones y ir investigando por que se estan dando los errores
Comparto un link al github que cree para este curso:
Fecha: 28 de Marzon 2024
El video se congela en el minuto 17:41