DEV Community

Nivethan
Nivethan

Posted on • Updated on • Originally published at nivethan.dev

A Web App in Rust - 09 A New Index Page

Welcome back! We've come a long way from our humble beginnings of printing hello world to now being able to register new users and submitting new posts. This chapter we'll update our index route that we had stubbed out last week.

Currently if we navigate to 127.0.0.1:8000/ we will cause rust to panic, let's fix that!

The Index Route

What we want to do in our index route is to fetch all the posts sorted by date. We could add votes and weights to our posts so that the order can change but let's keep it simple. Our plan of attack will be to set up a database connection then query the posts table for all the entries. We will also need to do a join to get the user for each post, using the author field on the post.

Let's get started.

./src/main.rs

...
async fn index(tera: web::Data<Tera>) -> impl Responder {
    use schema::posts::dsl::{posts};
    use schema::users::dsl::{users};

    let connection = establish_connection();
    let all_posts :Vec<(Post, User)> = posts.inner_join(users)
        .load(&connection)
        .expect("Error retrieving all posts.");

    let mut data = Context::new();
    data.insert("title", "Hacker Clone");
    data.insert("posts_users", &all_posts);

    let rendered = tera.render("index.html", &data).unwrap();
    HttpResponse::Ok().body(rendered)
}
...
Enter fullscreen mode Exit fullscreen mode

We first bring in the domain specific languages for posts and users, as we will need information from both tables.

The next step is to establish our connection to the postgres database.

Next we will do query. Here we join our posts table to users and then our load function fill take in the connection and execute it. The load means that it will get all posts in the posts table.

The reason we can do an inner join is because diesel knows that it is joinable. When we first wrote our raw SQL back in the chapter on databases, we gave our author id field on posts the foreign key constraint.

./migrations/2020-10-18-064517_hackerclone/up.sql

...
CREATE TABLE posts (
    id SERIAL PRIMARY KEY,
    title VARCHAR NOT NULL,
    link VARCHAR,
    author INT NOT NULL,
    created_at TIMESTAMP NOT NULL,

    CONSTRAINT fk_author
        FOREIGN KEY(author)
            REFERENCES users(id)
);
...
Enter fullscreen mode Exit fullscreen mode

This constraint got written in our schema file as a joinable.

./src/schema.rs

...
joinable!(posts -> users (author));
...
Enter fullscreen mode Exit fullscreen mode

This means that we can join our users table to our posts via the author field on our post.

Now that our query is returning a tuple of Post and User we'll need to update our template to reflect this. We also renamed our posts variable in our tera context to posts_users as this is more descriptive.

./templates/index.html

{% extends "base.html" %}

{% block content %}        
{% for post_user in posts_users %}
{% set p = post_user[0] %}
{% set u = post_user[1] %}
<div>
    <a href="{{ p.link }}">{{ p.title }}</a>
    <small>{{ u.username }}</small>
</div>
{% endfor %}
{% endblock %}

Enter fullscreen mode Exit fullscreen mode

The first item in our tuple is the post and the second is the user. With these 2 objects we can now build our index page.

We should be able to navigate to 127.0.0.1:8000/ in our browsers now and see the posts that we have made.

! There we go! We have our index page all wired up now and we learned how to do joins to get data from other tables.

Now before we move on, let's do a quick update to our templates so we can access all of our routes without having to manually type them in. We can even clean up our index page so it formats a little bit better.

./templates/base.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>{{title}}</title>
        <style>
body { font-size:18px; }
td { vertical-align:top; }
        </style>
    </head>
    <body>
        <header>
            Hi, <i>Friend</i>, to <b>HackerClone</b>
            <div style="float:right;">
                <button onclick="window.location.href='/login'">
                    Login
                </button>
                <button onclick="window.location.href='/signup'">
                    Sign Up
                </button>
                <button onclick="window.location.href='/submission'">
                    Submit
                </button>
                <button onclick="window.location.href='/logout'">
                    Logout
                </button>
            </div>
        </header>
        <hr>
        {% block content %}
        {% endblock %}
    </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Here all we do is add a header and we make buttons for the various routes we set up. If we wanted we could making this template smarter and pass in the fact if we are logged in or not. Only logged in users should see the submit and logout button whereas only logged out users should see the login and signup button.

For now however, we'll leave it as it is.

Now for the index page.

./templates/index.html

{% extends "base.html" %}

{% block content %}        
<table>
    {% for post_user in posts_users %}
    {% set p = post_user[0] %}
    {% set u = post_user[1] %}
    <tr>
        <td>{{loop.index}}. </td>
        <td>
        <a href="{{ p.link }}">{{ p.title }}</a>
        <br>
        <small>
            submitted by 
            <a href="/user/{{u.username}}">
                {{ u.username }}
            </a>
        </small>
        <small><a href="/post/{{p.id}}">comments</a></small>
        <br>
        <small>{{ p.created_at }}</small>
        </td>
    </tr>
    {% endfor %}
</table>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Here we use table to format our posts and we also add some new information like our created at field on the post and we also added links to our user page and comments. For now ignore these as we will be wiring them up in the next chapter.

Now we have an index page and we have ours posts working fully! We can submit new posts and view them, let's get started on adding comments!

Discussion (1)

Collapse
tonylee3737 profile image
tonylee3737

Error occurs.
: data.insert("posts_users", &all_posts);
^^^^^^^^^^ the trait Serialize is not implemented for Post
*Fix here.

.model.rs

[derive(Debug, Queryable, Serialize)] <- add this Serialize

pub struct Post {
pub id: i32,
pub title: String,
pub link: Option,
pub author: i32,
pub created_at: chrono::NaiveDateTime,
}

Thanks for your effort :)