Skip to main content

FastAPI File Response

In web development, there are many scenarios where you need to return files to clients - downloading generated reports, serving images, providing access to documents, and more. FastAPI makes this easy with its dedicated FileResponse class.

Introduction to FileResponse

FileResponse is a specialized response type in FastAPI that allows you to return file content directly to the client. It handles all the necessary HTTP headers and streaming to efficiently deliver files of any size. This is particularly useful when you need to:

  • Provide downloadable files to users
  • Serve static content like images or documents
  • Generate and return dynamic files like PDFs or Excel spreadsheets
  • Stream large files without loading them entirely into memory

Basic Usage of FileResponse

To use FileResponse, you first need to import it from FastAPI:

python
from fastapi import FastAPI
from fastapi.responses import FileResponse

app = FastAPI()

Let's start with a simple example that returns a text file:

python
@app.get("/get-text-file")
async def get_text_file():
file_path = "files/sample.txt"
return FileResponse(file_path)

In this example:

  • We specify the path to our file (files/sample.txt)
  • FastAPI automatically:
    • Determines the content type (MIME type) based on the file extension
    • Sets appropriate HTTP headers
    • Streams the file content to the client

Customizing FileResponse

FileResponse offers several parameters to customize how files are delivered:

python
@app.get("/download-report")
async def get_report():
report_path = "reports/quarterly_report.pdf"

return FileResponse(
path=report_path,
filename="Q3_Financial_Report.pdf", # Custom filename for download
media_type="application/pdf", # Explicitly set media type
headers={"Content-Disposition": "attachment"} # Force download
)

Key Parameters:

  • path: Path to the file on your server (required)
  • filename: The name that will be suggested when downloading
  • media_type: MIME type for the file (if not specified, it's guessed from the extension)
  • headers: Additional HTTP headers to include

Handling Different File Types

FastAPI's FileResponse works with any file type. Here are a few common examples:

Image Files

python
@app.get("/profile-image/{user_id}")
async def get_profile_image(user_id: int):
image_path = f"users/{user_id}/profile.jpg"
return FileResponse(image_path)

PDF Documents

python
@app.get("/invoice/{invoice_id}")
async def get_invoice(invoice_id: str):
pdf_path = f"invoices/{invoice_id}.pdf"
return FileResponse(
path=pdf_path,
media_type="application/pdf",
filename=f"Invoice-{invoice_id}.pdf"
)

CSV Data Files

python
@app.get("/export-data")
async def export_data():
csv_path = "exports/data.csv"
return FileResponse(
path=csv_path,
media_type="text/csv",
filename="exported_data.csv",
headers={"Content-Disposition": "attachment; filename=exported_data.csv"}
)

Conditional File Responses

Sometimes you might need to return a file only under certain conditions. Here's how you can handle such cases:

python
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
import os

app = FastAPI()

@app.get("/document/{doc_id}")
async def get_document(doc_id: str):
file_path = f"documents/{doc_id}.pdf"

if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="Document not found")

return FileResponse(path=file_path)

Generating Dynamic Files

You can combine FastAPI with libraries that generate files on the fly:

python
from fastapi import FastAPI
from fastapi.responses import FileResponse
import pandas as pd
import tempfile
import os

app = FastAPI()

@app.get("/generate-excel-report")
async def generate_excel_report():
# Create sample data
data = {
"Name": ["Alice", "Bob", "Charlie"],
"Age": [25, 30, 35],
"City": ["New York", "Paris", "Tokyo"]
}

# Create a pandas DataFrame
df = pd.DataFrame(data)

# Create a temporary file
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp:
temp_path = tmp.name

# Save DataFrame to Excel
df.to_excel(temp_path, index=False)

# Return the Excel file and delete it after the response is sent
return FileResponse(
path=temp_path,
filename="user_report.xlsx",
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
background=BackgroundTask(lambda: os.unlink(temp_path)) # Delete file after sending
)

For the above example to work, you'll need to import BackgroundTask:

python
from fastapi.background import BackgroundTask

Streaming Large Files

One of the benefits of FileResponse is that it streams files in chunks rather than loading them entirely into memory. This is efficient for large files:

