Skip to content

Quick Start

This guide walks you through building your first file upload endpoint with filestore.

1. Create a FastAPI App

app.py
from fastapi import Depends, FastAPI
from filestore import FileStore, LocalEngine, Store

app = FastAPI()

# Configure a storage dependency
storage = FileStore(
    "file",               # HTML form field name
    required=True,        # Reject requests with no files
    engine=LocalEngine(
        base_dir="uploads",   # Directory to write files to
        base_url="/media",    # Public URL prefix
    ),
)


@app.post("/upload")
async def upload(store: Store = Depends(storage)) -> Store:
    """Accept a single file upload."""
    return store  # Store is a Pydantic model — FastAPI serializes it

2. Run It

uvicorn app:app --reload

3. Test It

curl -X POST http://localhost:8000/upload \
  -F "file=@document.pdf"
import httpx

with open("document.pdf", "rb") as f:
    response = httpx.post(
        "http://localhost:8000/upload",
        files={"file": ("document.pdf", f, "application/pdf")},
    )
print(response.json())
const form = new FormData();
form.append("file", fileInput.files[0]);

const response = await fetch("/upload", {
  method: "POST",
  body: form,
});
const data = await response.json();

What Happened?

  1. FastAPI parsed the multipart request and extracted the file field
  2. filestore validated the upload (size, type, etc.)
  3. The file was written atomically to uploads/document.pdf
  4. If document.pdf already existed, it was automatically renamed to document-1.pdf
  5. A Store model was returned with all the results

Adding Validation

Let's restrict uploads to images under 5 MB:

app.py
from filestore import FileStore, LocalEngine, StoreConfig

storage = FileStore(
    "image",
    required=True,
    engine=LocalEngine(base_dir="uploads/images", base_url="/media/images"),
    config=StoreConfig(
        allowed_extensions=[".jpg", ".jpeg", ".png", ".webp"],
        allowed_content_types=["image/jpeg", "image/png", "image/webp"],
        max_file_size=5 * 1024 * 1024,  # 5 MB
    ),
)

A plain dict works too — it is validated into a StoreConfig automatically:

storage = FileStore("image", config={"max_file_size": 5 * 1024 * 1024})

Any file that doesn't match the constraints is rejected with a clear error message in store.errors.

Switching Backends

Every engine shares the same interface. To switch from local disk to S3, swap the engine:

# Before: local storage
from filestore import FileStore, LocalEngine
storage = FileStore("file", engine=LocalEngine(base_dir="uploads"))

# After: S3 storage
from filestore import FileStore, S3Engine
storage = FileStore("file", engine=S3Engine(bucket="my-bucket", region="us-east-1"))

Your endpoint code doesn't change at all.

Next Steps