Basic CRUD with rust using tide - refactoring
Tags: rust, tide
Published: 2021-07-11 19:17:37

In the last [post](https://javierviola.com/post/lets-create-a-basic-crud-with-rust-using-tide/) I started a basic crud using [tide](https://github.com/http-rs/tide) and we end up with a simple api that allow us to store `dinosaurs` information.

Starting from there, let's clean the code a little bit to be more organized. First, we had a closure in every route (let's call it `endpoint` from here) and will be more clear if we extract that to functions.

```rust
async fn dinos_create(mut req: Request<State>) -> tide::Result {
    let dino: Dino = req.body_json().await?;
    // let get a mut ref of our store ( hashMap )
    let mut dinos = req.state().dinos.write().await;
    dinos.insert(String::from(&dino.name), dino.clone());
    let mut res = Response::new(201);
    res.set_body(Body::from_json(&dino)?);
    Ok(res)
}

async fn dinos_list(req: tide::Request<State>) -> tide::Result {
    let dinos = req.state().dinos.read().await;
    // get all the dinos as a vector
    let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
    let mut res = Response::new(200);
    res.set_body(Body::from_json(&dinos_vec)?);
    Ok(res)
}


( ... )

    app.at("/dinos")
        .post(dinos_create)
        .get(dinos_list);


```

We moved the closures for this two endpoints to its own functions, let's run the tests to make sure we didn't break anything.

```bash
$ cargo test

    Finished test [unoptimized + debuginfo] target(s) in 12.48s
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test index_page ... ok
test list_dinos ... ok
test create_dino ... ok
test update_dino ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Great! works as expected. We can move now the rest of the endpoints

```rust
    app.at("/dinos/:name")
        .get( dinos_get )
        .put( dinos_update )
        .delete( dinos_delete );
```

Awesome, **but**  now we have five `dinos_` functions in the `main` file. Let's refactor this to be more organized

First, let's create a new `struct` to represent a `rest` entity with a `base_path` field.

```rust
struct RestEntity {
    base_path: String,
}
```
 And implement the same methods we had earlier

```rust
impl RestEntity {
    async fn create(mut req: Request<State>) -> tide::Result {
        let dino: Dino = req.body_json().await?;
        // let get a mut ref of our store ( hashMap )
        let mut dinos = req.state().dinos.write().await;
        dinos.insert(String::from(&dino.name), dino.clone());
        let mut res = Response::new(201);
        res.set_body(Body::from_json(&dino)?);
        Ok(res)
    }

    async fn list(req: tide::Request<State>) -> tide::Result {
        let dinos = req.state().dinos.read().await;
        // get all the dinos as a vector
        let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
        let mut res = Response::new(200);
        res.set_body(Body::from_json(&dinos_vec)?);
        Ok(res)
    }

