src | ||
.gitignore | ||
Cargo.lock | ||
Cargo.toml | ||
questions.json | ||
README.md |
David Westgate
Rust Axum Question and Answer Server
Background
This is a simple Rust Web Axum server, so far serving a handful of REST API endpoints as the basis of a "Q&A" or question and answer application. The APIs supported offer basic CRUD functionality. Specifically, this example mirrors that which is found in the early chapters of the Rust Web Development textbook by Bastian Gruber (except using Axum and not Warp)
Contents
I'll do my best to keep these contents up-to-date as changes are made to the source code
- Setup
- Dependency overview
- Source overview
- Looking ahead
Setup
Rustup
First install Rust. I suggest using the rustup toolchain
Build and Run
cargo run
Dependency overview
std
We utilize std library crates for file input/output, hash maps, and network sockets
Axum
Axum is the rust web framework doing much of the heavy lifting of this application. It is used here for its core functionality of routing, and abstracting http concepts like methods, request/response handling, and status codes.
Tokio
The tokio framework allows rust to operate asynchoronously. By axums asychonous web nature, it is required by axum.
Serde/serde_json
A useful package for serializing and deserializing data (specifically json), used in tandem with file operations.
Source overview
src/main.rs
Our applications main entry point, here we declare the use of the applications external packages for easier management.
main
is then responsible for creating the new question store
(behind the necessary tokio RwLock
). main
creates the axum router, and specifies all of the routes our application supports by pointing them to handlers in the src/api.rs
file via the appropriate http methods, before serving the axum service
src/api.rs
Five handler are defined here for our five API endpoints, supporting the basic CRUD operations regarding questions. Care was taken to handle most common possible error cases, such as attempting to get a question by an id which does not exist, or attempting to create a question with the same ID as one that already exists.
Some minor derivations were taken here from the exact routes specified in the text
- GET /question/:id is included to return a specific question by id
- GET /questions is programmed to return all questions (soon to support pagination)
- PUT /question does not include the question ID in its path param, but rather just the body
- DELETE /question/:id returns in its body the deleted content
src/question.rs
This is the definition of our struct Question
. An interesting potential for confilct arose here, as our question struct is supposed to keep track of the question id in addition of the in-memory hashmap of our store (next section). This two-sources-of-truth situation would lead to various error cases that we would need to handle, and would cause concerns with scalability.
To address this, I seperated the Question 'entity' from the Question 'DTO' (data transfer object) from an ORM philosophy. The result is that the Question entity does not contain a direct reference to the question ID, as it is assumed while we are in program space, the store's hashmap will manage this. to_entity
and to_dto
function implementions have been provided to make this easier to work with.
src/store.rs
The store is responsible for the management of the questions. It does this by
- Loading the questions from the
questions.json
(if it exists, or creating it if not) - Creating an in-memory hashmap of the jokes
- Providing public functions to create, retrieve, update or delete questions
- Writing any mutating changes out to
questions.json
- Handling possible unpermitted operations by returning errors
- Handling possible file or I/O errors with some sense of grace before panicing
Looking ahead
These are a few things still to be added
Higher priority
- Support pagination (via query parameters) for GET /questions. Omission of the query parameters would default in page 0 and a page size of ~10
- Support POST /answers
- API documentation tooling (
utoipa
) - API testing tooling (
swagger
) - Coded API tests with mock data
- Specific defined Error types for common errors
- Additional comments (rustdoc)
- Serve basic web page(s) to utilize all APIs
Lesser priority
- Optimize flush/file writing: Write out the JSON in a pretty structure and avoid re-writing the whole file in cases when it can be avoid (like adding 1 item)
- Use a database alltogether instead of a json file for persistance
- Re-structure the application to serve API endpoint from an
api
directory, and generically serve web content from aweb
directory - Optimize the put/update handler to support path or body identification, and to update only individual fields passed.
- Host application on my raspberry pi on the internet