Cookiecutter Rust Actix template for jumpstarting production-ready projects quickly.

Overview

Cookiecutter actix simple clean architecture

This is a reusable Rust Cookiecutter template. The project is based on Actix web in combination with Diesel ORM.

Complete list of features the template provides:

  • Onion architecture
  • Actix Web
  • Maintenance window support
  • Diesel ORM
  • Database migrations
  • Local postgres database docker support
  • Test containers integration for testing

Getting started

To start a new project, run the following command:

cookiecutter -c v1 https://github.com/microsoft/cookiecutter-rust-actix-clean-architecture

This will prompt you for some information about your project. The information you provide will be used to populate the files in the new project directory.

You can then build the project locally.

cargo build

Architecture

The application follows the Onion Architecture pattern. An article is written about our experience integrating an onion architecture with actix web in combination with diesel ORM that can be found here.

This architecture is a design pattern that organizes the codebase of a software application into multiple layers, where the innermost layer is the domain layer and the outermost layer is the application layer. Each layer depends only on the layers inside of it and not on the layers outside of it, creating a separation of concerns, allowing for a more maintainable and scalable codebase.

For this template we suggest using a service-repository design pattern. For example implementations you can have a look at

Running the application locally

To run the application locally, you need to have a Postgres database running. You can use the run_postgres.sh script in the scripts directory to run a Postgres container.

./scripts/run_postgres.sh

You can then run the application.

cargo run

Testing support

All tests are can be found under the src/tests folder. When using the template you can place all you tests in this folder.

To run the tests, you can use the following command:

cargo test

To run the tests with error output you can run the following command:

cargo test -- --nocapture

or

cargo test -- --show-output

Diesel ORM

The template uses Diesel ORM for its database connection and database models integration. Its is currently setup with postgres, however you can change it to any other database that is supported by diesel. For other databases have a look at the official Diesel documentation that can be found here

Database migrations

  1. Make sure you have the diesel cli installed. You can install it with the following command:
    cargo install diesel_cli --no-default-features --features postgres
  2. Add your postgres database url to the .env file:
    echo DATABASE_URL=postgres://username:password@localhost/diesel_demo > .env
  3. Setup diesel before creating a migration:
    diesel setup
  4. Create a migration with the following command:
    diesel migration generate <migration_name>
  5. Apply your migrations:
    diesel migration run

Service repository design pattern

Diesel Repositories

The onion architecture is best being used with a repository-service pattern. An example repository can be seen below:

// Can be placed under /src/domain/repositories/todo.rs
#[derive(Debug, Serialize, Deserialize)]
pub struct TodoQueryParams {
    pub limit: Option<i64>,
    pub offset: Option<i64>,
    pub title: Option<String>,
}

impl QueryParams for TodoQueryParams {
    fn limit(&self) -> i64 {
        self.limit.or(DEFAULT_LIMIT).unwrap_or_default()
    }
    fn offset(&self) -> i64 {
        self.offset.or(DEFAULT_OFFSET).unwrap_or_default()
    }
}

#[async_trait]
pub trait TodoRepository: Send + Sync {
    async fn create(&self, new_todo: &CreateTodo) -> RepositoryResult<Todo>;
    async fn list(&self, params: TodoQueryParams) -> RepositoryResult<ResultPaging<Todo>>;
    async fn get(&self, todo_id: i32) -> RepositoryResult<Todo>;
    async fn delete(&self, todo_id: i32) -> RepositoryResult<()>;
}
// Can be placed under /src/infrastructure/repositories/todo.rs
pub struct TodoDieselRepository {
    pub pool: Arc<DBConn>
}

impl TodoDieselRepository {
    pub fn new(db: Arc<DBConn>) -> Self {
        TodoDieselRepository { pool: db }
    }
}

#[async_trait]
impl TodoRepository for TodoDieselRepository {

    async fn create(&self, new_todo: &CreateTodo) -> RepositoryResult<Todo> {
        use crate::infrastructure::schema::todos::dsl::todos;
        let new_todo_diesel: CreateTodoDiesel = CreateTodoDiesel::from(new_todo.clone());
        let mut conn = self.pool.get().unwrap();
        let result: TodoDiesel = run(move || diesel::insert_into(todos).values(new_todo_diesel)
            .get_result(&mut conn))
            .await
            .map_err(|v| DieselRepositoryError::from(v).into_inner())?;
        Ok(result.into())
    }

    async fn list(&self, params: TodoQueryParams) -> RepositoryResult<ResultPaging<Todo>> {
        use crate::infrastructure::schema::todos::dsl::todos;
        let pool = self.pool.clone();
        let builder = todos.limit(params.limit()).offset(params.offset());
        let result = run(move || {
            let mut conn = pool.get().unwrap();
            builder.load::<TodoDiesel>(&mut conn)
        })
            .await
            .map_err(|v| DieselRepositoryError::from(v).into_inner())?;
        Ok(ResultPaging {
            total: 0,
            items: result.into_iter().map(|v| v.into()).collect()
        })
    }

    async fn get(&self, todo_id: i32) -> RepositoryResult<Todo> {
        use crate::infrastructure::schema::todos::dsl::{id, todos};
        let mut conn = self.pool.get().unwrap();
        run(move || todos.filter(id.eq(todo_id)).first::<TodoDiesel>(&mut conn))
            .await
            .map_err(|v| DieselRepositoryError::from(v).into_inner())
            .map(|v| -> Todo { v.into() })
    }

    async fn delete(&self, todo_id: i32) -> RepositoryResult<()> {
        use crate::infrastructure::schema::todos::dsl::{id, todos};
        let mut conn = self.pool.get().unwrap();
        run(move || diesel::delete(todos).filter(id.eq(todo_id))
            .execute(&mut conn))
            .await
            .map_err(|v| DieselRepositoryError::from(v).into_inner())?;
        Ok(())
    }
}