    async fn get(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(entry) => {
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn update(mut req: tide::Request<State>) -> tide::Result {
        let dino_update: Dino = req.body_json().await?;
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(mut entry) => {
                *entry.get_mut() = dino_update;
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn delete(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let deleted = dinos.remove(&key);
        let res = match deleted {
            None => Response::new(404),
            Some(_) => Response::new(204),
        };
        Ok(res)
    }
}
```

Now we can create a `helper` function that allow us to `register` `rest` like entities to our server, registering five different endpoints to handle the list/create/read/update/delete operations.

```rust
fn register_rest_entity(app: &mut Server<State>, entity: RestEntity) {
    app.at(&entity.base_path)
        .get(RestEntity::list)
        .post(RestEntity::create);

    app.at(&format!("{}/:id", entity.base_path))
        .get(RestEntity::get)
        .put(RestEntity::update)
        .delete(RestEntity::delete);
}
```

And in the `server` we just need to create a new instance of the struct with the desired `base_path` and call the `helper fn`

```rust
    let dinos_endpoint = RestEntity {
        base_path: String::from("/dinos"),
    };

    register_rest_entity(&mut app, dinos_endpoint);
```

Great, let's just run the test to ensure that all the operations are still working...

```bash
cargo test
   Compiling tide-basic-crud v0.1.0
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test list_dinos ... ok
test create_dino ... ok
test index_page ... ok
test update_dino ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Awesome, we just create a nice abstraction that allow us to create easily more `rest` like entities and implement the basic operations.


That's all for today, in the next iteration I will try to move away from the `HashMap` and persist the entities information in a `db`.

As always, I write this as a learning journal and there could be another more elegant and correct way to do it and any feedback is welcome.

I leave here the [repo](https://github.com/pepoviola/tide-basic-crud) of this example and the [pr](https://github.com/pepoviola/tide-basic-crud/pull/1) of the refactor.

Thanks!




blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯


blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯



blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯




blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯





blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯





blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯






blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯






blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯

blog/content/post on  blog [$!?] on ☁️  (us-east-1)
❯ cat basic-crud-with-rust-using-tide-refactoring.md
---
layout: blog
title: Basic CRUD with rust using tide - refactoring
tags:
  - rust
  - notes
  - tide
  - http_rs
  - tide-basic-crud
date: 2020-10-03T19:20:36.901Z
---
In the last [post](https://javierviola.com/post/lets-create-a-basic-crud-with-rust-using-tide/) I started a basic crud using [tide](https://github.com/http-rs/tide) and we end up with a simple api that allow us to store `dinosaurs` information.

Starting from there, let's clean the code a little bit to be more organized. First, we had a closure in every route (let's call it `endpoint` from here) and will be more clear if we extract that to functions.

```rust
async fn dinos_create(mut req: Request<State>) -> tide::Result {
    let dino: Dino = req.body_json().await?;
    // let get a mut ref of our store ( hashMap )
    let mut dinos = req.state().dinos.write().await;
    dinos.insert(String::from(&dino.name), dino.clone());
    let mut res = Response::new(201);
    res.set_body(Body::from_json(&dino)?);
    Ok(res)
}

async fn dinos_list(req: tide::Request<State>) -> tide::Result {
    let dinos = req.state().dinos.read().await;
    // get all the dinos as a vector
    let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
    let mut res = Response::new(200);
    res.set_body(Body::from_json(&dinos_vec)?);
    Ok(res)
}


( ... )

    app.at("/dinos")
        .post(dinos_create)
        .get(dinos_list);


```

We moved the closures for this two endpoints to its own functions, let's run the tests to make sure we didn't break anything.

```bash
$ cargo test

    Finished test [unoptimized + debuginfo] target(s) in 12.48s
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test index_page ... ok
test list_dinos ... ok
test create_dino ... ok
test update_dino ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Great! works as expected. We can move now the rest of the endpoints

```rust
    app.at("/dinos/:name")
        .get( dinos_get )
        .put( dinos_update )
        .delete( dinos_delete );
```

Awesome, **but**  now we have five `dinos_` functions in the `main` file. Let's refactor this to be more organized

First, let's create a new `struct` to represent a `rest` entity with a `base_path` field.

```rust
struct RestEntity {
    base_path: String,
}
```
 And implement the same methods we had earlier

```rust
impl RestEntity {
    async fn create(mut req: Request<State>) -> tide::Result {
        let dino: Dino = req.body_json().await?;
        // let get a mut ref of our store ( hashMap )
        let mut dinos = req.state().dinos.write().await;
        dinos.insert(String::from(&dino.name), dino.clone());
        let mut res = Response::new(201);
        res.set_body(Body::from_json(&dino)?);
        Ok(res)
    }

    async fn list(req: tide::Request<State>) -> tide::Result {
        let dinos = req.state().dinos.read().await;
        // get all the dinos as a vector
        let dinos_vec: Vec<Dino> = dinos.values().cloned().collect();
        let mut res = Response::new(200);
        res.set_body(Body::from_json(&dinos_vec)?);
        Ok(res)
    }

    async fn get(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(entry) => {
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn update(mut req: tide::Request<State>) -> tide::Result {
        let dino_update: Dino = req.body_json().await?;
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let res = match dinos.entry(key) {
            Entry::Vacant(_entry) => Response::new(404),
            Entry::Occupied(mut entry) => {
                *entry.get_mut() = dino_update;
                let mut res = Response::new(200);
                res.set_body(Body::from_json(&entry.get())?);
                res
            }
        };
        Ok(res)
    }

    async fn delete(req: tide::Request<State>) -> tide::Result {
        let mut dinos = req.state().dinos.write().await;
        let key: String = req.param("id")?;
        let deleted = dinos.remove(&key);
        let res = match deleted {
            None => Response::new(404),
            Some(_) => Response::new(204),
        };
        Ok(res)
    }
}
```

Now we can create a `helper` function that allow us to `register` `rest` like entities to our server, registering five different endpoints to handle the list/create/read/update/delete operations.

```rust
fn register_rest_entity(app: &mut Server<State>, entity: RestEntity) {
    app.at(&entity.base_path)
        .get(RestEntity::list)
        .post(RestEntity::create);

    app.at(&format!("{}/:id", entity.base_path))
        .get(RestEntity::get)
        .put(RestEntity::update)
        .delete(RestEntity::delete);
}
```

And in the `server` we just need to create a new instance of the struct with the desired `base_path` and call the `helper fn`

```rust
    let dinos_endpoint = RestEntity {
        base_path: String::from("/dinos"),
    };

    register_rest_entity(&mut app, dinos_endpoint);
```

Great, let's just run the test to ensure that all the operations are still working...

```bash
cargo test
   Compiling tide-basic-crud v0.1.0
     Running target/debug/deps/tide_basic_crud-3d6db2bae3cd08a5

running 5 tests
test delete_dino ... ok
test list_dinos ... ok
test create_dino ... ok
test index_page ... ok
test update_dino ... ok

test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
```

Awesome, we just create a nice abstraction that allow us to create easily more `rest` like entities and implement the basic operations.


That's all for today, in the next iteration I will try to move away from the `HashMap` and persist the entities information in a `db`.

As always, I write this as a learning journal and there could be another more elegant and correct way to do it and any feedback is welcome.

I leave here the [repo](https://github.com/pepoviola/tide-basic-crud) of this example and the [pr](https://github.com/pepoviola/tide-basic-crud/pull/1) of the refactor.

Thanks!