- Purpose
- Features
- Quickstart
- Goals
- Included Components
- Incomplete Planned Features
- Not Included
- Decisions
Educational resource for coworkers looking to explore REST development with Rust. Quite opinionated.
The project itself provides an extremely simplified, API-only URL shortener with various sub-optimal choices and in no way represents a production-ready way to build such a service. It’s basically just enough to show how to wire up a database query to satisfy a request.
This document will generally not go into much project-agnostic detail about Rust practices and conventions.
- Read/write unauthenticated API endpoint to POST full URLs to and receive a shortened `hash` back
- Configurable via TOML and/or environment variables
- Can be run in a container via Docker Compose, along with a suite of observability tools around it
- Task automation with Just
- Database migrations via sqlx
This section still needs drastic improvement for audiences who are unfamiliar with any combination of Rust, Rustup, Nix, Cargo, etc.
Non-Nix:
rustup toolchain install stable
cargo install --locked sqlx-cli
# i.e. postgresql://$USER:postgres@localhost/axum_rest_example_dev
export DATABASE_URL=...
sqlx -d $DATABASE_URL database create
sqlx -d $DATABASE_URL migrate run
cargo clippy --lib
cargo build
cargo test
Hot iteration loop using cargo-watch
cargo watch -x 'clippy --lib' -x 'test --lib' -x run
Requires a recent Nix (2.7+) with Flakes support, plus nix-direnv.
direnv allow .
Requires a recent Nix (2.7+) with Flakes support.
nix develop
export DATABASE_URL=...
Install Rustup, then:
cargo install --locked cargo-watch just sqlx-cli
export DATABASE_URL=...
You’ll also need to install clang
and lld
for the faster linking experience.
Native, with just
# i.e. postgresql://$USER:postgres@localhost/axum_rest_example_dev
export DATABASE_URL=...
just migrate run
Native, no just
# i.e. postgresql://$USER:postgres@localhost/axum_rest_example_dev
export DATABASE_URL=...
sqlx -d $DATABASE_URL database create
sqlx -d $DATABASE_URL migrate run
cargo run
Using httpie:
http post :8080/v1/link destination=https://www.google.com/
HTTP/1.1 201 Created
content-length: 104
content-type: application/json
date: Fri, 10 Sep 2021 15:38:53 GMT
{
"destination": "https://www.google.com/",
"hash": "ghMW5",
"id": "c92ead3b-f319-44e5-9764-6b12dffb5a46"
}
http get :8080/ghMW5
HTTP/1.1 307 Temporary Redirect
content-length: 0
date: Fri, 10 Sep 2021 15:39:18 GMT
location: https://www.google.com/
- [ ] Demonstrate expressivity of Rust’s stdlib patterns such as Result/Option/enums/pattern-matching
- [ ] Demonstrate utility of
thiserror
/anyhow
for domain errors - [ ] Demonstrate utility of
serde
for handling structural issues with incoming payloads - [ ] Demonstrate viability of Rust for backend service development
- [X] Don’t depend on beta releases of libraries to be able to compile with the latest Tokio
- [X] Comprehensive use of async
- [ ] Framework-level conventions and configurability
- [ ] High-quality observability
- [X] Don’t shy away from intermediate or advanced Rust if it’s needed
- [X] Don’t shy away from community tooling that would be commonly used by an experienced practitioner
Nix Flake
Direnv config with Nix Flake support
- Uses cargo-chef to produce cache-friendly layers containing just your dependencies
- This README
- Rustdoc
To view local code-specific documentation, you may use:
cargo doc --features otel --document-private-items --open
- Prometheus-based technical and domain-specific metrics
- Grafana dashboards and documentation
- Documentation and automation improvements for guest contributors who are Nix-averse
- Non-trivial authentication
- Any kind of ORM or database abstraction beyond
sqlx
I find Tokio a technically more compelling suite of libraries, as well as
finding its community extremely welcoming, curious, and active. async-std
simply doesn’t seem to have the community or velocity I’m looking for, and I
find a bifurcation of the Rust community along tribal lines to be extremely
frustrating as a developer. You can’t use libraries written for one runtime in a
project that uses another without wastefully running threads for both and
needing translation layers anywhere they might need to directly interact. I’ve
never once found an async-std
-derived project compelling enough to look past
the division it creates. I’m very confident that by comparison, fewer projects
built with Tokio will have reason to regret their choice in 2-5 years.
I’ve found that many many projects and examples default to env_logger
as an
easy logging solution, but tracing
is incredibly robust when you outgrow those
features, and has been adopted by core community crates as well as part of the
Rust toolchain itself.
Despite my background with Ruby’s Rack and Elixir’s Plug, I personally find
Warp’s compiler messages to be quite opaque and struggled to be successful with
it. Until axum
became available, this was still the best choice to be on a
relatively pure Tokio + Hyper stack.
In order to build with a modern stable release of Tokio, you need to run beta versions of Actix-Web 4.0.x, and this has been the case for essentially all of 2021. They’re possibly getting close, but I more or less have stopped caring. Has pretty good docs and features, but also has a huge dependency footprint too. Finally, I’m not able to totally put all of the controversy around gaming the TechEmpower benchmarks behind me, which left a very bad taste in my mouth even as a bystander.
Until very recently you flat out couldn’t use Rocket without nightly Rust, and if you stick to stable versions from Crates.io this continues to be the case. Also irregularly gives surprisingly poor results in comparative benchmarks, which I’m not willing to trade for decent ergonomics.
sqlx
is emphatically not an ORM, so you write raw SQL with occasional
SQL-compatible annotations for type hinting at the boundary between Rust and
SQL. Using its `query!` and `query_as!` family of macros, it allows compile-time
checking of query semantics, i.e. have you written something that PostgreSQL
understands to be a viable query given your current database schema? Uses pure
SQL for DDL migrations.
Diesel appears to have a lot going for it ergnomically, but it is both not async and somewhere between apathetic and actively disinterested in being converted to async. That’s a total non-starter for me given that I am able to avoid such compromises anywhere else in the web development stack used for this project.