Rust program is not inserting inserting row into Postgres

Rust program is not inserting inserting row into Postgres

Am following a Rust tutorial online which consists of creating a minimal Rust HTTP server which uses a Postgres database in a Docker container. Am running this inside my macOS Sequoia via Docker Desktop.

rust-crud-api/cargo.toml:

[package]
name = "rust-crud-api"
version = "0.1.0"
edition = "2021"

[dependencies]
postgres = "0.19.10"
serde = "1.0.219"
serde_json = "1.0.140"
serde_derive = "1.0.219"

rust-crud-api/main.rs:

use postgres::Error as PostgresError;
use postgres::{Client, NoTls};
use std::env;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};

#[macro_use]
extern crate serde_derive;

#[derive(Serialize, Deserialize)]
struct User {
    id: Option<i32>,
    name: String,
    email: String,
}

const DB_URL: &str = env!("DATABASE_URL");

const OK_RESPONSE: &str = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n";
const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND\r\n\r\n";
const INTERNAL_SERVER_ERROR: &str = "HTTP/1.1 500 INTERNAL SERVER ERROR\r\n\r\n";

fn main() {
    if let Err(e) = set_database() {
        println!("Error: {}", e);
        return;
    }

    let listener = TcpListener::bind(format!("0.0.0.0:8080")).unwrap();
    println!("Server started at port 8080");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => {
                println!("Error: {}", e);
            }
        }
    }
}

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 1024];
    let mut request = String::new();

    match stream.read(&mut buffer) {
        Ok(size) => {
            request.push_str(String::from_utf8_lossy(&buffer[..size]).as_ref());

            let (status_line, content) = match &*request {
                r if r.starts_with("POST /users") => handle_post_request(r),
                r if r.starts_with("GET /users") => handle_get_all_request(r),
                _ => (NOT_FOUND.to_string(), "404 Not Found".to_string()),
            };

            stream
                .write_all(format!("{}{}", status_line, content).as_bytes())
                .unwrap();
        }
        Err(e) => {
            println!("Error: {}", e);
        }
    }
}

fn handle_post_request(request: &str) -> (String, String) {
    match (
        get_user_request_body(&request),
        Client::connect(DB_URL, NoTls),
    ) {
        (Ok(user), Ok(mut client)) => {
            client
                .execute(
                    "INSERT INTO users (name, email) VALUES ($1, $2)",
                    &[&user.name, &user.email],
                )
                .unwrap();

            (OK_RESPONSE.to_string(), "User created".to_string())
        }
        _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()),
    }
}

fn handle_get_all_request(request: &str) -> (String, String) {
    match Client::connect(DB_URL, NoTls) {
        Ok(mut client) => {
            let mut users = Vec::new();

            for row in client.query("SELECT * FROM users", &[]).unwrap() {
                users.push(User {
                    id: row.get(0),
                    name: row.get(1),
                    email: row.get(2),
                });
            }

            (
                OK_RESPONSE.to_string(),
                serde_json::to_string(&users).unwrap(),
            )
        }
        _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()),
    }
}


fn get_id(request: &str) -> &str {
    request
        .split("/")
        .nth(2)
        .unwrap_or_default()
        .split_whitespace()
        .next()
        .unwrap_or_default()
}

fn get_user_request_body(request: &str) -> Result<User, serde_json::Error> {
    serde_json::from_str(request.split("\r\n\t\n").last().unwrap_or_default())
}

fn set_database() -> Result<(), PostgresError> {
    let mut client = Client::connect(DB_URL, NoTls)?;

    client.batch_execute(
        "CREATE TABLE IF NOT EXISTS users (
            id SERIAL PRIMARY KEY,
            name VARCHAR NOT NULL,
            email VARCHAR NOT NULL
     )",
    )?;
    Ok(())
}

rust-crud-api/Dockerfile:

FROM rust:1.86.0 as builder

WORKDIR /app

ARG DATABASE_URL 

ENV DATABASE_URL=$DATABASE_URL

COPY . .

RUN cargo build --release

FROM ubuntu:25.04

WORKDIR /usr/local/bin

COPY --from=builder /app/target/release/rust-crud-api .

CMD ["./rust-crud-api"]

rust-crud-api/docker-compose.yml:

services:
  rustapp:
    container_name: rustapp
    image: francescoxx/rustapp:1.0.0
    build:
      context: .
      dockerfile: Dockerfile
      args:
        DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
    ports:
      - '8080:8080'
    depends_on:
      - db
  db:
    container_name: db
    image: postgres:12
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - '5432:5432'
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata: {}

  1. Setup the database using docker compose:

docker compose up -d db

  1. Built and ran the Rust app:

docker compose up rustapp

When I go to Postman and hit the http://localhost:8080/users endpoint, I get the following:

HTTP 200 OK

Response body is an empty JSON array (since no users have been created/inserted):

[]

  1. When I try doing a HTTP POST against the http://localhost:8080/users endpoint with the following JSON request body/payload:
{
    "name": "joe",
    "email": "Read more"
}

Receive an HTTP 500 with Error for the Response body.

There are no errors written to stdout, however...

  1. When I go inside the Postgres instance the users table is created but empty:

docker exec -it db psql -U postgres

postgres=# \dt
         List of relations
 Schema | Name  | Type  |  Owner
--------+-------+-------+----------
 public | users | table | postgres
(1 row)

postgres=# SELECT * FROM users;
 id | name | email
----+------+-------
(0 rows)

What am I possibly doing wrong? Thanks in advance!

Answer

The problem was the \t escape sequence inside this function:

fn get_user_request_body(request: &str) -> Result<User, serde_json::Error> {
    serde_json::from_str(request.split("\r\n\t\n").last().unwrap_or_default())
}

Fix was replacing that \t with a \r:

serde_json::from_str(request.split("\r\n\r\n").last().unwrap_or_default())

Enjoyed this article?

Check out more content on our blog or follow us on social media.

Browse more articles