A Simple Crud on Rust (With Rocket.rs and Diesel.rs)
Project structure.

A Simple Crud on Rust (With Rocket.rs and Diesel.rs)

?? If you want, you can have a better time reading this not on LinkedIn (Here we don`t have cool features as we have in medium, so heres the medium link pay-wall free: https://medium.com/swlh/a-simple-crud-on-rust-with-rocket-rs-and-diesel-rs-e885672cb23d?sk=a78a73580666e0ba7ea858d0d556d20e )

This tutorial shows you how to create a simple CRUD on Rust Language (with rocket.rs as our web server and diesel.rs being our ORM for PostgreSQL.)

?? To follow this, you must have:

  • Rust nightly version & cargo and as well it must be on your path (you can check it by tipping on any terminal: $ cargo — version )
  • PostgreSQL (the LATEST stable version is recommended)
  • PgAdmin, because it has a nice interface for the DataBase management System and will make things a lot easier to debug..

After setting all this up, we’re able to start, first:

$ cargo new heroes

Then change to the directory of the project into this new folder by typing: “cd heroes/”

And you will see something like this:

No alt text provided for this image

After that, open this folder in your favorite text editor, like vscode. If you do have vs-code too, you can open it fast by typing: code ./

The project inside VSCODE

No alt text provided for this image

Let’s install the diesel_cli. The diesel_cli will manage our migration process. On your terminal, do:

$ cargo install diesel_cli --no-default-features --features postgres

This will make the diesel_cli available for use. Since we have diesel installed, let’s create a “.env” file that will hold our environment information, like our database connection. To enter the database connection you must run the following command:

$ echo DATABASE_URL=postgres://username:password@localhost/heroes > .env

After that, a new file will pop on your folder (.env). If everything is ok inside it, now we can run:

$ diesel setup

After that, on our PgAdmin session, we’ll be able to see our new database, “heroes”:

No alt text provided for this image

Since we have a database, the only thing missing is our table that will hold heroes information. For the sake of good practices we will use diesel migration system to instance it:

$ diesel migration generate heroes

By running it, another two files will pop on our project folder inside migrations: the “up.sql” that will bring our table structure up and the “down.sql” that will drop our table (it must be the inverse of up).

For up.sql:

CREATE TABLE heroes (
    id SERIAL PRIMARY KEY,
    fantasy_name VARCHAR NOT NULL,
    real_name VARCHAR NULL,
    spotted_photo TEXT NOT NULL,
    strength_level INT NOT NULL DEFAULT 0
);

And for down.sql, just:

DROP TABLE heroes;

And to migrate it to our database… just:

$ diesel migration run

You also must need to create a folder named: “templates” on your project root, and for sure, there must be a folder named “imgs” to hold our leaked heroes photos. Your folder tree should look like this:

No alt text provided for this image

So far so good, we can start with rust stuff.

?? First things first.

Let import what we will be needing to follow up inside Cargo.toml (our dependencies).

    [package]
	name = "heroes"
	version = "0.1.0"
	authors = ["luisvonmuller <[email protected]>"]
	edition = "2018"
	

	[dependencies]
	rocket = "0.4.5"
	rocket_codegen = "0.4.5"
	diesel = { version = "1.4.5", features = ["postgres"] }
	dotenv = "0.15.0"
	rocket-multipart-form-data = "0.9.5"
	serde = { version = "1.0", features = ["derive"] }
	

	[dependencies.rocket_contrib]
	version = "0.4.5"
	default-features = false
	features = ["handlebars_templates"]
  • dotenv stands for the library that makes easier to read our .env file.
  • rocket provides our webserver, the rocket code gen and the multipart will give us the libraries needed to use images and templates via rocket.
  • diesel (ORM)

Our main.rs

#![feature(proc_macro_hygiene, decl_macro)]
	

	/* Our extern crates */
	#[macro_use]
	extern crate diesel;
	

	#[macro_use]
	extern crate rocket;
	

	extern crate dotenv;
	

	/* Importing functions */
	use diesel::pg::PgConnection;
	use diesel::Connection;
	use dotenv::dotenv;
	use std::env;
	use rocket_contrib::templates::Template;
	

	/* Static files imports */
	use std::path::{Path, PathBuf};
	use rocket::response::NamedFile;
	

	/* Declaring a module, just for separating things better */
	pub mod heroes;
	

	/* Will hold our data structs */
	pub mod models;
	

	/* auto-generated table macros */
	pub mod schema;
	


	/* This will return our pg connection to use with diesel */
	pub fn establish_connection() -> PgConnection {
	    dotenv().ok();
	

	    let database_url = env::var("DATABASE_URL")
	        .expect("DATABASE_URL must be set");
	

	    PgConnection::establish(&database_url)
	        .expect(&format!("Error connecting to {}", database_url))
	}
	

	/* Static files Handler, will give back our heroes images */ 
	#[get("/imgs/<file..>")]
	fn assets(file: PathBuf) -> Option<NamedFile> {
	    NamedFile::open(Path::new("imgs/").join(file)).ok()
	}
	

	

	fn main() {
	    rocket::ignite().mount("/", routes![
	        assets,
	        heroes::list, 
	        heroes::new, 
	        heroes::insert,
	        heroes::update,
	        heroes::process_update,
	        heroes::delete
	        ]).attach(Template::fairing()).launch();
	}

This file declares a lot of stuff, we must take a closer look at our modules declarations:

  • schema.rs” : The auto-generated table macro that diesel gives us when we run migrations. (You don’t need to create it, diesel will.)
  • models.rs”: This one you have to manually create, will store our database related data structures.
    /* Import macros and others */
	use crate::schema::*;
	

	/* For beeing able to serialize */
	use serde::Serialize;
	

	#[derive(Debug, Queryable, Serialize)]
	pub struct Hero {
	    pub id: i32, 
	    pub fantasy_name: String,
	    pub real_name: Option<String>,
	    pub spotted_photo: String,
	    pub strength_level: i32,
	}
	

	#[derive(Debug, Insertable, AsChangeset)]
	#[table_name="heroes"]
	pub struct NewHero<'x> {
	    pub fantasy_name: &'x str,
	    pub real_name: Option<&'x str>,
	    pub spotted_photo: String,
	    pub strength_level: i32,
	}

  • heroes.rs”: This one will hold our back-end processing (CRUD)
    /* To be able to return Templates */
	use rocket_contrib::templates::Template;
	use std::collections::HashMap;
	

	/* Diesel query builder */
	use diesel::prelude::*;
	

	/* Database macros */
	use crate::schema::*;
	

	/* Database data structs (Hero, NewHero) */
	use crate::models::*;
	

	/* To be able to parse raw forms */
	use rocket::http::ContentType;
	use rocket::Data;
	use rocket_multipart_form_data::{
	    MultipartFormData, MultipartFormDataField, MultipartFormDataOptions,
	};
	

	/* Flash message and redirect */
	use rocket::request::FlashMessage;
	use rocket::response::{Flash, Redirect};
	

	/* List our inserted heroes */
	#[get("/")]
	pub fn list(flash: Option<FlashMessage>) -> Template {
	    let mut context = HashMap::new();
	

	    /* Get all our heroes from database */
	    let heroes: Vec<Hero> = heroes::table
	        .select(heroes::all_columns)
	        .load::<Hero>(&crate::establish_connection())
	        .expect("Whoops, like this went bananas!");
	

	    /* Insert on the template rendering
	    context our new heroes vec */
	    if let Some(ref msg) = flash {
	        context.insert("data", (heroes, msg.msg()));
	    } else {
	        context.insert("data", (heroes, "Listing heroes..."));
	    }
	

	    /* Return the template */
	    Template::render("list", &context)
	}
	

	#[get("/new")]
	pub fn new(flash: Option<FlashMessage>) -> Template {
	    let mut context = HashMap::new();
	    if let Some(ref msg) = flash {
	        context.insert("flash", msg.msg());
	    }
	    Template::render("new", context)
	}
	

	#[post("/insert", data ?= "<hero_data>")]
	pub fn insert(content_type: &ContentType, hero_data: Data) -> Flash<Redirect> {
	    /* File system */
	    use std::fs;
	

	    /* First we declare what we will be accepting on this form */
	    let mut options = MultipartFormDataOptions::new();
	

	    options.allowed_fields = vec![
	        MultipartFormDataField::file("spotted_photo"),
	        MultipartFormDataField::text("fantasy_name"),
	        MultipartFormDataField::text("real_name"),
	        MultipartFormDataField::text("strength_level"),
	    ];
	

	    /* If stuff matches, do stuff */
	    let multipart_form_data = MultipartFormData::parse(content_type, hero_data, options);
	

	    match multipart_form_data {
	        Ok(form) => {
	            /* If everything is ok, we will move the image and the insert into our datatabase */
	            let hero_img = match form.files.get("spotted_photo") {
	                Some(img) => {
	                    let file_field = &img[0];
	                    let _content_type = &file_field.content_type;
	                    let _file_name = &file_field.file_name;
	                    let _path = &file_field.path;
	

	                    /* Lets split name to get format */
	                    let format: Vec<&str> = _file_name.as_ref().unwrap().split('.').collect(); /* Reparsing the fileformat */
	

	                    /* Path parsing */
	                    let absolute_path: String = format!("imgs/{}", _file_name.clone().unwrap());
	                    fs::copy(_path, &absolute_path).unwrap();
	

	                    Some(format!("imgs/{}", _file_name.clone().unwrap()))
	                }
	                None => None,
	            };
	

	            /* Insert our form data inside our database */
	            let insert = diesel::insert_into(heroes::table)
	                .values(NewHero {
	                    fantasy_name: match form.texts.get("fantasy_name") {
	                        Some(value) => &value[0].text,
	                        None => "No Name.",
	                    },
	                    real_name: match form.texts.get("real_name") {
	                        Some(content) => Some(&content[0].text),
	                        None => None,
	                    },
	                    spotted_photo: hero_img.unwrap(),
	                    strength_level: match form.texts.get("strength_level") {
	                        Some(level) => level[0].text.parse::<i32>().unwrap(),
	                        None => 0,
	                    },
	                })
	                .execute(&crate::establish_connection());
	

	            match insert {
	                Ok(_) => Flash::success(
	                    Redirect::to("/"),
	                    "Success! We got a new Hero on our database!",
	                ),
	                Err(err_msg) => Flash::error(
	                    Redirect::to("/new"),
	                    format!(
	                        "Houston, We had problems while inserting things into our database ... {}",
	                        err_msg
	                    ),
	                ),
	            }
	        }
	        Err(err_msg) => {
	            /* Falls to this patter if theres some fields that isn't allowed or bolsonaro rules this code */
	            Flash::error(
	                Redirect::to("/new"),
	                format!(
	                    "Houston, We have problems parsing our form... Debug info: {}",
	                    err_msg
	                ),
	            )
	        }
	    }
	}
	

	#[get("/update/<id>")]
	pub fn update(id: i32) -> Template {
	    let mut context = HashMap::new();
	    let hero_data = heroes::table
	        .select(heroes::all_columns)
	        .filter(heroes::id.eq(id))
	        .load::<Hero>(&crate::establish_connection())
	        .expect("Something happned while retrieving the hero of this id");
	

	    context.insert("hero", hero_data);
	

	    Template::render("update", &context)
	}
	

	#[post("/update", data ?= "<hero_data>")]
	pub fn process_update(content_type: &ContentType, hero_data: Data) -> Flash<Redirect> {
	    /* File system */
	    use std::fs;
	

	    /* First we declare what we will be accepting on this form */
	    let mut options = MultipartFormDataOptions::new();
	

	    options.allowed_fields = vec![
	        MultipartFormDataField::file("spotted_photo"),
	        MultipartFormDataField::text("id"),
	        MultipartFormDataField::text("fantasy_name"),
	        MultipartFormDataField::text("real_name"),
	        MultipartFormDataField::text("strength_level"),
	    ];
	

	    /* If stuff matches, do stuff */
	    let multipart_form_data = MultipartFormData::parse(content_type, hero_data, options);
	

	    match multipart_form_data {
	        Ok(form) => {
	            /* If everything is ok, we will move the image and the insert into our datatabase */
	            let hero_img = match form.files.get("spotted_photo") {
	                Some(img) => {
	                    let file_field = &img[0];
	                    let _content_type = &file_field.content_type;
	                    let _file_name = &file_field.file_name;
	                    let _path = &file_field.path;
	

	                    /* Lets split name to get format */
	                    let format: Vec<&str> = _file_name.as_ref().unwrap().split('.').collect(); /* Reparsing the fileformat */
	

	                    /* Path parsing */
	                    let absolute_path: String = format!("imgs/{}", _file_name.clone().unwrap());
	                    fs::copy(_path, &absolute_path).unwrap();
	

	                    Some(format!("imgs/{}", _file_name.clone().unwrap()))
	                }
	                None => None,
	            };
	

	            /* Insert our form data inside our database */
	            let insert = diesel::update(
	                heroes::table.filter(
	                    heroes::id.eq(form.texts.get("id").unwrap()[0]
	                        .text
	                        .parse::<i32>()
	                        .unwrap()),
	                ),
	            )
	            .set(NewHero {
	                fantasy_name: match form.texts.get("fantasy_name") {
	                    Some(value) => &value[0].text,
	                    None => "No Name.",
	                },
	                real_name: match form.texts.get("real_name") {
	                    Some(content) => Some(&content[0].text),
	                    None => None,
	                },
	                spotted_photo: hero_img.unwrap(),
	                strength_level: match form.texts.get("strength_level") {
	                    Some(level) => level[0].text.parse::<i32>().unwrap(),
	                    None => 0,
	                },
	            })
	            .execute(&crate::establish_connection());
	

	            match insert {
	                Ok(_) => Flash::success(
	                    Redirect::to("/"),
	                    "Success! We got a new Hero on our database!",
	                ),
	                Err(err_msg) => Flash::error(
	                    Redirect::to("/new"),
	                    format!(
	                        "Houston, We had problems while inserting things into our database ... {}",
	                        err_msg
	                    ),
	                ),
	            }
	        }
	        Err(err_msg) => {
	            /* Falls to this patter if theres some fields that isn't allowed or bolsonaro rules this code */
	            Flash::error(
	                Redirect::to("/new"),
	                format!(
	                    "Houston, We have problems parsing our form... Debug info: {}",
	                    err_msg
	                ),
	            )
	        }
	    }
	}
	

	#[get("/delete/<id>")]
	pub fn delete(id: i32) -> Flash<Redirect> {
	    diesel::delete(heroes::table.filter(heroes::id.eq(id)))
	        .execute(&crate::establish_connection())
	        .expect("Ops, we can't delete this.");
	    Flash::success(Redirect::to("/"), "Yey! The hero was deleted.")
	
    }

??????

And we will have our templates too, since they’re 3 files, and this small post will turn into a big one if I throw them here too, I’ll give the link to the repo then you can get them at: https://github.com/luisvonmuller/heroes-crud-rust/tree/master/templates

??????

After all this, you can just run:

$ cargo run

And you will be able to see the result on your favorite web browser, located at: https://locahost:8000/.

No alt text provided for this image


This is it — Strokes, the.


?? Would be great if you could follow me on twitter: @luisvonmuller

Soon I'll post it on portuguese too. ??

?? I'm open to new opportunities, mail me at: [email protected]


Augusto Ferrari Silva

Analista de conteúdo na Von Muller Software

4 年

Muito show...

回复

要查看或添加评论,请登录

Luís Von Müller的更多文章

  • ?? O LinkedIn N?O quer que você leia esse artigo!

    ?? O LinkedIn N?O quer que você leia esse artigo!

    Eu sou um programador, atualmente acabei de ser promovido para Para dar conta desse cargo e chegar até ele, eu aprendi…

    22 条评论
  • Montando a SUA estratégia de marketing matadora.

    Montando a SUA estratégia de marketing matadora.

    Aqui se promete e se faz, ent?o seguindo o cronograma: Hoje - Como montar uma estratégia de marketing? #howto…

    8 条评论
  • Presen?a é venda

    Presen?a é venda

    O tópico sobre qual trataremos hoje, é chamado pelos psicólogos de "efeito de mera exposi??o" e o relacionaremos ao…

    5 条评论

社区洞察

其他会员也浏览了