├── .dockerignore ├── .gitignore ├── Cargo.toml ├── Dockerfile ├── docker-compose.yml └── src └── main.rs /.dockerignore: -------------------------------------------------------------------------------- 1 | **/target -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust-crud-api" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | postgres = "0.19" 10 | serde = "1.0" 11 | serde_json = "1.0" 12 | serde_derive = "1.0" 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build stage 2 | FROM rust:1.69-buster as builder 3 | 4 | WORKDIR /app 5 | 6 | # accept the build argument 7 | ARG DATABASE_URL 8 | 9 | ENV DATABASE_URL=$DATABASE_URL 10 | 11 | COPY . . 12 | 13 | RUN cargo build --release 14 | 15 | # Production stage 16 | FROM debian:buster-slim 17 | 18 | WORKDIR /usr/local/bin 19 | 20 | COPY --from=builder /app/target/release/rust-crud-api . 21 | 22 | CMD ["./rust-crud-api"] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | 3 | services: 4 | rustapp: 5 | container_name: rustapp 6 | image: francescoxx/rustapp:1.0.0 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | args: 11 | DATABASE_URL: postgres://postgres:postgres@db:5432/postgres 12 | ports: 13 | - '8080:8080' 14 | depends_on: 15 | - db 16 | db: 17 | container_name: db 18 | image: postgres:12 19 | environment: 20 | POSTGRES_USER: postgres 21 | POSTGRES_PASSWORD: postgres 22 | POSTGRES_DB: postgres 23 | ports: 24 | - '5432:5432' 25 | volumes: 26 | - pgdata:/var/lib/postgresql/data 27 | 28 | volumes: 29 | pgdata: {} 30 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use postgres::{ Client, NoTls }; 2 | use postgres::Error as PostgresError; 3 | use std::net::{ TcpListener, TcpStream }; 4 | use std::io::{ Read, Write }; 5 | use std::env; 6 | 7 | #[macro_use] 8 | extern crate serde_derive; 9 | 10 | //Model: USer struct with id, name, email 11 | #[derive(Serialize, Deserialize)] 12 | struct User { 13 | id: Option, 14 | name: String, 15 | email: String, 16 | } 17 | 18 | //DATABASE_URL 19 | const DB_URL: &str = env!("DATABASE_URL"); 20 | 21 | //constants 22 | const OK_RESPONSE: &str = "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"; 23 | const NOT_FOUND: &str = "HTTP/1.1 404 NOT FOUND\r\n\r\n"; 24 | const INTERNAL_SERVER_ERROR: &str = "HTTP/1.1 500 INTERNAL SERVER ERROR\r\n\r\n"; 25 | 26 | //main function 27 | fn main() { 28 | //Set database 29 | if let Err(e) = set_database() { 30 | println!("Error: {}", e); 31 | return; 32 | } 33 | 34 | //start server and print port 35 | let listener = TcpListener::bind(format!("0.0.0.0:8080")).unwrap(); 36 | println!("Server started at port 8080"); 37 | 38 | //handle the client 39 | for stream in listener.incoming() { 40 | match stream { 41 | Ok(stream) => { 42 | handle_client(stream); 43 | } 44 | Err(e) => { 45 | println!("Error: {}", e); 46 | } 47 | } 48 | } 49 | } 50 | 51 | //handle_client function 52 | fn handle_client(mut stream: TcpStream) { 53 | let mut buffer = [0; 1024]; 54 | let mut request = String::new(); 55 | 56 | match stream.read(&mut buffer) { 57 | Ok(size) => { 58 | request.push_str(String::from_utf8_lossy(&buffer[..size]).as_ref()); 59 | 60 | let (status_line, content) = match &*request { 61 | r if r.starts_with("POST /users") => handle_post_request(r), 62 | r if r.starts_with("GET /users/") => handle_get_request(r), 63 | r if r.starts_with("GET /users") => handle_get_all_request(r), 64 | r if r.starts_with("PUT /users/") => handle_put_request(r), 65 | r if r.starts_with("DELETE /users/") => handle_delete_request(r), 66 | _ => (NOT_FOUND.to_string(), "404 Not Found".to_string()), 67 | }; 68 | 69 | stream.write_all(format!("{}{}", status_line, content).as_bytes()).unwrap(); 70 | } 71 | Err(e) => { 72 | println!("Error: {}", e); 73 | } 74 | } 75 | } 76 | 77 | //CONTROLLERS 78 | 79 | //handle_post_request function 80 | fn handle_post_request(request: &str) -> (String, String) { 81 | match (get_user_request_body(&request), Client::connect(DB_URL, NoTls)) { 82 | (Ok(user), Ok(mut client)) => { 83 | client 84 | .execute( 85 | "INSERT INTO users (name, email) VALUES ($1, $2)", 86 | &[&user.name, &user.email] 87 | ) 88 | .unwrap(); 89 | 90 | (OK_RESPONSE.to_string(), "User created".to_string()) 91 | } 92 | _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()), 93 | } 94 | } 95 | 96 | //handle_get_request function 97 | fn handle_get_request(request: &str) -> (String, String) { 98 | match (get_id(&request).parse::(), Client::connect(DB_URL, NoTls)) { 99 | (Ok(id), Ok(mut client)) => 100 | match client.query_one("SELECT * FROM users WHERE id = $1", &[&id]) { 101 | Ok(row) => { 102 | let user = User { 103 | id: row.get(0), 104 | name: row.get(1), 105 | email: row.get(2), 106 | }; 107 | 108 | (OK_RESPONSE.to_string(), serde_json::to_string(&user).unwrap()) 109 | } 110 | _ => (NOT_FOUND.to_string(), "User not found".to_string()), 111 | } 112 | 113 | _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()), 114 | } 115 | } 116 | 117 | //handle_get_all_request function 118 | fn handle_get_all_request(request: &str) -> (String, String) { 119 | match Client::connect(DB_URL, NoTls) { 120 | Ok(mut client) => { 121 | let mut users = Vec::new(); 122 | 123 | for row in client.query("SELECT * FROM users", &[]).unwrap() { 124 | users.push(User { 125 | id: row.get(0), 126 | name: row.get(1), 127 | email: row.get(2), 128 | }); 129 | } 130 | 131 | (OK_RESPONSE.to_string(), serde_json::to_string(&users).unwrap()) 132 | } 133 | _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()), 134 | } 135 | } 136 | 137 | //handle_put_request function 138 | fn handle_put_request(request: &str) -> (String, String) { 139 | match 140 | ( 141 | get_id(&request).parse::(), 142 | get_user_request_body(&request), 143 | Client::connect(DB_URL, NoTls), 144 | ) 145 | { 146 | (Ok(id), Ok(user), Ok(mut client)) => { 147 | client 148 | .execute( 149 | "UPDATE users SET name = $1, email = $2 WHERE id = $3", 150 | &[&user.name, &user.email, &id] 151 | ) 152 | .unwrap(); 153 | 154 | (OK_RESPONSE.to_string(), "User updated".to_string()) 155 | } 156 | _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()), 157 | } 158 | } 159 | 160 | //handle_delete_request function 161 | fn handle_delete_request(request: &str) -> (String, String) { 162 | match (get_id(&request).parse::(), Client::connect(DB_URL, NoTls)) { 163 | (Ok(id), Ok(mut client)) => { 164 | let rows_affected = client.execute("DELETE FROM users WHERE id = $1", &[&id]).unwrap(); 165 | 166 | if rows_affected == 0 { 167 | return (NOT_FOUND.to_string(), "User not found".to_string()); 168 | } 169 | 170 | (OK_RESPONSE.to_string(), "User deleted".to_string()) 171 | } 172 | _ => (INTERNAL_SERVER_ERROR.to_string(), "Error".to_string()), 173 | } 174 | } 175 | 176 | //set_database function 177 | fn set_database() -> Result<(), PostgresError> { 178 | //Connect to database 179 | let mut client = Client::connect(DB_URL, NoTls)?; 180 | 181 | //Create table 182 | client.batch_execute( 183 | "CREATE TABLE IF NOT EXISTS users ( 184 | id SERIAL PRIMARY KEY, 185 | name VARCHAR NOT NULL, 186 | email VARCHAR NOT NULL 187 | )" 188 | )?; 189 | Ok(()) 190 | } 191 | 192 | //get_id function 193 | fn get_id(request: &str) -> &str { 194 | request.split("/").nth(2).unwrap_or_default().split_whitespace().next().unwrap_or_default() 195 | } 196 | 197 | //deserialize user from request body with the id 198 | fn get_user_request_body(request: &str) -> Result { 199 | serde_json::from_str(request.split("\r\n\r\n").last().unwrap_or_default()) 200 | } --------------------------------------------------------------------------------