├── .env ├── .gitignore ├── Cargo.toml ├── README.md ├── app.db ├── diesel.toml ├── src ├── main.rs ├── models.rs ├── routes.rs └── schema.rs └── templates └── index.html /.env: -------------------------------------------------------------------------------- 1 | DATABASE_URL=app.db -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | 3 | debug/ 4 | target/ 5 | **/*.rs.bk 6 | *.pdb 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "demo" 3 | version = "0.1.0" 4 | authors = ["kilroy ", "M. Yas. Davoodeh "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | actix-web = "4.1.0" 11 | diesel = { version = "1.4.4", features = ["sqlite", "r2d2", "chrono"] } 12 | dotenv = "0.15.0" 13 | serde = { version = "1.0.139", features = ["derive"] } 14 | anyhow = "1.0.58" 15 | chrono = { version = "0.4.19", features = ["serde"] } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Repo for a Rust tutorial on Actix and the Diesel ORM. 2 | 3 | You can find the video series 4 | [here](https://www.youtube.com/playlist?list=PLBok0UdvO6519tl0LzER8w_J-0pcro7q9). 5 | 6 | **Update: October, 2021** 7 | 8 | While this tutorial may still be helpful, Rust has been moving quite fast in 9 | recent years and you'd do best to look around and see if Diesel is still the 10 | best choice. 11 | 12 | NOTE that Actix is actively maintained and some of the codes may be deprecated 13 | by the time you use them. 14 | 15 | As of July 2022, Actix-Web 4 is out and the videos are not updated yet. 16 | 17 | This is an updated version that does not have any videos explaining it. 18 | 19 | ## Commands 20 | 21 | ### Creating the docs 22 | 23 | To see the documentation (updated) run: 24 | 25 | ```sh 26 | cargo doc --open 27 | ``` 28 | 29 | ` 30 | -------------------------------------------------------------------------------- /app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kilroyjones/rust-tutorial-actix-diesel-sqlite/92cea50f1dfb3b264d0f9306018255c3090e9678/app.db -------------------------------------------------------------------------------- /diesel.toml: -------------------------------------------------------------------------------- 1 | # For documentation on how to configure this file, 2 | # see diesel.rs/guides/configuring-diesel-cli 3 | 4 | [print_schema] 5 | file = "src/schema.rs" 6 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | //! A simple Actix Web "Link saving service" to save and retrieve some links. 2 | //! 3 | //! Upon understanding the code, it is suggested to improve the code by adding 4 | //! TODOs or solving them. 5 | #[macro_use] 6 | extern crate diesel; 7 | 8 | mod models; 9 | mod routes; 10 | mod schema; 11 | 12 | use actix_web::{web, App, HttpServer}; 13 | use diesel::r2d2::{self, ConnectionManager}; 14 | use diesel::sqlite::SqliteConnection; 15 | 16 | /// Helps with changing the database engine without much edits. 17 | pub type DatabaseConnection = SqliteConnection; 18 | pub type Pool = r2d2::Pool>; 19 | 20 | #[actix_web::main] 21 | async fn main() -> std::io::Result<()> { 22 | dotenv::dotenv().ok(); 23 | 24 | // Read the DATABASE_URL from the environment or set it to app.db assuming 25 | // Sqlite is used. 26 | // TODO one can improve this and set the `DatabaseConnection` to 27 | // PostgresConnection despite what user wants if the DATABASE_URL is not 28 | // set. 29 | let database_url = std::env::var("DATABASE_URL").unwrap_or("app.db".to_string()); 30 | // To manage threads in an efficient way, we use database threadpools. 31 | let database_pool = Pool::builder() 32 | .build(ConnectionManager::::new(database_url)) 33 | .unwrap(); 34 | 35 | // TODO move the database_pool to a subsection of a custom AppData struct 36 | // that is derived from `Clone` and has such an impl: 37 | // impl AppState { 38 | // fn new() -> web::Data { 39 | // web::Data { 40 | // ... // DATA here 41 | // } 42 | // } 43 | // } 44 | // 45 | // This is a more favorable approach, see: https://stackoverflow.com/a/65993435/8401058 46 | 47 | // TODO make this an TLS SSL Secure server and use `mkcert` to test it. 48 | HttpServer::new(move || { 49 | App::new() 50 | .app_data(web::Data::new(database_pool.clone())) // App data NEEDS to be wrapped in Data 51 | // TODO convert routes to services. 52 | // TODO implement authorization using JWT 53 | .route("/", web::get().to(routes::home)) 54 | .route("/addlink", web::post().to(routes::add_link)) 55 | .route("/getlinks", web::get().to(routes::get_links)) 56 | }) 57 | .bind("127.0.0.1:8888")? 58 | .run() 59 | .await 60 | } 61 | -------------------------------------------------------------------------------- /src/models.rs: -------------------------------------------------------------------------------- 1 | use crate::schema::*; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // TODO implement From<> and Into<> traits for all of the three. 5 | // TODO make a macro that automatically creates/guesses such structs. 6 | // TODO move it all to `models/link.rs` 7 | 8 | /// Represents a Link object from the database. 9 | #[derive(Debug, Serialize, Deserialize, Queryable)] 10 | pub struct Link { 11 | pub id: i32, 12 | pub link: String, 13 | pub title: String, 14 | pub date_create: String, 15 | } 16 | 17 | /// Represents a link that is being newly generated. 18 | #[derive(Debug, Insertable)] 19 | #[table_name = "links"] 20 | pub struct LinkNew<'a> { 21 | pub link: &'a str, 22 | pub title: &'a str, 23 | pub date_created: &'a str, 24 | } 25 | 26 | /// Represents a JSON data form filled request that will turn to a `Link`. 27 | #[derive(Debug, Serialize, Deserialize)] 28 | pub struct LinkJson { 29 | pub link: String, 30 | pub title: String, 31 | } 32 | -------------------------------------------------------------------------------- /src/routes.rs: -------------------------------------------------------------------------------- 1 | //! This file holds all the routes and request related functions. 2 | // TODO rename to handlers as it is more common in Actix Web and also handlers 3 | // files are expected to host services alongside routes but routes files are not 4 | // expected to have handlers in them. 5 | use crate::models::{Link, LinkJson, LinkNew}; 6 | use crate::Pool; 7 | 8 | use actix_web::http::StatusCode; 9 | use actix_web::{web, Error, HttpResponse}; 10 | use anyhow::Result; 11 | use diesel::dsl::insert_into; 12 | use diesel::prelude::*; 13 | use diesel::RunQueryDsl; 14 | 15 | /// Returns the home page of the site. 16 | pub async fn home() -> Result { 17 | Ok(HttpResponse::build(StatusCode::OK) 18 | .content_type("text/html; charset=utf-8") 19 | .body(include_str!("../templates/index.html"))) 20 | } 21 | 22 | /// Adds a link entry to the database if not already added. 23 | pub async fn add_link( 24 | pool: web::Data, 25 | item: web::Json, 26 | ) -> Result { 27 | Ok( 28 | match web::block(move || add_single_link(pool, item)).await { 29 | Ok(link) => HttpResponse::Created().json(link), 30 | _ => HttpResponse::from(HttpResponse::InternalServerError()), 31 | }, 32 | ) 33 | } 34 | 35 | /// Get all the links in the database. 36 | pub async fn get_links(pool: web::Data) -> Result { 37 | Ok(match get_all_links(pool).await { 38 | Ok(links) => HttpResponse::Ok().json(links), 39 | _ => HttpResponse::from(HttpResponse::InternalServerError()), 40 | }) 41 | } 42 | 43 | // TODO move to a method of the `Link`. 44 | fn add_single_link(pool: web::Data, item: web::Json) -> Link { 45 | use crate::schema::links::dsl::*; 46 | let db_connection = pool.get().unwrap(); 47 | 48 | match links 49 | .filter(link.eq(&item.link)) 50 | .first::(&db_connection) 51 | { 52 | Ok(result) => result, 53 | Err(_) => { 54 | let new_link = LinkNew { 55 | link: &item.link, 56 | title: &item.title, 57 | date_created: &format!("{}", chrono::Local::now().naive_local()), 58 | }; 59 | 60 | insert_into(links) 61 | .values(&new_link) 62 | .execute(&db_connection) 63 | .expect("Error saving new link"); 64 | 65 | let result = links.order(id.desc()).first(&db_connection).unwrap(); 66 | result 67 | } 68 | } 69 | } 70 | 71 | // TODO move to a method of the `Link`. 72 | async fn get_all_links(pool: web::Data) -> Result, diesel::result::Error> { 73 | use crate::schema::links::dsl::*; 74 | let db_connection = pool.get().unwrap(); 75 | let result = links.load::(&db_connection)?; 76 | Ok(result) 77 | } 78 | -------------------------------------------------------------------------------- /src/schema.rs: -------------------------------------------------------------------------------- 1 | //! This required header was automatically generated by `diesel print-schema`. 2 | //! 3 | //! Before creating this `setup` and `migrate` your tables. 4 | table! { 5 | links (id) { 6 | id -> Integer, 7 | link -> Text, 8 | title -> Text, 9 | date_created -> Text, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Kilroy was here

4 | فریمن اینجا بود! 5 | 6 | 7 | --------------------------------------------------------------------------------