Getting Started with Axum | Rust
Tags: rust, backend, webdev, axum
Published: 2022-07-20 23:41:00

I'm a newbie in Rust and I was looking for a web framework to use and build a server or an API, and I found Axum on Github, and I want to start to use it. I wanted to learn about Axum, so I started to write this article while exploring it to solidify some of its concepts and features.

DISCLAIMER: This is not a comprehensive guide about Axum, if you want to know every feature and how to use them, here is the [documentation](https://docs.rs/axum/latest/axum/).

In this article, we just going to use the `get()` and `post()` methods and serve an HTML file.

> According to its documentation:
> axum is a web application framework that focuses on ergonomics and modularity.

> High level features

 > - Route requests to handlers with a macro-free API.
 > - Declaratively parse requests using extractors.
 > - Simple and predictable error handling model.
 > - Generate responses with minimal boilerplate.
 > - Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities.

> In particular the last point is what sets axum apart from existing frameworks. axum doesn't have its own middleware system but instead uses tower::Service. This means axum gets timeouts, tracing, compression, authorization, and more, for free. It also enables you to share middleware with applications written using hyper or tonic.

Let's start importing our dependencies.

Cargo.toml

```

[dependencies]
axum = "0.5.11"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1.35"
tracing-subscriber = "0.3.14"
serde = { version = "1.0.138", features = ["derive"] }
serde_json = "1.0"
```

main.rs

```
use axum::{
    routing::{get, post},
    http::StatusCode,
    response::IntoResponse,
    Json, Router};

use std::net::SocketAddr;
use serde::{Deserialize, Serialize};

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn root() -> &'static str {
    "Hello, World!"
}

```
Now let's talk about the code above. First, we import what we are going to use. And write `#[tokio::main]` above our main function to be allowed to use async. Then, we create an instance of Router and call the `route` method with the path and the service that will be called if the path matches, in this case, `root`, and it is wrapped in the method `get`.

Then we use `SocketAddr` to define the port we will use, and pass it the localhost IP address and a port number, in this case, '3000'.

We use the `Server` and pass the reference of `addr` to the `bind` function

According to its doc:

> The Server is the main way to start listening for HTTP requests. It wraps a listener with a MakeService, and then should be executed to start serving requests.

We pass `app` as an argument to the `serve` method but we need to use the `into_make_service` method because `serve` receives `make_service` as a parameter and `app` is a router instance.

Here is what its doc says about `into_make_service`:

> pub fn into_make_service(self) -> IntoMakeService< Self >

> Convert this router into a MakeService, which is a Service whose response is another service.

> This is useful when running your application with hyper’s Server

And `MakeService` :

> Creates new Service values.

> Acts as a service factory. This is useful for cases where new Service values must be produced. One case is a TCP server listener. The listener accepts new TCP streams, obtains a new Service value using the MakeService trait, and uses that new Service value to process inbound requests on that new TCP stream.

> This is essentially a trait alias for a Service of Services.

Here is the [link](https://docs.rs/tower/0.4.13/tower/make/trait.MakeService.html) if you want to know more about the MakeService trait.

Now we run the code
```
cargo run
```
It should print "listening on 127.0.0.1:3000" in our console, and if we copy the number and paste it into our browser we should see this page:


![hello_world_image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8twvoo61wotuixzo15y7.png)

### Extract a parameter from the path

Axum has many extractors, see the docs [here](https://docs.rs/axum/latest/axum/extract/index.html)

In this example, we will use `Path` to extract a name from the path and use it to send a JSON message.

```
...
use axum::extract::Path;  
...

async fn json_hello(Path(name): Path<String>) -> impl IntoResponse {
    let greeting = name.as_str();
    let hello = String::from("Hello ");

    (StatusCode::OK, Json(json!({"message": hello + greeting })))
}

```

In the code block above, we use `impl IntoResponse` as the return value of the function `json_hello()`. According to the documentation:

> Anything that implements IntoResponse can be returned from handlers. You generally shouldn’t have to implement IntoResponse manually, as axum provides implementations for many common types.

If you want to implement a custom error type, here is the [documentation](https://docs.rs/axum/latest/axum/response/trait.IntoResponse.html).

Now, let's update our `main()` function.

```
#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/hello/:name", get(json_hello));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

```

If we write in our browser `localhost:3000/hello/Carlos`, it will show this:

![Extracting parameter image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9qv1id8vqnx1igincu16.png)

Now, let's use the `post()` method, to create a user. 

```
...

#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

#[derive(Debug, Serialize,Deserialize, Clone, Eq, Hash, PartialEq)]
struct User {
    id: u64,
    username: String,
}
...

async fn create_user(Json(payload): Json<CreateUser>,) -> impl IntoResponse {
    let user = User {
        id: 1337,
        username: payload.username
    };

    (StatusCode::CREATED, Json(user))
}

```
In the code above, we create two structs: CreateUser and User. We use the JSON extractor, to extract the payload, that is the data from a JSON, pass it as a value to the field `username`, and return the `user` variable as a JSON.

We add `post()` route to `main()` function.

```
...

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/hello/:name", get(json_hello))
        .route("/user", post(create_user);

...

```
![post method image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hdxjr1lqsf22qxvpngi2.png)

### Serving Files
To serve files we have to add `tower-http` to our project's dependencies.

Cargo.toml 

```
...

[dependencies]
...
tower-http = { version = "0.3.0", features = ["fs", "trace"] }

```
We create a directory and create an HTML file in it.

```
src
static/
    hello.html
Cargo.toml

```
hello.html

```
<h1>Hello everyone</h1>
<h2>This is a static file</h2>

```

main.rs

Now, we update our `main()` function to add the route that serves our HTML file.


```
...

#[tokio::main]
async fn main() {

...
.route("/hello/:name", get(json_hello))
.route("/static", get_service(ServeFile::new("static/hello.html"))
        .handle_error(|error: io::Error| async move {
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Unhandled internal error: {}", error),
            )
        }));

...

}

```
Run the code and go to `localhost:3000/static` , we should see this page:


![html page image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/axhi9fmg70ilqzq0aodf.png)
 
### Complete code

main.rs

```
use axum::{
    routing::{get, post, get_service},
    http::StatusCode,
    response::IntoResponse,
    Json, Router};

use axum::extract::Path;
use tower_http::services::ServeFile;

use std::net::SocketAddr;
use serde::{Deserialize, Serialize};
use serde_json::{json};

use std::{io};



#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

#[derive(Debug, Serialize,Deserialize, Clone, Eq, Hash, PartialEq)]
struct User {
    id: u64,
    username: String,
}

#[tokio::main]
async fn main() {

    tracing_subscriber::fmt::init();
    let app = Router::new()
        .route("/", get(root))
        .route("/user", post(create_user))
        .route("/hello/:name", get(json_hello))
        .route("/static", get_service(ServeFile::new("static/hello.html"))
            .handle_error(|error: io::Error| async move {
                (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    format!("Unhandled internal error: {}", error),
                )
            }));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    tracing::info!("listening on {}", addr);
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();


}

async fn root() -> &'static str {
    "Hello, World!"
}

async fn create_user(Json(payload): Json<CreateUser>) -> impl IntoResponse {
    let user = User {
        id: 1337,
        username: payload.username
    };

    (StatusCode::CREATED, Json(user))
}


async fn json_hello(Path(name): Path<String>) -> impl IntoResponse {
    let greeting = name.as_str();
    let hello = String::from("Hello ");

    (StatusCode::OK, Json(json!({"message": hello + greeting })))
}

```

### Conclusion

Axum is the first web framework I try in Rust, and I like it. The documentation is well written and has many examples on its Github page. One of the aspects a liked about it is its Macro-free API, another aspect is the possibility to chain the routes, so I can wrap all my handlers in the same code block.

The only thing that is stopping me to use it frequently and share more aspects of it is my lack of knowledge in Rust, but it is up to me to be more disciplined and I will because I want to contribute to this project.

Axum has a discord channel, the community is really great and helpful, here is the [link](https://discord.gg/tokio).

Thank you for taking your time and read this article.

If you have any recommendations, advice, tips about how to improve my code, my English, or anything; please leave a comment or contact me through [Twitter](https://twitter.com/Carlos_marcv) or [LinkedIn](https://www.linkedin.com/in/carlos-marcano-a2135a134/).

The source code is [here](https://github.com/carlosm27/blog/tree/main/axum_getting_started).

### Reference

[Examples](https://github.com/tokio-rs/axum/tree/main/examples)

[Axum documentation](https://docs.rs/axum/latest/axum/)

[Tower_http documentation](https://docs.rs/tower-http/0.3.4/tower_http/)

[Hyper documentation](https://docs.rs/hyper/0.14.20/hyper/)