├── static
├── assets
│ ├── demo.css
│ └── tokio.jpg
└── index.html
├── justfile
├── .dockerignore
├── index.http
├── Cargo.toml
├── Dockerfile
├── .gitignore
├── src
└── main.rs
└── README.md
/static/assets/demo.css:
--------------------------------------------------------------------------------
1 | .demo {
2 |
3 | }
4 |
--------------------------------------------------------------------------------
/justfile:
--------------------------------------------------------------------------------
1 | image-build:
2 | docker build -t axum-demo .
3 |
--------------------------------------------------------------------------------
/static/assets/tokio.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/linux-china/axum-demo/HEAD/static/assets/tokio.jpg
--------------------------------------------------------------------------------
/static/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 |
8 | Index
9 |
10 |
11 | Welcome
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | ### Rust template
2 | # Generated by Cargo
3 | # will have compiled files and executables
4 | debug/
5 | target/
6 |
7 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
8 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
9 | Cargo.lock
10 |
11 | # These are backup files generated by rustfmt
12 | **/*.rs.bk
13 |
14 | .idea/
15 |
--------------------------------------------------------------------------------
/index.http:
--------------------------------------------------------------------------------
1 | ### Welcome page
2 | GET http://localhost:3000/
3 |
4 | ### json output
5 | GET http://localhost:3000/json
6 |
7 | ### user login form
8 | POST http://localhost:3000/login
9 | Content-Type: application/x-www-form-urlencoded
10 |
11 | username=leijuan&password=123456
12 |
13 | ### user json
14 | GET http://localhost:3000/user/1
15 |
16 | ### problem detail test
17 | GET http://localhost:3000/user/0
18 |
19 | ### Save user
20 | POST http://localhost:3000/user/save
21 | Content-Type: application/json
22 |
23 | {
24 | "id": 111,
25 | "name": "Tevita Solares"
26 | }
27 |
28 | ### search
29 | GET http://localhost:3000/search?q=java
30 |
31 | ### static html
32 | GET http://localhost:3000/index.html
33 |
34 | ### static css
35 | GET http://localhost:3000/assets/demo.css
36 |
37 | ### 404 handler
38 | GET http://localhost:3000/demo404
39 |
40 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "axum-demo"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | http = "1"
10 | http-body = "1"
11 | futures = "0.3"
12 | tokio = { version = "1", features = ["full"] }
13 | tokio-util = "0.7"
14 | serde = { version = "1.0", features = ["derive"] }
15 | async-trait = "0.1"
16 | serde_json = "1.0"
17 | hyper = { version = "1.5", features = ["full"] }
18 | tower = { version = "0.5", features = ["full"] }
19 | tower-http = { version = "0.6", features = ["full"] }
20 | axum = { version = "0.8", features = ["default", "ws", "multipart"] }
21 | axum-extra = "0.10"
22 | include_dir = "0.7"
23 | axum-ctx = "0.4"
24 |
25 | [profile.dev]
26 | opt-level = 1
27 |
28 | [profile.dev.package."*"]
29 | opt-level = 3
30 |
31 | [profile.release]
32 | strip = true
33 | lto = true
34 | opt-level = "z"
35 | codegen-units = 1
36 |
37 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build Stage
2 | FROM rust:1.83.0 as builder
3 |
4 | RUN USER=root cargo new --bin axum-demo
5 | WORKDIR ./axum-demo
6 | COPY ./Cargo.toml ./Cargo.toml
7 | # Build empty app with downloaded dependencies to produce a stable image layer for next build
8 | RUN cargo build --release
9 |
10 | # Build web app with own code
11 | RUN rm src/*.rs
12 | ADD . ./
13 | RUN rm ./target/release/deps/axum_demo*
14 | RUN cargo build --release
15 |
16 |
17 | FROM debian:buster-slim
18 | ARG APP=/usr/src/app
19 |
20 | RUN apt-get update \
21 | && apt-get install -y ca-certificates tzdata \
22 | && rm -rf /var/lib/apt/lists/*
23 |
24 | EXPOSE 3000
25 |
26 | ENV TZ=Etc/UTC \
27 | APP_USER=appuser
28 |
29 | RUN groupadd $APP_USER \
30 | && useradd -g $APP_USER $APP_USER \
31 | && mkdir -p ${APP}
32 |
33 | COPY --from=builder /axum-demo/target/release/axum-demo ${APP}/axum-demo
34 | COPY --from=builder /axum-demo/static ${APP}/static
35 |
36 | RUN chown -R $APP_USER:$APP_USER ${APP}
37 |
38 | USER $APP_USER
39 | WORKDIR ${APP}
40 |
41 | CMD ["./axum-demo"]
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/rust,jetbrains+all
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=rust,jetbrains+all
3 |
4 | ### JetBrains+all ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | # User-specific stuff
9 | .idea/**/workspace.xml
10 | .idea/**/tasks.xml
11 | .idea/**/usage.statistics.xml
12 | .idea/**/dictionaries
13 | .idea/**/shelf
14 |
15 | # AWS User-specific
16 | .idea/**/aws.xml
17 |
18 | # Generated files
19 | .idea/**/contentModel.xml
20 |
21 | # Sensitive or high-churn files
22 | .idea/**/dataSources/
23 | .idea/**/dataSources.ids
24 | .idea/**/dataSources.local.xml
25 | .idea/**/sqlDataSources.xml
26 | .idea/**/dynamic.xml
27 | .idea/**/uiDesigner.xml
28 | .idea/**/dbnavigator.xml
29 |
30 | # Gradle
31 | .idea/**/gradle.xml
32 | .idea/**/libraries
33 |
34 | # Gradle and Maven with auto-import
35 | # When using Gradle or Maven with auto-import, you should exclude module files,
36 | # since they will be recreated, and may cause churn. Uncomment if using
37 | # auto-import.
38 | # .idea/artifacts
39 | # .idea/compiler.xml
40 | # .idea/jarRepositories.xml
41 | # .idea/modules.xml
42 | # .idea/*.iml
43 | # .idea/modules
44 | # *.iml
45 | # *.ipr
46 |
47 | # CMake
48 | cmake-build-*/
49 |
50 | # Mongo Explorer plugin
51 | .idea/**/mongoSettings.xml
52 |
53 | # File-based project format
54 | *.iws
55 |
56 | # IntelliJ
57 | out/
58 |
59 | # mpeltonen/sbt-idea plugin
60 | .idea_modules/
61 |
62 | # JIRA plugin
63 | atlassian-ide-plugin.xml
64 |
65 | # Cursive Clojure plugin
66 | .idea/replstate.xml
67 |
68 | # Crashlytics plugin (for Android Studio and IntelliJ)
69 | com_crashlytics_export_strings.xml
70 | crashlytics.properties
71 | crashlytics-build.properties
72 | fabric.properties
73 |
74 | # Editor-based Rest Client
75 | .idea/httpRequests
76 |
77 | # Android studio 3.1+ serialized cache file
78 | .idea/caches/build_file_checksums.ser
79 |
80 | ### JetBrains+all Patch ###
81 | # Ignores the whole .idea folder and all .iml files
82 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
83 |
84 | .idea/
85 |
86 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
87 |
88 | *.iml
89 | modules.xml
90 | .idea/misc.xml
91 | *.ipr
92 |
93 | # Sonarlint plugin
94 | .idea/sonarlint
95 |
96 | ### Rust ###
97 | # Generated by Cargo
98 | # will have compiled files and executables
99 | debug/
100 | target/
101 |
102 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
103 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
104 | Cargo.lock
105 |
106 | # These are backup files generated by rustfmt
107 | **/*.rs.bk
108 |
109 | # MSVC Windows builds of rustc generate these, which store debugging information
110 | *.pdb
111 |
112 | # End of https://www.toptal.com/developers/gitignore/api/rust,jetbrains+all
113 |
114 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use axum::response::Response;
2 | use axum::{
3 | extract,
4 | response::{Html, IntoResponse, Json},
5 | routing::{get, post},
6 | Router,
7 | };
8 | use http::StatusCode;
9 | use serde::{Deserialize, Serialize};
10 | use serde_json::{json, Value};
11 | use tower_http::services::ServeDir;
12 |
13 | #[tokio::main]
14 | async fn main() {
15 | let app = Router::new()
16 | .nest_service("/assets", ServeDir::new("./static/assets"))
17 | .route("/", get(index))
18 | .route("/index.html", get(index))
19 | .route("/html", get(html))
20 | .route("/login", post(login))
21 | .route("/user/{id}", get(user))
22 | .route("/user/save", post(save_user))
23 | .route("/search", get(search))
24 | .route("/json", get(json));
25 |
26 | let app = app.fallback(handler_404);
27 | println!("Http Server started on 0.0.0.0:3000");
28 | let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
29 | axum::serve(listener, app).await.unwrap();
30 | }
31 |
32 | async fn index() -> Html<&'static str> {
33 | Html(include_str!("../static/index.html"))
34 | }
35 |
36 | // `Html` gives a content-type of `text/html`
37 | async fn html() -> Html<&'static str> {
38 | Html("Hello, World!
")
39 | }
40 |
41 | async fn save_user(extract::Json(user): extract::Json) -> Json {
42 | println!("name: {}", user.name);
43 | Json(json!(true))
44 | }
45 |
46 | #[derive(Deserialize)]
47 | struct LoginForm {
48 | username: String,
49 | password: String,
50 | }
51 |
52 | async fn login(extract::Form(form): extract::Form) -> Html {
53 | println!("username: {}, password: {}", form.username, form.password);
54 | Html(format!("Hello, {}!
", &form.username))
55 | }
56 |
57 | #[derive(Serialize, Deserialize, Debug)]
58 | pub struct Person {
59 | pub id: u32,
60 | pub name: String,
61 | }
62 |
63 | #[derive(Serialize, Deserialize, Debug)]
64 | pub struct ProblemDetail {
65 | pub status: u16,
66 | pub title: String,
67 | #[serde(rename = "type")]
68 | pub error_type: String,
69 | pub detail: String,
70 | pub instance: String,
71 | }
72 |
73 | impl IntoResponse for ProblemDetail {
74 | fn into_response(self) -> Response {
75 | let body: String = json!(self).to_string();
76 | Response::builder()
77 | .status(self.status)
78 | .header("Content-Type", "application/json")
79 | .body(body)
80 | .unwrap()
81 | .into_response()
82 | }
83 | }
84 |
85 | async fn json() -> Json {
86 | Json(json!({ "data": 42 }))
87 | }
88 |
89 | async fn user(extract::Path((id,)): extract::Path<(u32,)>) -> Result, ProblemDetail> {
90 | if id == 0 {
91 | Err(ProblemDetail {
92 | status: 500,
93 | title: "user id not correct".to_owned(),
94 | error_type: "illegal data".to_owned(),
95 | detail: "0 as user id not allowed".to_owned(),
96 | instance: format!("/user/{}", id),
97 | })
98 | } else {
99 | Ok(Json(Person {
100 | id,
101 | name: "linux_china".to_string(),
102 | }))
103 | }
104 | }
105 |
106 | #[derive(Deserialize)]
107 | struct SearchQuery {
108 | page: Option,
109 | q: Option,
110 | }
111 |
112 | async fn search(extract::Query(query): extract::Query) -> Html {
113 | let q = query.q.unwrap_or("".to_string());
114 | print!("q: {}, page: {}", q, query.page.unwrap_or(0));
115 | Html(format!("Hello, {}!
", q))
116 | }
117 |
118 | async fn handler_404() -> impl IntoResponse {
119 | (StatusCode::NOT_FOUND, "nothing to see here")
120 | }
121 |
122 | async fn handle_error(_err: std::io::Error) -> impl IntoResponse {
123 | (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong...")
124 | }
125 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Axum demo
2 | ============
3 |
4 | Axum: ergonomic and modular web framework built with Tokio, Tower, and Hyper.
5 |
6 | * Route requests to handlers with a macro free API.
7 | * Declaratively parse requests using extractors.
8 | * Simple and predictable error handling model.
9 | * Generate responses with minimal boilerplate.
10 | * Take full advantage of the tower and tower-http ecosystem of middleware, services, and utilities.
11 |
12 | Hyper: A fast and correct HTTP implementation for Rust.
13 |
14 | * HTTP/1 and HTTP/2
15 | * Asynchronous design
16 | * Leading in performance
17 | * Tested and correct
18 | * Extensive production use
19 | * Client and Server APIs
20 |
21 | tower-http: a collection of HTTP specific middleware and utilities built with Tower's Service trait.
22 |
23 | * Trace: Easily add high level tracing/logging to your application. Supports determining success or failure via status codes as well as gRPC specific headers. Has great defaults but also supports deep customization.
24 | * Compression and Decompression: Automatically compress or decompress response bodies. This goes really well with serving static files using ServeDir.
25 | * FollowRedirect: Automatically follow redirection responses.
26 |
27 | http: a general purpose library of common HTTP types, for examples `http::{Request, Response, StatusCode}`
28 |
29 | # Vocabulary
30 |
31 | * Handler: an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response.
32 | * Routing: between handlers and request paths
33 | * Extractor: a type that implements FromRequest. Extractors are how you pick apart the incoming request to get the parts your handler needs.
34 | * Building responses: anything that implements IntoResponse can be returned from a handler
35 | * Middleware: used to decorate the application, providing additional functionality
36 |
37 | # Demo cases
38 |
39 | * static assets handle
40 | * index page
41 | * form submit: login
42 | * json submit: REST API
43 | * json output: struct and json!() macro
44 | * query: `/search?q=java`
45 | * Path variables/params `/user/:id`
46 | * 404 handler: Separate net("/") and handler_404()
47 |
48 | # Docker support
49 | Refer from [Packaging a Rust web service using Docker](https://blog.logrocket.com/packaging-a-rust-web-service-using-docker/)
50 |
51 | ```bash
52 | docker build -t axum-demo .
53 | docker run -p 3000:3000 axum-demo
54 | ```
55 |
56 | Or you can refer https://kerkour.com/blog/rust-small-docker-image/
57 |
58 | # Axum Extension
59 |
60 | Community Projects: https://github.com/tokio-rs/axum/blob/main/ECOSYSTEM.md
61 |
62 | * axum-htmx: https://github.com/robertwayne/axum-htmx
63 |
64 | # References
65 |
66 | * Axum home: https://github.com/tokio-rs/axum
67 | * Axum examples: https://github.com/tokio-rs/axum/tree/main/examples
68 | * axum-prometheus: Prometheus metrics middleware for Axum https://github.com/ptrskay3/axum-prometheus
69 | * Replacing nginx with axum: https://felix-knorr.net/posts/2024-10-13-replacing-nginx-with-axum.html
70 | * Rust Web Programming - Third Edition: https://learning.oreilly.com/library/view/-/9781835887769/
71 | * Getting Started with Axum - Rust's Most Popular Web Framework: https://www.shuttle.rs/blog/2023/12/06/using-axum-rust
72 | * Introduction to Axum: https://www.youtube.com/playlist?list=PLrmY5pVcnuE-_CP7XZ_44HN-mDrLQV4nS
73 | * Announcing Axum: https://tokio.rs/blog/2021-07-announcing-axum
74 | * Using Rust with Axum for error handling: https://blog.logrocket.com/rust-axum-error-handling/
75 | * How to use “Type safe routing” of axum: https://medium.com/mixi-developers/how-to-use-type-safe-routing-of-axum-c06c1b1b1ab
76 | * How to deploy a Rust Web Server to Heroku using Axum, Docker, and Github Actions: https://fbzga.medium.com/how-to-deploy-a-rust-web-server-to-heroku-using-axum-docker-and-github-actions-6cddb442ea7e
77 | * Deploy a Rust web server to Heroku with axum, Tokio, and GitHub Actions: https://blog.logrocket.com/deploy-rust-web-server-heroku-axum-tokio-github-actions/
78 | * Learning by doing: An HTTP API with Rust: https://blog.frankel.ch/http-api-rust/
79 | * Building a Proxy Server in Rust with Axum: https://medium.com/dev-genius/building-a-proxy-server-in-rust-with-axum-4d1e0215a6b0
80 | * Writing a Rest HTTP Service with Axum: https://docs.shuttle.rs/tutorials/rest-http-service-with-axum
81 | * API Evolution in axum: https://www.youtube.com/watch?v=w1atdqNsA80
82 | * Bundle frontend into axum binary using include_dir: https://dev.to/konstantin/bundle-frontend-with-axum-build-using-includedir-g8i
83 | * Extracting Generic Substates in Axum: https://mhu.dev/posts/2024-07-02-axum-generic-substate/
84 | * Authentication with Axum: https://mattrighetti.com/2025/05/03/authentication-with-axum
85 | * Axum: Optimizing web API design with the Builder Pattern - https://medium.com/@adefemiadeoye/axum-optimizing-web-api-design-with-the-builder-pattern-08aa8e18a599
86 | * Rust-Powered APIs with Axum: A Complete 2025 Guide: https://medium.com/rustaceans/rust-powered-apis-with-axum-a-complete-2025-guide-213a28bb44ac
87 | * Axum Backend Series: Docker, Database and Connection Pooling: https://blog.0xshadow.dev/posts/backend-engineering-with-axum/axum-database-setup-using-docker/
88 | * Axum Backend Series: Models, Migration, DTOs and Repository Pattern: https://blog.0xshadow.dev/posts/backend-engineering-with-axum/axum-model-setup/
89 | * Axum Backend Series: Implement JWT Access Token: https://blog.0xshadow.dev/posts/backend-engineering-with-axum/axum-jwt-access-token/
90 | * Creating a REST API in Rust: https://arshsharma.com/posts/rust-api/
91 | * axum-gate: Fully customizable role based JWT cookie/bearer auth for axum https://github.com/emirror-de/axum-gate
92 | * axum streams for Rust: https://github.com/abdolence/axum-streams-rs
93 | * Using Axum Framework To Create REST API:
94 | - https://medium.com/intelliconnect-engineering/using-axum-framework-to-create-rest-api-part-1-7d434d2c5de4
95 | - https://medium.com/intelliconnect-engineering/using-axum-framework-to-create-rest-api-part-ii-4eba129c196b
96 |
--------------------------------------------------------------------------------