Browsers have support for client side validation of form data built in. We can use this along with server side validation to give the user a nice experience and ensure security on the back end.

Browser Validation

In the following form we use a pattern and a required attribute. The browser will now block form submission until the field is filled in with Banana or Cherry.

  <label for="choose">Would you prefer a banana or a cherry?</label>
  <input id="choose" name="i_like" required pattern="[Bb]anana|[Cc]herry">

Handling form submission

Axum has support for Handlers. We can use those in a lot of different ways and one way is for form implementations. The example below shows how we use a struct to handle the form data passed in to the accept_form function.

use axum::{
use serde::Deserialize;

struct FruitPreferences {
    i_like: String,

async fn accept_form(form: Form<FruitPreferences>) {
    let fruit_preferences: FruitPreferences = form.0;

    // ...

let app = Router::new().route("/sign_up", post(accept_form));

Handling validation server side

We can use Validator which will allow us to add validation to the struct that we use to process form requests.

In your app/Cargo.toml add the following

validator = { version = "0", features = ["derive"] }

And then we can change our FruitPreferences to

#[derive(Deserialize, Validate)]
struct FruitPreferences {
    #[validate(length(min = 1, message = "The i_like field is mandatory"))]
    i_like: String,

Then we can use logic like

if form.validate().is_ok() {

There are many validations that can be performed with this pattern.