/rust-actix-starter

A production-quality starter app using Actix 1.x

Primary LanguageRustMIT LicenseMIT

Rust/Actix Starter Kit

Build Status

A production-quality starter app using Actix 1.x. Includes tests and coverage. Diesel was intentionally omitted to add in flexibility for usage. A full framework that includes Diesel is being developed at https://github.com/ddimaria/rust-actix-framework.

Motivation

I needed a quick way to bootstrap an Actix repo for various projects that had all of the baseline, production-ready features baked in.

Features

  • Actix 1.x HTTP Server
  • Filesystem organized for scale
  • .env for local development
  • Lazy Static Config struct
  • Built-in Healthcheck (includes cargo version info)
  • Listeners configured for TDD
  • Custom Errors and HTTP Payload/Json Validation
  • Unit and Integration Tests
  • Test Coverage Reports
  • Dockerfile for Running the Server in a Container
  • TravisCI Integration

Packages

  • actix-web: Actix Web Server
  • derive_more: Error Formatting
  • dotenv: Configuration Loader (.env)
  • envy: Deserializes Environment Variables into a Config Struct
  • listenfd: Listens for Filesystem Changes
  • validator: Validates incoming Json
  • kcov: Coverage Analysis

Installation

Clone the repo and cd into the repo:

git clone https://github.com/ddimaria/rust-actix-starter.git
cd rust-actix-starter

Copy over the example .env file:

cp .env.example .env

Running the Server

To startup the server:

cargo run

Autoreloading

To startup the server and autoreload on code changes:

systemfd --no-pid -s http::3000 -- cargo watch -x run

Tests

Integration tests are in the /src/tests folder. There are helper functions to make testing the API straightforward. For example, if we want to test the GET /api/v1/user route:

  use crate::tests::helpers::tests::assert_get;

  #[test]
  fn test_get_users() {
      assert_get("/api/v1/user");
  }

Using the Actix test server, the request is sent and the response is asserted for a successful response:

assert!(response.status().is_success());

Similarly, to test a POST route:

use crate::handlers::user::CreateUserRequest;
use crate::tests::helpers::tests::assert_post;

#[test]
fn test_create_user() {
    let params = CreateUserRequest {
        first_name: "Satoshi".into(),
        last_name: "Nakamoto".into(),
        email: "satoshi@nakamotoinstitute.org".into(),
    };
    assert_post("/api/v1/user", params);
}

Running Tests

To run all of the tests:

cargo test

Test Covearage

I created a repo on DockerHub that I'll update with each Rust version (starting at 1.37), whose tags will match the Rust version.

In the root of the project:

docker run -it --rm --security-opt seccomp=unconfined --volume "${PWD}":/volume --workdir /volume ddimaria/rust-kcov:1.37 --exclude-pattern=/.cargo,/usr/lib,/src/main.rs,src/server.rs

note: converage takes a long time to run (up to 30 mins).

You can view the HTML output of the report at target/cov/index.html

Docker

To build a Docker image of the application:

docker build -t rust_actix_starter .

Once the image is built, you can run the container in port 3000:

docker run -it --rm --env-file=.env.docker -p 3000:3000 --name rust_actix_starter rust_actix_starter

Endpoints

Healthcheck

Determine if the system is healthy.

GET /health

Response

{
  "status": "ok",
  "version": "0.1.0"
}

Example:

curl -X GET http://127.0.0.1:3000/health

Get All Users

GET /api/v1/user

Response

[
  {
    "id": "a421a56e-8652-4da6-90ee-59dfebb9d1b4",
    "first_name": "Satoshi",
    "last_name": "Nakamoto",
    "email": "satoshi@nakamotoinstitute.org"
  },
  {
    "id": "c63d285b-7794-4419-bfb7-86d7bb3ff17d",
    "first_name": "Barbara",
    "last_name": "Liskov",
    "email": "bliskov@substitution.org"
  }
]

Example:

curl -X GET http://127.0.0.1:3000/api/v1/user

Get a User

GET /api/v1/user/{id}

Request

Param Type Description
id Uuid The user's id

Response

{
  "id": "a421a56e-8652-4da6-90ee-59dfebb9d1b4",
  "first_name": "Satoshi",
  "last_name": "Nakamoto",
  "email": "satoshi@nakamotoinstitute.org"
}

Example:

curl -X GET http://127.0.0.1:3000/api/v1/user/a421a56e-8652-4da6-90ee-59dfebb9d1b4

Response - Not Found

404 Not Found

{
  "errors": ["User c63d285b-7794-4419-bfb7-86d7bb3ff17a not found"]
}

Create a User

POST /api/v1/user

Request

Param Type Description Required Validations
first_name String The user's first name yes at least 3 characters
last_name String The user's last name yes at least 3 characters
email String The user's email address yes valid email address
{
  "first_name": "Linus",
  "last_name": "Torvalds",
  "email": "torvalds@transmeta.com"
}

Response

{
  "id": "0c419802-d1ef-47d6-b8fa-c886a23d61a7",
  "first_name": "Linus",
  "last_name": "Torvalds",
  "email": "torvalds@transmeta.com"
}

Example:

curl -X POST \
  http://127.0.0.1:3000/api/v1/user \
  -H 'Content-Type: application/json' \
  -d '{
    "first_name": "Linus",
    "last_name": "Torvalds",
    "email": "torvalds@transmeta.com"
}'

Response - Validation Errors

422 Unprocessable Entity

{
  "errors": [
    "first_name is required and must be at least 3 characters",
    "last_name is required and must be at least 3 characters",
    "email must be a valid email"
  ]
}

License

This project is licensed under: