Cover Page
Backend Page
Rust with axum
Cargo.toml: add dependencies
Change to your chatterd
folder and edit the file Cargo.toml
to add the 3rd-party libraries we will be using.
server$ cd ~/reactive/chatterd
server$ vi Cargo.toml
In Cargo.toml
, add the following lines below the existing [dependencies]
tag:
axum-extra = { version = "0.10.1" }
bb8 = "0.9.0"
bb8-postgres = "0.9.0"
chrono = { version = "0.4.40", features = ["serde"] }
postgres = { version = "0.19.10", features = ["with-chrono-0_4", "with-uuid-1"] }
serde = { version = "1.0.219", features = ["derive"] }
tokio-postgres = "0.7.13"
uuid = { version = "1.16.0", features = ["v4", "macro-diagnostics", "serde"] }
chatterd package
Edit src/main.rs
:
server$ vi src/main.rs
and replace the routing
line in the use axum
block with:
routing::{ get, post },
and add the following use
lines along with the PGPPool
type alias:
use bb8::Pool;
use bb8_postgres::PostgresConnectionManager;
use tokio_postgres::NoTls;
type PGPool = Pool<PostgresConnectionManager<NoTls>>;
Add a pgppool
property to your AppState
struct definition after the line
client: Client
:
pgpool: PGPool,
In the main()
function, after the line, client: Client::new()
, add a pgpool
property to your AppState
instantiation. For the property, we set up a pool of open connections to our PostgreSQL chatterdb
database. Maintaining a pool of open connections avoids the cost of opening and closing a connection on every database operation. The password
used in instantiating PostgresConnectionManager
must match the one you used when setting up PostgreSQL earlier.
pgpool: Pool::builder()
.build(
PostgresConnectionManager::new_from_stringlike(
"host=localhost user=chatter password=chattchatt dbname=chatterdb",
NoTls,
)
.map_err(|err| {
eprintln!("{:?}", err);
process::exit(1)
})
.unwrap(),
)
.await
.map_err(|err| {
eprintln!("{:?}", err);
process::exit(1)
})
.unwrap(),
Continuing inside the main()
function, find the router = Router::new()
instantiation statement and add two routes right
after the route for /llmprompt
. These serve Chatter
’s two APIs: HTTP GET request, with URL endpoint getchatts
, and HTTP POST request, with URL endpoint postchatt
. With each endpoint, we also specify the MethodRouter
and handler function assigned to it:
.route("/getchatts", get(handlers::getchatts))
.route("/postchatt", post(handlers::postchatt))
The functions getchatts()
and postchatt()
will be implemented in handlers.rs
.
We’re done with main.rs
. Save and exit the file.
handlers module
Edit src/handlers.rs
:
server$ vi src/handlers.rs
First modify the use
imports at the top of the file:
-
replace existing
use serde_json
line with this:use serde_json::{ json, Value };
- and add these new
use
lines:use chrono::{ DateTime, Local }; use serde::{ Deserialize, Serialize }; use uuid::Uuid;
We define a Chatt
struct to help postchatt()
deserialize JSON
received from clients. Add these lines right below the above:
#[derive(Debug, Serialize, Deserialize)]
pub struct Chatt {
username: String,
message: String,
id: Option<String>,
timestamp: Option<DateTime<Local>>,
}
To the existing logging functions, add the following:
fn logClientErr(clientIP: SocketAddr, errcode: StatusCode, errmsg: String) -> (StatusCode, String) {
tracing::info!("{:?} | {:?} |", errcode, clientIP);
(errcode, errmsg)
}
The handler getchatts()
uses an open connection from the connection pool passed
in from main()
, as part of AppState
, to query the database for stored chatts.
Once all the rows from the database are retrieved, we insert the resulting array of chatts
into the response JSON object.
Add getchatts()
to your handlers.rs
after the definition of the llmprompt()
function.
pub async fn getchatts(
State(appState): State<AppState>,
ConnectInfo(clientIP): ConnectInfo<SocketAddr>,
) -> Result<Json<Value>, (StatusCode, String)> {
let chatterDB = appState
.pgpool
.get()
.await
.map_err(|err| logServerErr(clientIP, err.to_string()))?;
let mut chattArr: Vec<Vec<Option<String>>> = Vec::new();
for row in chatterDB
.query(
"SELECT username, message, id, time FROM chatts",
&[],
)
.await
.map_err(|err| logServerErr(clientIP, err.to_string()))?
{
chattArr.push(vec![
row.get(0),
row.get(1),
Some(row.get::<usize, Uuid>(2).to_string()),
Some(row.get::<usize, DateTime<Local>>(3).to_string()),
]);
}
logOk(clientIP);
Ok(Json(json!(chattArr)))
}
Similarly, postchatt()
receives a posted chatt
in the expected JSON format,
deserializes it into the Chatt
struct, and inserts it into the database, using
a connection from the pool. The UUID and time stamp of each chatt are generated
at insertion time. Add postchatt()
to the end of your handlers.rs
:
pub async fn postchatt(
State(appState): State<AppState>,
ConnectInfo(clientIP): ConnectInfo<SocketAddr>,
Json(chatt): Json<Chatt>,
) -> Result<Json<Value>, (StatusCode, String)> {
let chatterDB = appState
.pgpool
.get()
.await
.map_err(|err| logServerErr(clientIP, err.to_string()))?;
chatterDB
.execute(
"INSERT INTO chatts (username, message, id) VALUES ($1, $2, gen_random_uuid())",
&[&chatt.username, &chatt.message],
)
.await
.map_err(|err| logClientErr(clientIP, StatusCode::NOT_ACCEPTABLE, err.to_string()))?;
logOk(clientIP);
Ok(Json(json!({})))
}
We’re done with handlers.rs
. Save and exit the file.
Build and test run
To build your server:
server$ cargo build --release
As before, it will take some time to download and build all the 3rd-party crates. Be patient.
Linking error with cargo build?
When running cargo build --release
, if you see:
error: linking with cc failed: exit status: 1
note: collect2: fatal error: ld terminated with signal 9 [Killed]
below a long list of object files, try running cargo build --release
again. It usually works the second time around, when it will have less remaining linking to do. If the error persisted, please talk to the teaching staff.
Rust
is a compiled language, like C/C++ and unlike Python, which is an interpreted language. This means you must run cargo build
each and every time you made changes to your code, for the changes to show up in your executable.
To run your server:
server$ sudo ./chatterd
# Hit ^C to end the test
You can test your implementation following the instructions in the Testing Chatter
APIs section.
References
Prepared by Sugih Jamin | Last updated July 23rd, 2025 |