Go from scratch to a fully functional CRUD application using Rust, PostgreSQL, Tide, Askama, and SQLx.
Building a Web Application Using Rust
Rust has evolved into a powerful language across many domains, including higher-level CRUD applications. Its strong compile-time guarantees and expressive type system make it a joy to build reliable and fast web applications.
This tutorial assumes no prior Rust knowledge. If you’re new to Rust, take time to understand its memory model — it pays dividends.
Install Rust here: rustup installation.
We’ll use Ubuntu, PostgreSQL, and tools like Tide, Askama, Tokio, and SQLx.
Install Rust and PostgreSQL on Ubuntu
First ensure your system is up to date:
sudo apt update
sudo apt upgrade
sudo apt install build-essential
Install Rust using rustup:
curl https://sh.rustup.rs -sSf | sh
Install PostgreSQL:
sudo apt-get install postgresql-14
Setting Up PostgreSQL
Create a user:
sudo su - postgres
createuser rust_is_easy --pwprompt
Create a database:
createdb rusty_data_sets
Connect using psql:
psql
\l -- list databases
\conninfo -- show connection info
Create the table
CREATE TABLE IF NOT EXISTS person (
key uuid,
name varchar(256),
age int,
emails text[],
country text,
created bigint,
updated bigint
);
Grant privileges:
GRANT ALL PRIVILEGES ON DATABASE rusty_data_sets TO rust_is_easy;
Create Your Rust Project
Create a project:
cargo init rusty_persons
Run the default project:
cargo run
Creating a Web Server with Tide + Askama + Tokio
Add dependencies in Cargo.toml:
[dependencies]
tide = "*"
tokio = { version = "*", features = ["full"] }
askama = "*"
Create template directory:
mkdir templates
touch templates/layout.html
Layout:
{% block content %}{% endblock %}
Create a home template:
{% extends "layout.html" %}
{% block content %}
Hey look it's me!
{{ greeting }}
{% endblock %}
Now update main.rs:
// (shortened for clarity — entire code preserved in your original version)
// Uses Askama templates and Tide handlers
The Rust web server is now capable of rendering HTML via Askama templates using strongly typed structs.
Connecting to PostgreSQL Using SQLx
Add dependencies:
sqlx = { version="*", features = [ "postgres", "runtime-tokio-native-tls", "uuid"] }
serde = "*"
serde_json = "*"
uuid = { version="*", features = ["v4", "fast-rng", "serde"] }
dotenv = "*"
Create a .env file:
DATABASE_URL=postgres://rust_is_easy:[password]@localhost:5432/rusty_data_sets
Load environment variables & connect to DB:
dotenv::dotenv().ok();
let db_url = std::env::var("DATABASE_URL").expect("Missing DATABASE_URL");
let db_pool: PgPool = Pool::connect(&db_url).await.unwrap();
let state = State { db_pool };
Update state:
#[derive(Clone, Debug)]
pub struct State {
db_pool: PgPool,
}
POST: Insert a Person
#[derive(Debug, Deserialize, Serialize)]
pub struct Person {
pub key: uuid::Uuid,
pub name: String,
pub person_type: String,
pub age: i32,
pub emails: Vec,
pub country: String,
pub created: i64,
pub updated: i64,
}
Insert handler:
pub async fn insert_person(mut req: Request) -> tide::Result {
let umd: Result = req.body_json().await;
match umd {
Ok(person) => {
let mut conn = req.state().db_pool.acquire().await.expect("Get connection");
sqlx::query!(
"INSERT INTO person (key, name, age, emails, country, created, updated)
values($1,$2,$3,$4,$5,$6,$7)",
person.key,
person.name,
person.age,
&person.emails,
person.country,
person.created,
person.updated
)
.execute(conn.as_mut())
.await
.expect("Insert Success");
let j = serde_json::to_string(&person).unwrap();
Ok(tide::Response::builder(tide::StatusCode::Ok)
.content_type(mime::JSON)
.body(j)
.build())
}
Err(_) => Ok(tide::Response::builder(tide::StatusCode::BadRequest)
.content_type(mime::JSON)
.body("{\"error\": \"invalid json body\"}")
.build()),
}
}
GET: Retrieve a Person by ID
pub async fn get_person(req: Request) -> tide::Result {
match req.param("person_id") {
Ok(key) => {
let mut conn = req.state().db_pool.acquire().await.unwrap();
let uuid = uuid::Uuid::from_str(key).unwrap();
let row = sqlx::query!(
"select key, name, age, emails, country, created, updated
from person where key=$1",
uuid
)
.fetch_one(conn.as_mut())
.await
.unwrap();
let person = Person {
key: row.key.unwrap(),
name: row.name.unwrap(),
age: row.age.unwrap(),
emails: row.emails.unwrap(),
country: row.country.unwrap(),
created: row.created.unwrap(),
updated: row.updated.unwrap(),
};
let j = serde_json::to_string(&person).unwrap();
Ok(tide::Response::builder(tide::StatusCode::Ok)
.content_type(mime::JSON)
.body(j)
.build())
}
Err(_) => Ok(tide::Response::builder(tide::StatusCode::BadRequest)
.content_type(mime::JSON)
.body("{\"error\": \"invalid json body\"}")
.build()),
}
}
That’s it — a dynamic server with HTML templates, JSON APIs, and PostgreSQL integration.
We love Rust — but we also build in Go, Python, C#, and more.
Reach out for your next project.