Skip to main content

FastAPI Request Forms

Introduction

Forms are a fundamental component of web applications, allowing users to submit data to your application. Whether it's login credentials, user registration information, or complex data with file uploads, handling form data properly is essential for building interactive web applications.

FastAPI provides powerful tools for handling form data, integrating with its validation system and Pydantic models. In this guide, we'll explore how to accept, validate, and process form submissions in your FastAPI applications.

Prerequisites

Before we dive in, make sure you have:

  • Basic knowledge of FastAPI
  • Python 3.7+
  • FastAPI installed (pip install fastapi)
  • Uvicorn installed (pip install uvicorn)
  • Python-multipart installed (pip install python-multipart)

The last library is particularly important because FastAPI needs it to process form data.

Understanding Form Data

Form data is typically submitted from HTML forms using either the application/x-www-form-urlencoded or multipart/form-data content types. The latter is used when forms include file uploads.

Unlike JSON data which FastAPI handles automatically, form data requires special handling. Let's get started with the basics.

Basic Form Handling

Step 1: Install Required Dependencies

First, ensure you have the necessary dependencies:

bash
pip install fastapi uvicorn python-multipart

Step 2: Create a Simple Form Handler

Here's a basic example of handling form data in FastAPI:

python
from fastapi import FastAPI, Form
import uvicorn

app = FastAPI()

@app.post("/login/")
async def login(username: str = Form(), password: str = Form()):
return {"username": username}

In this example:

  1. We import the Form class from FastAPI
  2. We define parameters with type annotations and set them to be Form() parameters
  3. FastAPI will expect form data instead of JSON for these parameters

Step 3: Test the Form Endpoint

You can test this endpoint using an HTML form:

html
<!DOCTYPE html>
<html>
<head>
<title>Login Form</title>
</head>
<body>
<form action="http://localhost:8000/login/" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username"><br><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password"><br><br>
<input type="submit" value="Login">
</form>
</body>
</html>

Or you can test it using tools like Postman or curl:

bash
curl -X POST "http://localhost:8000/login/" -d "username=johndoe&password=secret"

The response should be:

json
{
"username": "johndoe"
}

Form Validation with Pydantic

FastAPI's integration with Pydantic allows for sophisticated form validation. Let's see how to validate form data:

python
from fastapi import FastAPI, Form, HTTPException
from pydantic import BaseModel, EmailStr, validator
import uvicorn

app = FastAPI()

class UserRegistration:
def __init__(
self,
username: str = Form(...),
email: str = Form(...),
password: str = Form(...),
confirm_password: str = Form(...)
):
self.username = username
self.email = email
self.password = password
self.confirm_password = confirm_password

# Validate passwords match
if password != confirm_password:
raise HTTPException(status_code=400, detail="Passwords don't match")

# Could add more validations here

@app.post("/register/")
async def register_user(user_data: UserRegistration):
# Process the registration
return {
"username": user_data.username,
"email": user_data.email,
"message": "Registration successful"
}

In this example, we:

  1. Create a class to handle form data
  2. Perform validation on the submitted data
  3. Return a response based on the validated data

Handling File Uploads with Forms

Forms often include file uploads. Here's how to handle them in FastAPI:

python
from fastapi import FastAPI, File, Form, UploadFile
import uvicorn

app = FastAPI()

@app.post("/upload/")
async def upload_file(
file: UploadFile = File(...),
description: str = Form(...)
):
contents = await file.read()

# Here you would typically save the file or process it

return {
"filename": file.filename,
"content_type": file.content_type,
"description": description,
"file_size": len(contents)
}

This endpoint accepts a file upload and a text description. The UploadFile class provides methods to work with the uploaded file.

Multiple Form Fields with the Same Name

Sometimes you need to handle multiple values for the same form field. FastAPI supports this with a list parameter:

python
from fastapi import FastAPI, Form
from typing import List
import uvicorn

app = FastAPI()

@app.post("/interests/")
async def submit_interests(interests: List[str] = Form(...)):
return {"interests": interests}

The HTML form would look like:

html
<form action="http://localhost:8000/interests/" method="post">
<input type="checkbox" name="interests" value="sports"> Sports<br>
<input type="checkbox" name="interests" value="music"> Music<br>
<input type="checkbox" name="interests" value="reading"> Reading<br>
<input type="submit" value="Submit">
</form>