python
@app.get("/download-large-video")
async def download_large_video():
video_path = "videos/tutorial.mp4"

return FileResponse(
path=video_path,
media_type="video/mp4",
filename="programming-tutorial.mp4"
)

FastAPI will handle the streaming automatically, making it suitable for files of any size.

Real-World Example: File Download API

Let's create a more comprehensive example of a file download API:

python
from fastapi import FastAPI, HTTPException, Query, Depends
from fastapi.responses import FileResponse
from typing import Optional
import os
from datetime import datetime

app = FastAPI()

# Simple user authentication (in a real app, use proper authentication)
def get_current_user(api_key: str = Query(...)):
if api_key != "secret_key":
raise HTTPException(status_code=401, detail="Invalid API key")
return {"username": "demo_user"}

# Download endpoint with tracking
@app.get("/download/{file_type}/{file_id}")
async def download_file(
file_type: str,
file_id: str,
user: dict = Depends(get_current_user)
):
# Map file types to directories and extensions
file_types = {
"report": {"dir": "reports", "ext": "pdf"},
"image": {"dir": "images", "ext": "jpg"},
"document": {"dir": "documents", "ext": "docx"},
}

if file_type not in file_types:
raise HTTPException(status_code=400, detail="Invalid file type")

# Construct file path
file_info = file_types[file_type]
file_path = f"{file_info['dir']}/{file_id}.{file_info['ext']}"

# Check if file exists
if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="File not found")

# Log download (in a real app, store this in a database)
print(f"User {user['username']} downloaded {file_path} at {datetime.now()}")

# Return the file with appropriate naming
return FileResponse(
path=file_path,
filename=f"{file_type}-{file_id}.{file_info['ext']}",
headers={"Content-Disposition": "attachment"}
)

This example demonstrates:

  • Authentication using dependencies
  • Path parameters to identify files
  • Error handling for missing files
  • Download logging
  • Proper content disposition for downloads

Common Pitfalls and Solutions

1. File Not Found Errors

Always check if a file exists before attempting to return it:

python
import os
from fastapi import HTTPException

@app.get("/file/{filename}")
async def get_file(filename: str):
file_path = f"files/{filename}"

if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="File not found")

return FileResponse(file_path)

2. Security Concerns

Be careful about allowing users to specify file paths directly:

python
# BAD - Security vulnerability (path traversal)
@app.get("/unsafe-file")
async def get_unsafe_file(filepath: str):
return FileResponse(filepath) # DON'T DO THIS!

# GOOD - Validate and restrict file access
@app.get("/safe-file/{filename}")
async def get_safe_file(filename: str):
# Validate filename to prevent directory traversal
if ".." in filename or filename.startswith("/"):
raise HTTPException(status_code=400, detail="Invalid filename")

safe_path = os.path.join("safe_files", filename)
if not os.path.isfile(safe_path):
raise HTTPException(status_code=404, detail="File not found")

return FileResponse(safe_path)

3. Memory Usage for Large Files

While FileResponse streams files efficiently, generating large files in memory before sending them can be problematic:

python
# Better approach for large generated files
@app.get("/large-report")
async def get_large_report():
# Generate file directly to disk instead of in memory
output_path = "temp/large_report.csv"

# Call function that writes to file instead of returning data
generate_large_report_to_file(output_path)

return FileResponse(
path=output_path,
filename="large_report.csv",
background=BackgroundTask(lambda: os.unlink(output_path))
)

Summary

FastAPI's FileResponse provides a powerful and flexible way to serve files in your web applications:

  • It handles proper HTTP headers and content types automatically
  • It streams files efficiently without loading them entirely into memory
  • It supports customization of filenames and download behavior
  • It works with files of any type and size

With FileResponse, you can easily implement file downloads, serve static content, or provide dynamically generated files to your API clients.

Additional Resources

Exercises

  1. Create an endpoint that serves a random image from a directory of images
  2. Build a simple file manager API with endpoints to list, upload, and download files
  3. Create a PDF generation endpoint that creates a custom report and serves it as a download
  4. Implement a secure file serving system with user authentication and permissions
  5. Create an API that converts uploaded files to different formats and allows downloading the converted file


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