This codebase was created to demonstrate a fully fledged fullstack application named Conduit (Medium-like) built with Actix Web (4.0) including CRUD operations, authentication, routing, pagination, and more.
For more information on how to this works with other frontends/backends, head over to the RealWorld repo.
- SQLx, 🧰 The Rust SQL Toolkit
- PostgreSQL, The World's Most Advanced Open Source Relational Database
- Docker 🐳
- GitHub Actions ⚙️
This project is a partial implementation of the RealWorld spec.
I aimed to use as much as good practices as I could from the Rust community. I also extensively used functional and unit testing in order to have a great code coverage.
GitHub Actions is used to test the whole project, audit security of supply chain and continuously deliver containers.
What is implemented is the following:
- JWT Authentication
- Users API (Registration, Login, Update)
- Profiles API (Get, Follow, Unfollow)
- Articles API (List, Feed, Comments, Tags...)
Your first need to install the SQLx CLI:
cargo install sqlx-cli --no-default-features --features rustls,postgres
You need PostgreSQL running somewhere, and to run migrations on it. To run on localhost, Docker is the preferred way:
docker run \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=password \
-e POSTGRES_DB=conduit \
-p 5432:5432 \
-d postgres:14 \
postgres -N 1000
sqlx database create
sqlx migrate run
💡 Tip: For faster deployment you can use the dev_env.sh script.
Finally you can run the API on port 8080:
cargo run
And in another terminal (or in Postman):
curl -i -X POST http://127.0.0.1:8080/api/users \
-H "Content-Type: application/json" \
--data '{"user":{"username":"john","email":"john.doe@github.com","password":"test1234"}}'
Make sure a database a Postgres database is running on localhost.
Just run:
cargo test
Running with Docker Compose is fairly simple but not very flexible during development (requires to build the API image for each change of the source code).
Just go into the docker
folder, and run the stack:
cd docker/
docker compose up -d
The API will be exposed on port 8080, you can run your own tests with tools such as curl
or Postman.
The config
crate is used to provide a convenient layered configuration system (satisfies the third of the 12-factor).
The config files are written in YAML and are placed under the configuration
folder in the same directory as the app.
You can see an example of layers with the base.yml
file which contains a default listening port and credentials for the database, and the local.yml
and production.yml
that overrides some settings from the base as well as adding others (such as the listening address).
Config values can also be overriden with environment variables (highest precedence) following this naming convention: CONDUIT__<settings-category>__<setting>
.
Example: override the DB hostname with CONDUIT__DATABASE__HOST
.
Almost all API endpoints require authentication (see RealWorld backend specs) with a JWT token.
This implementation thus makes use of the jsonwebtoken
crate coupled with a custom authentication middleware that wraps endpoints which need authentication and validate (or reject) requests before they arrive to the endpoint (adding authenticated user information on top of the request).
Please take a look at the auth.rs
source file if you want to know more about the implementation of the middleware, code is documented.
The source code of this implementation resides in the src
directory:
src
├── domain
│ └── ...
├── dtos
│ └── ...
├── handlers
│ └── ...
├── middlewares
│ ├── auth.rs
│ └── mod.rs
├── repositories
│ └── ...
├── configuration.rs
├── lib.rs
├── main.rs
└── startup.rs
The conduit
library is exposed through the lib.rs
file.
The main.rs
source file use the configuration
and startup
modules to respectively configure and run the application on the desired listening address and port.
The middlewares
module contains middlewares such as the Authentication middleware described above.
The repositories
module contains exposed functions doing SQL queries to the database.
The dtos
module contains Data Transfer Objects (DTOs) for defining input and output types (as struct
) of the API.
The domain
module contains functions and modules dealing with business logic (input validation, JWT tokens...) for the API.
Finally, the handlers
module contains functions and modules for the handlers: the functions mapped to API endpoints. These handlers use the dtos
, domain
and repositories
modules for their internal logic.
Under the tests
folder, you will find all the functional tests of the application, this means API tests.
Especially, the reqwest
, claim
and tokio
crates are used for HTTP requests, assert functions and background tasks (e.g.: running a test server) respectively.
Before each test, a database with a random name is created, SQLx migrations are runned against it and an API server is launched in background on a random port (this is called a TestApp
within the code). The details are in the helpers.rs
source file.
- Zero To Production In Rust and A learning journal from Luca Palmieri, really helped me to catch good practices on Rust Web dev tooling (such as Actix, SQLx...).
- Demystifying Actix Web Middleware - Daniel Imfeld, helped me to write the Authentication middleware with the last version of Actix (v4).
- Actix website and its crate documentation.