Getting Started
To get started, you’ll want to initialise a Shuttle project with Axum (you can find more about getting started here).Routing
In Axum, routers can be made by writingRouter::new()
to create it and then adding a route with the route
method by chaining it. Below is an example that returns a router that has one route at the base that will return “Hello World!” if we go to localhost:8000
.
impl IntoResponse
type from Axum, which allows us to return a tuple of things that can convert into a HTTP response.
json!()
macro, or adding a derive macro to a struct - you can find more about this here.
We can also attach things like a request body type which we need to define as a type or a struct that can be (de)serialized to/from JSON via serde
. Here’s an example that takes a JSON request with a message field and spits it back out to the client that made the request as well as an OK status code indicating it was successful:
serde::Deserialize
and serde::Serialize
through the use of derive macros (a form of powerful Rust metaprogramming which you can find more about here. This then converts the received JSON into the struct.
We can also utilise dynamic routing by using the Path type, allowing us to use things like record IDs or article slugs as a variable in our function, as shown below.
/:id
) and then visit localhost:8000/32
(for example - assuming the server is run at port 8000), it should return this:
.nest()
to nest the deeper-level router first, and then we can use .route()
to add our higher-level routes.
Using State
State in Axum is a way to share scoped app-wide variables over your Router - this is great for us as we can store things like a database connection pool, a hashmap key-value store or an API key for an external service inside. Basic usage of a state struct might look like this:with_state
function. We can also use FromRef<T>
to generate a subset of our original app state so that we can avoid having to share all of our variables everywhere:
Static Files
Let’s say you want to serve some static files using Axum - or that you have an application made using a frontend JavaScript framework like React, and you want to combine it with your Axum backend to make one large application instead of having to host your frontend and backend separately. How would you do that? Axum does not by itself have capabilities to be able to do this; however, what it does have is super-strong compatibility withtower-http
, which offers utility for serving your own static files whether you’re running a SPA, statically-generated files from a framework like Next.js or simply just raw HTML, CSS and JavaScript.
If you’re using static-generated files, you can easily slip this into your router (assuming your static files are in a ‘dist’ folder at the root of your project):
Tower
, which is the underlying crate for tower-http
. We can build a fallback service that uses this:
Cookies
Cookies are essentially a way to store data on the user side. They have a variety of uses for web development; we can use them for tracking metrics for targeted advertising, we can use them for storing player score data and more. Cookies are also used for application authentication; however, it should be said that although this is a valid use case, no user information like username or passwords should be stored through cookies - only session IDs that get validated against a database. Cookies in Axum can be easily handled through use of axum-extra’s cookiejar types. There are three types, a private cookiejar (where it uses private signing), a signed cookiejar where all cookies are signed publicly and then a regular cookiejar. You will need to enable the relevant flag to be able to use cookies, but you can enable the private cookiejar (for example) by running the following command:CookieBuilder
struct from the cookie
crate, re-imported to axum-extra
(requires the ‘cookie’ flag enabled). Let’s see what building this might look like:
Cookie::new
!
Deleting a cookie is also pretty much the same as adding a cookie; to be able to pass the changes properly, the deletion of the cookie needs to be specified in the return otherwise it will not delete properly:
Middleware
Middleware is essentially a function that runs before the client hits an endpoint; it can be used for things like adding a wait time to prevent server overload, to validating user sessions from cookies. Fortunately, middleware in Axum is quite simple to use! There’s two ways to be able to create middleware in Axum, but we will be focusing on the more simple way which is to just write a function that uses Axum’s middleware utilities as parameters, then simply just adding it to our router and declaring it as middleware. We can also add middleware that uses state this way too, which is great as it means we can share a database connection pool (for example) in the middleware:layer
method is a very versatile method that we can use to layer multiple things - a CORS layer (which will be discussed later), middleware as well as tracing. what if we want to implement a middleware that has state? Thankfully, Axum has an aptly named from_fn_with_state
method that we can use instead of from fn
, like so:
with_state
method if your middleware function requires state to access things like a database connection pool.
You can read more about writing middleware with Axum here.
CORS
CORS is a mechanism designed to allow servers to serve content to a domain where the content does not originate from (for instance: one website to another website). Despite CORS protecting legitimate sites from being interacted with by malicious websites written by bad actors, it’s still considered to be something that can be quite tricky to set up. Like with middleware, although Axum does not deal with CORS by itself, it uses theCorsLayer
type from tower-http
to assist with us being able to quickly set up CORS quickly and efficiently so that we don’t have to do everything ourselves. Let’s see what that would look like:
CorsLayer
that accepts GET and POST HTTP request methods from any origin. If we try to put in a DELETE request anywhere, CORS will automatically deny the request as it fails to meet CORS. We can also add an origin URL by using a string literal then parsing and unwrapping it in the allow_origin
method. However, if you’re using an environmental variable to store it then you can also just parse it as a http::HeaderValue
type like so:
Deployment
With Shuttle, you can deploy your app withshuttle deploy
and then it’ll deploy your app to a live URL (assuming there’s no errors). No Docker containerisation required!
If you’re migrating to Shuttle, you need to wrap your entry point function with the Shuttle runtime macro and then add relevant code annotations, and it’ll work without needing to do anything else. You can read more about this here.