Building a Web Application Using Rust

Go from scratch to a fully functional CRUD application using Rust, PostgreSQL, Tide, Askama, and SQLx.

Building a Web Application Using Rust

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.