How to create a web app in Rust with Rocket and Diesel
Tags: rust, webdev
Published: 2023-03-09 14:26:21

**Written by [Eze Sunday](https://blog.logrocket.com/author/ezesunday/)✏️**


For seven years now, the [Rust](https://blog.logrocket.com/tag/rust/) programming language has been voted the most loved programming language, according to a survey by [Stack Overflow](https://insights.stackoverflow.com/survey). Its popularity stems from its focus on [safety, performance, built-in memory management, and concurrency features](https://blog.logrocket.com/why-is-rust-popular/). All of these reasons make it an excellent choice for building web applications. 

However, Rust is a system programming language. How do you use it to create web applications? Enter [Rocket](https://rocket.rs/), [Actix](http://actix.rs), [Warp](https://docs.rs/warp/latest/warp/), and more. These web frameworks enable developers to create web applications with Rust. 

Rocket and Diesel provide a powerful and efficient toolset for building web apps in Rust. In the rest of this article, we will go over how to create a web app using Rust, Rocket, and Diesel. We’ll go over setting up the development environment, examining the different components, setting up API endpoints, and rendering HTML. 

To get ultimate value from this piece, you’ll need a [basic understanding of Rust](https://blog.logrocket.com/getting-up-to-speed-with-rust/). You will also need to have Rust and PostgreSQL database installed and running. You can follow the [documentation](https://doc.rust-lang.org/book/ch01-01-installation.html) to install Rust for your operating system and download PostgreSQL for your OS from the [official website](https://www.postgresql.org/download/). If you are using macOS, you can install and get your PostgreSQL database up and running quickly by running the following commands on your terminal:

```shell
brew update && brew install postgresql && brew services start postgresql
```

Let’s dive right in! 

_Jump ahead:_

*   [Intro to Rocket and Diesel](#rocket-diesel)
*   [Setting up Rocket and Diesel](#setting-up-rocket-diesel)
    *   [Installing the Diesel CLI](#diesel-cli)
    *   [Running the migration](#running-migration)
*   [Building the Rocket model](#building-rocket-model)
    *   [Connecting to the database](#connecting-database)
*   [Creating a post](#creating-post)
*   [How to view posts](#viewing-posts)
*   [Testing with Postman and the web browser](#testing-postman-web-browser)

## Intro to Rocket and Diesel <a name="rocket-diesel">

Rocket is a [Rust web framework](https://blog.logrocket.com/rust-web-apps-using-rocket-framework/) with built-in tools developers need to create efficient and secure web apps while maintaining flexibility, usability, memory, and type safety with a clean and simple syntax. 

As is customary for most web frameworks, Rocket allows you to use [object-relational mappers](https://blog.logrocket.com/11-database-drivers-and-orms-for-rust-that-are-ready-for-production/) (ORMs) as a data access layer for your application. [Rocket is ORM agnostic](https://rocket.rs/v0.5-rc/guide/state/#databases), which means you can use any Rust ORM of your choice to access your database in your Rocket application. In this article, we’ll use [Diesel](https://diesel.rs/) ORM as our ORM of choice as it’s one of the most popular Rust ORMs. At the time of writing, Diesel ORM supports [PostgreSQL](https://blog.logrocket.com/tag/postgresql/)**,** [MySQL](https://blog.logrocket.com/5-ways-rapidly-improve-mysql-database-performance/)**,** and [SQLite](https://blog.logrocket.com/create-api-rust-sqlite-rocket/) databases.

## Setting up Rocket and Diesel <a name="setting-up-rocket-diesel">

First things first, let’s create a new [Rust Binary-based application](https://doc.rust-lang.org/cargo/guide/creating-a-new-project.html) with [Cargo](https://blog.logrocket.com/demystifying-cargo-in-rust/), as shown below:

```shell
cargo new blog --bin 
```

When you run the command above, a `blog` directory will be automatically generated with the following structure:

```shell
.
├── Cargo.toml
└── src
    └── main.rs
```

Next, we will need to add Diesel, Rocket, and other dependencies to the `Cargo.toml` file. These additional dependencies include:

*   `dotenvy` crate: To allow us to use environment variables from the `.env` file
*   `serde`: For data serialization and deserialization
*   [`dependencies.rocket_contrib`](https://api.rocket.rs/v0.4/rocket_contrib/): To work with `serde` for data deserialization
*   [`dependencies.rocket_dyn_templates`](https://api.rocket.rs/master/rocket_dyn_templates/): For dynamic templating engine support

```toml
#Cargo.toml
[package]
name = "blog"
version = "0.1.0"
edition = "2021"

[dependencies]
rocket = { version = "0.5.0-rc.2", features=["json"]}
diesel = { version = "2.0.0", features = ["postgres", "r2d2"] }
dotenvy = "0.15"
serde = "1.0.152"

[dependencies.rocket_dyn_templates]
features = ["handlebars"]

[dependencies.rocket_contrib]
version = "0.4.4"
default-features = false
features = ["json"]
```

Now, we have all the dependencies. Take note that I enabled `json` in the `features` to make [`serde json`](https://api.rocket.rs/master/rocket/serde/json/struct.Json.html) available in the Rocket application. I also enabled `postgres` and [`r2d2`](https://docs.rs/diesel/latest/diesel/r2d2/index.html) for Diesel to make the PostgreSQL and connection pooling feature available.

### Installing the Diesel CLI <a name="diesel-cli">

Diesel provides a [CLI](https://crates.io/crates/diesel_cli) that allows you to manage and automate the Diesel setup database reset and database migrations processes. Install the Diesel CLI by running the command below:

```shell
cargo install diesel_cli --no-default-features --features postgres
```

> NOTE: Make sure you have PostgreSQL installed; otherwise, you’ll have errors.

Next, create a `.env` file and add your database connection string as shown below:

```shell
DATABASE_URL=postgres://username:password@localhost/blog
```

Keep in mind that the database `blog` must exist on your `Postgres` database. From there, run the `diesel setup` command on your Terminal. 

This command will create a `migration` file and a `diesel.toml` file with the necessary configurations, as shown below:

```toml
├── Cargo.toml
├── diesel.toml
├── migrations
│   └── 00000000000000_diesel_initial_setup
│       ├── down.sql
│       └── up.sql
└── src
    └── main.rs
```

Because this project is a blog with a posts table to store all posts, we need to create a migration with `diesel migration generate posts` code. 

Once you run that command, the response should look like this: ![Rust, Rocket, and Diesel Project Set Up](https://blog.logrocket.com/wp-content/uploads/2023/02/rust-project-start.png) Now, when you open the `up.sql` file in the `migration` directory, there should be no content in it. Next, add the SQL query to create the `posts` table with the code below:

```sql
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR NOT NULL,
  body TEXT NOT NULL,
  published BOOLEAN NOT NULL DEFAULT FALSE
)
```

Also, open the `down.sql` file and add a query to drop the `table`:

```sql
-- This file should undo anything in `up.sql`
DROP TABLE posts
```

### Running the migration <a name="running-migration">

Once these files are updated, we can run the migration. You will need to create the `down.sql` file and make sure it’s accurate so that you can quickly roll back your migration with a simple command. To apply the changes we just made to the `migration` file, run the `diesel migration run` command. You’ll notice that the `schema.rs` file and the database `table` will be created. Here’s what it should look like: ![Example of the Diesel Migration](https://blog.logrocket.com/wp-content/uploads/2023/02/diesel-migration-rust.png) 

Here’s how the table will look in PostgreSQL: ![List of Tables in the Blog Database](https://blog.logrocket.com/wp-content/uploads/2023/02/tables-blog-rust.png) ![Table Description in Rust](https://blog.logrocket.com/wp-content/uploads/2023/02/tables-blog-rust-info.png) 

To redo the `migration`, run the `diesel migration redo` command. The table we just created will be deleted from the Postgres database. Of course, this is not something you want to do when you have real data because you’ll delete everything. So far, we’ve set up Diesel. We need to set up the blog post model to allow Rocket to interact with the schema effectively.

## Building the Rocket model <a name="building-rocket-model">

To build the Rocket model, create a `Models` directory and a `mod.rs` file. Then, add the following content to it:

```rust
// models/mod.rs
use super::schema::posts;
use diesel::{prelude::*};
use serde::{Serialize, Deserialize};

#[derive(Queryable, Insertable, Serialize, Deserialize)]
#[diesel(table_name = posts)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}
```

Notice that we are deriving `Queryable, Insertable, Serialize, Deserialize`. `Queryable` will allow us to run select queries on the table. If you don’t want the table to be selectable, you can ignore it, the same as the `Insertable`. `Insertable` allows you to create a record in the database. And finally, `Serialize` and `Deserialize` automatically allow you to serialize and deserialize the table. 

If you are following along, your application structure should look like this:

```shell
.
├── Cargo.lock
├── Cargo.toml
├── diesel.toml
├── target
├── migrations
│   ├── 00000000000000_diesel_initial_setup
│   │   ├── down.sql
│   │   └── up.sql
│   └── 2023-01-18-115141_posts
│       ├── down.sql
│       └── up.sql
└── src
    ├── main.rs
    ├── models
    │   └── mod.rs
    └── schema.rs
```

At this point, we have everything all setup! Now, let’s write a service to create a blog post via an API and display it in the browser — that way, we get to see how to handle both scenarios. Before making services to create and view blog posts, let’s connect to the database.

### Connecting to the database <a name="connecting-database">

Create a `services` directory and add the following content to the `mod.rs` file:

```rust
extern crate diesel;
extern crate rocket;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenvy::dotenv;
use rocket::response::{status::Created, Debug};
use rocket::serde::{json::Json, Deserialize, Serialize};
use rocket::{get, post };
use crate::models;
use crate::schema;
use rocket_dyn_templates::{context, Template};
use std::env;

pub fn establish_connection_pg() -> PgConnection {
    dotenv().ok();
    let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
    PgConnection::establish(&database_url)
        .unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}
```

In the code above, we imported all the necessary `crates` we’ll use in the service and created the Postgres `connection` function. We’ll reuse this function when we create and query data from the database. Now that we have a database connection let’s implement a function to create a record in the database.

## Creating a post <a name="creating-post">

Creating a record with Diesel ORM is pretty straightforward. We’ll start by creating a struct that represents the structure of the data we are expecting from the client and enable `Serialization` via the derive attribute as shown below:

```rust
//service/mod.rs

#[derive(Serialize, Deserialize)]
pub struct NewPost {
    title: String,
    body: String,
}
```

Next, we’ll create the actual function that receives the data from the client and processes it:

```rust
type Result<T, E = Debug<diesel::result::Error>> = std::result::Result<T, E>;

#[post("/post", format = "json", data = "<post>")]
pub fn create_post(post: Json<NewPost>) -> Result<Created<Json<NewPost>>> {
    use self::schema::posts::dsl::*;
    use models::Post;
    let connection = &mut establish_connection_pg();

    let new_post = Post {
        id: 1,
        title: post.title.to_string(),
        body: post.body.to_string(),
        published: true,
    };

    diesel::insert_into(self::schema::posts::dsl::posts)
        .values(&new_post)
        .execute(connection)
        .expect("Error saving new post");
    Ok(Created::new("/").body(post))
}
```

The `create_post` function above accepts a `post` object as a parameter and returns a `Result that` could be an `error` or a successful creation. The attribute `#[post("/posts")]` indicates that it’s a `POST` request. The `Created` response returns a `200` status code, and this line `Created::new("/").body(post)` returns both the `200` status code and the `record` that was just inserted if the insertion was successful as a `JSON Deserialized object`.

## How to view posts <a name="viewing-posts">

Now that we can create records, let’s create the functionality to view the records we’ve created in the browser. The creation logic was for a [Rest API](https://blog.logrocket.com/building-rest-api-rust-rhai-actix-web/). Now we need to meddle with HTML templates:

```rust
#[get("/posts")]
pub fn list() -> Template {
    use self::models::Post;
    let connection = &mut establish_connection_pg();
    let results = self::schema::posts::dsl::posts
        .load::<Post>(connection)
        .expect("Error loading posts");
    Template::render("posts", context! {posts: &results, count: results.len()})
}
```

In the code above, we requested all the posts in the `posts` table. Note that the `#[get("/posts")]` attribute indicates that it’s a `GET` request. We can also use a `filter` only to fetch published posts. For example, the code below will fetch all posts that have been published:

```rust
let results = self::schema::posts::dsl::posts
        .filter(published.eq(true))
        .load::<Post>(connection)
        .expect("Error loading posts");
```

Notice that the function returns a `Template`, right? Remember the `Cargo.toml` file that we added `handlebars` to as the templating engine? That function will return a `template`, and `handlebars` will take care of the rest:

```handlebars
[dependencies.rocket_dyn_templates]
features = ["handlebars"]
```

Let’s take a closer look at the response:

```rust
Template::render("posts", context! {posts: &results, count: results.len()})
```

The first argument is the handlebar’s template filename. We haven’t created it yet, so let’s do that. First, create a directory named `templates` and ad the file `posts.html.hbs`. Make sure the `HTML` is in the name. Otherwise, Rocket might not be able to recognize the file as a template. 

Add the code below as the content of the file:

```handlebars
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Blog Posts</title>
</head>
<body>
    <section id="hello">
        <h1>Posts</h1>
        New Posts
        <ul>
            {{#each posts}}
            <li>Title: {{ this.title }}</li>
            <li>Body: {{ this.body }}</li>
            {{else}}
            <p> No posts yet</p>
            {{/each}}
        </ul>
    </section>
</body>
</html>
```

We use the `#each` loop in the template to loop through the posts and display the content individually. By now, your directory structure should look like this:

```shell
.
├── Cargo.lock
├── Cargo.toml
├── diesel.toml
├── migrations
│   ├── 00000000000000_diesel_initial_setup
│   │   ├── down.sql
│   │   └── up.sql
│   └── 2023-01-18-115141_posts
│       ├── down.sql
│       └── up.sql
├── src
│   ├── main.rs
│   ├── models
│   │   └── mod.rs
│   ├── schema.rs
│   └── services
│       └── mod.rs
└── templates
    └── posts.html.hbs
```

Lastly, let’s add a `route` and test the application. Open the `main.rs` file and replace the existing `"Hello, World!"` function with the following:

```rust
extern crate rocket;
use rocket::{launch, routes};
use rocket_dyn_templates::{ Template };
mod services;
pub mod  models;
pub mod  schema;

#[launch]
fn rocket() -> _ {
    rocket::build()
        .mount("/", routes![services::create_post])
        .mount("/", routes![services::list])
        .attach(Template::fairing())
}
```

In the code above, we imported the [`launch`](https://api.rocket.rs/master/rocket/attr.launch.html) macro that generates the main function — the application's entry point and returns `Rocket<Build>`. You'll have to mount every `route` you want to add. We use the `attach` method to render the template and pass the [`fairing`](https://api.rocket.rs/v0.4/rocket/fairing/trait.Fairing.html) trait to it. 

That’s it. We are now ready to test the application. If you have followed through to this point, you are the real MVP.

## Testing with Postman and the web browser <a name="testing-postman-web-browser">

At this point, we’ve done the hard part. Let’s do the fun part, where you see how what we’ve built works. First, compile and run the application by running the `cargo run` command on your terminal. You should see something like this if everything goes well: ![Rust, Rocket, and Diesel Running](https://blog.logrocket.com/wp-content/uploads/2023/02/rust-rocket-diesel-running.png) Go to `http://127.0.0.1:8000/posts` via your browser (`GET` request) to view all the posts you have created. Initially, there will be no posts. Let’s create one with [Postman](https://blog.logrocket.com/how-automate-api-tests-postman/). So, we’ll make an `HTTP POST` request to the `/post` endpoint to create a blog post: ![Blog Post Created With Rust, Rocket, and Diesel](https://blog.logrocket.com/wp-content/uploads/2023/02/blog-post-browser.png) 

When we check back again on the browser, we should see our new post. ![Created Web App With Rust, Rocket, and Diesel](https://blog.logrocket.com/wp-content/uploads/2023/02/final-blog-post-rust.png)

## Conclusion

This article taught us how to create a web application with Rust, Rocket, and Diesel. We explored how to create an API endpoint, insert and read from a database, and how to render HTML templates. I hope you enjoyed reading it as much as I did writing it. For further reading, I encourage you to read this article about [building a web app with Rocket](https://blog.logrocket.com/rust-web-apps-using-rocket-framework/). 

You should also check out the [GitHub repo](https://github.com/ezesundayeze/rust-web) for the built demo application. It should be a primary point of reference if you get confused here.

---
## LogRocket: Full visibility into web frontends for Rust apps

Debugging Rust applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, [try LogRocket](https://lp.logrocket.com/blg/signup). 

[![LogRocket Dashboard Free Trial Banner](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://lp.logrocket.com/blg/signup)

[LogRocket](https://logrocket.com/signup) is like a DVR for web apps, recording literally everything that happens on your Rust app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.

Modernize how you debug your Rust apps — [start monitoring for free](https://lp.logrocket.com/blg/signup).