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:
pip install fastapi uvicorn python-multipart
Step 2: Create a Simple Form Handler
Here's a basic example of handling form data in FastAPI:
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:
- We import the
Form
class from FastAPI - We define parameters with type annotations and set them to be
Form()
parameters - 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:
<!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:
curl -X POST "http://localhost:8000/login/" -d "username=johndoe&password=secret"
The response should be:
{
"username": "johndoe"
}
Form Validation with Pydantic
FastAPI's integration with Pydantic allows for sophisticated form validation. Let's see how to validate form data:
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:
- Create a class to handle form data
- Perform validation on the submitted data
- 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:
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:
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:
<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:
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:
- A complete contact form with HTML
- Form validation for email format and message length
- Error handling with appropriate status codes and messages
- 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:
-
Always validate user input: Use Pydantic's validation or write your own validation logic.
-
Use appropriate status codes: Return 400 for client errors, 200 or 201 for successful operations.
-
Include CSRF protection: For production applications, implement CSRF protection to prevent cross-site request forgery attacks.
-
Limit file upload sizes: When handling file uploads, set reasonable limits to prevent abuse.
-
Provide meaningful error messages: Help users understand what went wrong when form submission fails.
-
Use Depends for complex form handling: The
Depends
feature allows for cleaner code with complex form processing logic. -
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
- FastAPI Official Documentation on Forms
- Pydantic Documentation for more validation options
- HTML Forms Reference on MDN
Exercises
- Create a user registration form that validates password strength.
- Build a form that accepts multiple file uploads with descriptions for each file.
- Implement a form with dependent fields (e.g., if a user selects "Other" from a dropdown, show an additional text field).
- Create a multi-step form that saves state between submissions.
- 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! :)