Use the axum::extract::Form extractor with the form_urlencoded crate to automatically parse application/x-www-form-urlencoded data into a strongly typed struct. For multipart form data (file uploads), use axum::extract::Multipart to stream parts individually, as the full payload cannot be loaded into memory at once.
For standard text forms, define a struct matching your input fields and derive Deserialize. Axum handles the parsing and validation automatically, returning a 400 error if the data is malformed.
use axum::{
extract::Form,
response::Html,
routing::post,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct LoginInput {
username: String,
password: String,
}
async fn handle_login(Form(input): Form<LoginInput>) -> Html<String> {
// Access fields directly: input.username, input.password
Html(format!("Hello, {}!", input.username))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/login", post(handle_login));
// Run server...
}
For file uploads, you must handle the Multipart stream manually. Iterate over the stream, check the content type, and save the file part to disk using tokio::fs.
use axum::{
extract::Multipart,
response::Html,
routing::post,
Router,
};
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
async fn handle_upload(mut multipart: Multipart) -> Html<String> {
let mut filename = String::new();
while let Some(field) = multipart.next_field().await.unwrap() {
let data = field.bytes().await.unwrap();
let name = field.content_disposition().unwrap().get_filename().unwrap_or("file.txt");
filename = name.to_string();
let mut file = File::create(name).await.unwrap();
file.write_all(&data).await.unwrap();
}
Html(format!("Uploaded: {}", filename))
}
Remember to add form_urlencoded = "1.0" to your Cargo.toml if you are using Form, and ensure your HTML form uses enctype="multipart/form-data" for file uploads. Always validate input lengths and types in production to prevent buffer overflows or logic errors.