├── 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 | --------------------------------------------------------------------------------