So I'm trying to create a basic actix-web application that will allow me to create a very basic blog system. It is handling my GET requests, but it's not handling my POST requests.
main.rs:
use actix_web::{HttpServer, App, web};
use sqlx::postgres::PgPool;
use dotenv::dotenv;
mod posts;
mod database;
#[actix_web::main]
pub async fn main() -> std::io::Result<()>{
dotenv().ok();
let pool = PgPool::connect(&dotenv::var("DATABASE_URL").unwrap()).await.unwrap();
HttpServer::new(move || {
// The scope for all post services
let posts = web::scope("/posts").service(posts::get_all_posts).service(posts::create_post);
App::new()
.data(pool.clone())
.service(web::scope("/api").service(posts))
})
.bind(("127.0.0.1", 8080))?
.run()
.await
}
posts/routes.rs:
use super::*;
use database::{NewPost, model::Post};
use actix_web::{get, post, Responder, HttpResponse};
#[get("/")]
pub async fn get_all_posts(pool: web::Data<PgPool>) -> impl Responder {
println!("New GET request for all posts!");
let result = Post::find_all(pool.get_ref()).await;
match result {
Ok(posts) => HttpResponse::Ok().json(posts),
_ => HttpResponse::BadRequest().body("Error trying to read all the posts from the database")
}
}
#[post("/new")]
pub async fn create_post(post: web::Json<NewPost>, pool: web::Data<PgPool>) -> impl Responder {
println!("New POST request to create a post!");
let result = Post::new_post(post.into_inner(), pool.as_ref()).await;
match result {
Ok(post) => HttpResponse::Ok().json(post),
_ => HttpResponse::BadRequest().body("Error trying to create a new post")
}
}
database/model.rs:
use serde::{Serialize, Deserialize};
use sqlx::{FromRow, PgPool, Row, postgres::PgRow};
use uuid::Uuid;
use chrono::prelude::*;
/// Struct to represent database record.
#[derive(Serialize, FromRow, Debug)]
pub struct Post {
pub id: Uuid,
pub content: String,
pub created_at: chrono::NaiveDateTime
}
/// Struct to receive user input.
#[derive(Serialize, Deserialize)]
pub struct NewPost {
pub content: String
}
impl Post {
pub async fn find_all(pool: &PgPool) -> std::io::Result<Vec<Post>> {
let mut posts = Vec::new();
let recs = sqlx::query_as!(Post, r#"SELECT id, content, created_at FROM post ORDER BY id"#)
.fetch_all(pool)
.await
.unwrap();
for rec in recs {
posts.push(Post {
id: rec.id,
content: rec.content,
created_at: rec.created_at
});
}
Ok(posts)
}
pub async fn new_post(post: NewPost, pool: &PgPool) -> std::io::Result<Post> {
let mut tx = pool.begin().await.unwrap();
let post = sqlx::query("INSERT INTO post (id, content, created_at) VALUES ($1, $2, $3) RETURNING id, content, created_at")
.bind(Uuid::new_v4())
.bind(post.content)
.bind(Utc::now())
.map(|row: PgRow| {
Post {
id: row.get(0),
content: row.get(1),
created_at: row.get(2)
}
})
.fetch_one(&mut tx)
.await
.unwrap();
tx.commit().await.unwrap();
Ok(post)
}
}
Currently, I am using curl to test out the API. When I want to send a GET request I use the command:
curl localhost:8080/api/posts/
When I run this command my rust application prints out the statement: "New GET request for all posts!" as I expected it to.
When I want to send a POST request I use the command:
curl -H "Content-Type: application/json" -X POST -d '{ \
"content": "This is my first post" \
}' localhost:8080/api/posts/new
After running this curl command I don't get any output at all, there is nothing from the curl terminal nor any output from my rust program.
Just reading over everything, your curl might actually be the culprit.
If you include -i
to show the response headers, then you can see you're actually receiving a 400 Bad Request.
$ curl -i -H "Content-Type: application/json" -X POST -d '{ \
> "content": "This is my first post" \
> }' localhost:8080/api/posts/new
HTTP/1.1 400 Bad Request
content-length: 0
date: Sun, 03 Jan 2021 14:04:12 GMT
If you change create_post
to take post: String
instead of post: web::Json<NewPost>
, then it makes it easier to inspect the payload the endpoint is receiving.
pub async fn create_post(post: String) -> impl Responder {
println!("{}", post);
Now performing the same curl
reveals the following output:
{ \
"content": "This is my first post" \
}
In short, the issue is that the backslashes remain in the payload. So the only thing you need to do to get it working, is to remove the backslashes.
curl -H "Content-Type: application/json" -X POST -d '{
"content": "This is my first post"
}' localhost:8080/api/posts/new