Services

The onion architecture is best being used with a repository-service pattern. An example service can be seen below:

// Can be placed under /src/services/todo.rs
#[derive(Clone)]
pub struct TodoServiceImpl {
    pub repository: Arc<dyn TodoRepository>,
}

impl TodoServiceImpl {
    pub fn new(repository: Arc<dyn TodoRepository>) -> Self {
        TodoServiceImpl {
            repository,
        }
    }
}

#[async_trait]
impl TodoService for TodoServiceImpl {
    async fn create(&self, todo: CreateTodo) -> Result<Todo, CommonError> {
        let mut cloned = todo.clone();
        self.repository
            .create(&mut cloned)
            .await
            .map_err(|e| -> CommonError { e.into() })
    }

    async fn list(&self, params: TodoQueryParams) -> Result<ResultPaging<Todo>, CommonError> {
        self.repository
            .list(params)
            .await
            .map_err(|e| -> CommonError { e.into() })
    }

    async fn get(&self, todo_id: i32) -> Result<Todo, CommonError> {
        self.repository
            .get(todo_id)
            .await
            .map_err(|e| -> CommonError { e.into() })
    }

    async fn delete(&self, todo_id: i32) -> Result<(), CommonError> {
        self.repository
            .delete(todo_id)
            .await
            .map_err(|e| -> CommonError { e.into() })
    }
}
You might also like...
Basic Actix + Diesel + Postgres REST API

Actix-Crud Basic Actix + Diesel + Postgres REST API Setup Install and setup PostgreSQL Set DATABASE_URL environment variable or specify in .env file g

Fahrenheit-celsius converter using actix

fahrenheit-celsius-converter Simple http Fahrenheit/Celsius/Kelvin converter using actix-web. Note This is a toy project, not yet finished. It's not r

Source Code for 'Practical Rust Web Projects' by Shing Lyu

Apress Source Code This repository accompanies Practical Rust Web Projects by Shing Lyu (Apress, 2021). Download the files as a zip using the green bu

handle some lichess.org/tournament load with rust, while learning rust

lila-http Take some of the HTTP load away from lila. WIP! Arena tournaments Clients connected to a tournament page request new data about the tourname

Simple http server in Rust (Windows/Mac/Linux)
Simple http server in Rust (Windows/Mac/Linux)

How it looks like? Screenshot Command Line Arguments Simple HTTP(s) Server 0.6.1 USAGE: simple-http-server [FLAGS] [OPTIONS] [--] [root] FLAGS:

Rust / Hasura / GraphQL

Rust + Hasura This is an example of a Rust server that functions as a remote schema for Hasura. It demonstrates: user login + signup JWT authorization

An HTTP library for Rust

hyper A fast and correct HTTP implementation for Rust. HTTP/1 and HTTP/2 Asynchronous design Leading in performance Tested and correct Extensive produ

JSON Web Token implementation in Rust.

Frank JWT Implementation of JSON Web Tokens in Rust. Algorithms and features supported HS256 HS384 HS512 RS256 RS384 RS512 ES256 ES384 ES512 Sign Veri

Rust templating with Handlebars

handlebars-rust Handlebars templating language implemented in Rust and for Rust. Handlebars-rust is the template engine that renders the official Rust

Owner
Microsoft
Open source projects and samples from Microsoft
Microsoft
A starter template for actix-web projects that feels very Django-esque. Avoid the boring stuff and move faster.

Jelly A.K.A, the actix-web starter you probably wish you had. This is provided as-is, and anyone is free to extend it or rework it as they desire - ju

SecretKeys 198 Dec 15, 2022
Static Web Server - a very small and fast production-ready web server suitable to serve static web files or assets

Static Web Server (or SWS abbreviated) is a very small and fast production-ready web server suitable to serve static web files or assets.

Jose Quintana 496 Jan 2, 2023
Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

Actix Web Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust Features Supports HTTP/1.x and HTTP/2 Streaming and pipelining

Actix 16.3k Jan 8, 2023
Example Blog using Rust, Actix Web, HTMX, Mustache

Actix Blog An example blog built with Actix. It uses htmx and handlebar templates. Running To run the blog, you need to have a recent version of Rust

Dru Jensen 2 Nov 11, 2022
Actix-web wrapper for garde, a Rust validation library.

Garde-actix-web   Actix-web wrapper for garde, a Rust validation library. Installation Usage example Feature flags About us Installation [dependencies

Netwo 5 Sep 8, 2023
Extension for actix-web to validate user permissions

actix-web-grants Extension for actix-web to validate user permissions. To check user access to specific services, you can use built-in proc-macro, Per

Artem Medvedev 114 Dec 22, 2022
Add Facebook and Google authentication to your HTTP REST API in Actix-web

I created this project while learning Rust. Project shows how to handle Facebook and Google token verification in Rust using Actix-Web. Hope this help

null 37 Dec 31, 2022
Example Actix 2.x REST application implementing many features

Rust/Actix Example An Actix 2.0 REST server using the Rust language. Motivation Actix Web is a fast, powerful web framework for building web applicati

David D. 238 Dec 31, 2022
In-progress extractors and middleware for Actix Web

actix-web-lab Experimental extractors, middleware, and other extras for possible inclusion in Actix Web. Things To Know About This Crate It will never

Rob Ede 51 Dec 20, 2022
Easy to use multipart forms for actix-web

Actix Easy Multipart Easy to use Multipart Forms for actix-web. File uploads are written to disk as temporary files similar to the way the $_FILES var

Jacob Halsey 17 Jan 3, 2023