Real-World Example: Contact Form with Validation

Let's create a more complete example of a contact form with validation:

python
from fastapi import FastAPI, Form, HTTPException, Depends
from fastapi.responses import HTMLResponse
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
import uvicorn

app = FastAPI()

class ContactForm:
def __init__(
self,
name: str = Form(...),
email: str = Form(...),
subject: str = Form(...),
message: str = Form(...)
):
self.name = name
self.email = email
self.subject = subject
self.message = message

# Validate email format
if '@' not in email or '.' not in email:
raise HTTPException(status_code=400, detail="Invalid email format")

# Validate message length
if len(message) < 10:
raise HTTPException(status_code=400, detail="Message too short")

@app.post("/contact/")
async def contact(form_data: ContactForm = Depends()):
# Here you would typically send an email or store the contact submission
print(f"Received contact from {form_data.name} ({form_data.email})")
print(f"Subject: {form_data.subject}")
print(f"Message: {form_data.message}")

return {
"status": "success",
"message": "Thank you for your message. We'll be in touch soon!"
}

@app.get("/", response_class=HTMLResponse)
async def get_form():
return """
<!DOCTYPE html>
<html>
<head>
<title>Contact Form</title>
<style>
body { font-family: Arial, sans-serif; max-width: 500px; margin: 0 auto; padding: 20px; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input, textarea { width: 100%; padding: 8px; box-sizing: border-box; }
button { background: #4CAF50; color: white; padding: 10px 15px; border: none; cursor: pointer; }
button:hover { background: #45a049; }
</style>
</head>
<body>
<h1>Contact Us</h1>
<form action="/contact/" method="post">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="subject">Subject:</label>
<input type="text" id="subject" name="subject" required>
</div>
<div class="form-group">
<label for="message">Message:</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">Send Message</button>
</form>
</body>
</html>
"""

This example provides:

  1. A complete contact form with HTML
  2. Form validation for email format and message length
  3. Error handling with appropriate status codes and messages
  4. A realistic workflow showing how to process the form data

Best Practices for Form Handling

When working with forms in FastAPI, keep these best practices in mind:

  1. Always validate user input: Use Pydantic's validation or write your own validation logic.

  2. Use appropriate status codes: Return 400 for client errors, 200 or 201 for successful operations.

  3. Include CSRF protection: For production applications, implement CSRF protection to prevent cross-site request forgery attacks.

  4. Limit file upload sizes: When handling file uploads, set reasonable limits to prevent abuse.

  5. Provide meaningful error messages: Help users understand what went wrong when form submission fails.

  6. Use Depends for complex form handling: The Depends feature allows for cleaner code with complex form processing logic.

  7. Sanitize data: Always clean and sanitize form data to prevent security issues like XSS attacks.

Common Issues and Solutions

Issue: Form submission returns "422 Unprocessable Entity"

Solution: Ensure you have python-multipart installed and you're using the correct content type (multipart/form-data or application/x-www-form-urlencoded) in your request.

Issue: File uploads are too slow

Solution: Consider using streaming for large files or implementing chunked uploads.

Issue: Form fields with special characters don't work

Solution: Ensure you're handling URL encoding/decoding properly.

Summary

In this tutorial, we've learned:

  • How to handle basic form submissions in FastAPI
  • How to validate form data using Pydantic and custom logic
  • How to process file uploads along with form data
  • How to handle multiple values for the same form field
  • Real-world examples for practical application
  • Best practices for form handling in production applications

Forms are a crucial part of web applications, and FastAPI provides powerful tools to handle them efficiently and safely. By leveraging FastAPI's form handling capabilities, you can create robust applications that process user input reliably.

Additional Resources

Exercises

  1. Create a user registration form that validates password strength.
  2. Build a form that accepts multiple file uploads with descriptions for each file.
  3. Implement a form with dependent fields (e.g., if a user selects "Other" from a dropdown, show an additional text field).
  4. Create a multi-step form that saves state between submissions.
  5. Build a form that processes and validates CSV file uploads, checking the structure of the uploaded file.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)