← Back to 2026-02-06
fastapifile-uploadtext-extractiondrag-and-dropmultipartpdfdocx

Building a Complete File Upload System: From Drag-and-Drop to Text Extraction

Oli·

Building a Complete File Upload System: From Drag-and-Drop to Text Extraction

Ever wondered what it takes to build a complete file upload system from scratch? Today I'm sharing the journey of implementing a full-featured upload system that handles everything from drag-and-drop interactions to extracting text from PDFs and Word documents—and getting it production-ready in a single development session.

The Mission

The goal was ambitious but clear: add comprehensive file upload support to our document management system. This meant building:

  • A robust backend API endpoint handling multipart uploads
  • Text extraction for multiple formats (PDF, DOCX, TXT, MD, CSV)
  • A polished drag-and-drop interface with glassmorphism styling
  • Complete test coverage and production deployment

The Technical Journey

Backend Foundation: Multi-Format Text Extraction

The heart of the system is a smart text extraction service that handles different file types gracefully:

def extract_text(file_content: bytes, filename: str) -> str:
    """Extract text content based on file extension"""
    extension = Path(filename).suffix.lower()
    
    if extension in ['.txt', '.md']:
        return file_content.decode('utf-8')
    elif extension == '.csv':
        return file_content.decode('utf-8')  # Keep CSV structure
    elif extension == '.pdf':
        return extract_pdf_text(file_content)
    elif extension == '.docx':
        return extract_docx_text(file_content)
    else:
        raise ValueError(f"Unsupported file type: {extension}")

The beauty here is in the simplicity—each format gets the treatment it needs. Plain text files are decoded as UTF-8, PDFs are processed with pypdf, and Word documents are handled by python-docx.

API Design: Clean and Robust

The upload endpoint follows FastAPI best practices with proper multipart handling:

@router.post("/upload", response_model=SourceResponse)
async def upload_source(
    file: UploadFile = File(...),
    name: str = Form(...),
    description: str = Form(""),
    current_user: User = Depends(get_current_user)
):
    # Validation, extraction, and processing...

One critical design decision was route ordering. The upload endpoint needed to be placed before the /{source_id} routes to avoid FastAPI's path matching conflicts—a small detail that can cause big headaches if overlooked.

Frontend Polish: Glassmorphism Drag-and-Drop

The frontend features a modern drag-and-drop zone that feels native and responsive:

.drop-zone {
    border: 2px dashed rgba(139, 92, 246, 0.3);
    border-radius: 12px;
    padding: 2rem;
    text-align: center;
    transition: all 0.3s ease;
    background: rgba(255, 255, 255, 0.05);
    backdrop-filter: blur(10px);
}

.drop-zone.dragover {
    border-color: rgba(139, 92, 246, 0.6);
    background: rgba(139, 92, 246, 0.1);
    transform: scale(1.02);
}

The JavaScript handles all the interaction states smoothly:

handleFileDrop(event) {
    event.preventDefault();
    const files = event.dataTransfer.files;
    if (files.length > 0) {
        this.validateAndSetFile(files[0]);
    }
    // Reset visual state
    event.currentTarget.classList.remove('dragover');
}

Testing: The Confidence Builder

What made this implementation truly production-ready was comprehensive testing. The test suite covers:

  • File format validation (60 tests total, all passing)
  • Cross-tenant isolation (ensuring users can't access others' uploads)
  • Size limits and error handling
  • End-to-end upload workflows

Here's a snippet from the PDF extraction test:

def test_extract_pdf():
    # Create a minimal PDF using pypdf internals
    pdf_content = create_test_pdf_content()
    result = extract_text(pdf_content, "test.pdf")
    assert "Test PDF content" in result

Lessons Learned

The Good

The implementation was surprisingly smooth. Modern tools like FastAPI's built-in multipart support and Alpine.js for reactive UI made complex interactions feel straightforward.

The Challenges

The main gotcha was in the test suite. Creating test PDFs programmatically using pypdf's internal APIs (DecodedStreamObject, DictionaryObject) works but is fragile—these internals could change in future versions. For production systems, I'd recommend using fixture files instead.

Another small but important lesson: always use the project's virtual environment (.venv/bin/python3) when running scripts that import project packages. It seems obvious, but it's easy to forget when switching between system Python and project Python.

The Results

After implementation, the system handles uploads beautifully:

  • ✅ Drag-and-drop .txt files → instant text extraction
  • ✅ Upload .pdf documents → full text searchability
  • ✅ Process .docx files → content ready for analysis
  • ✅ Auto-trigger background processing for search indexing
  • ✅ Complete error handling with user-friendly messages

What's Next?

The foundation is solid, but there's always room for enhancement:

  1. Upload progress tracking with server-sent events
  2. File content previews in the management interface
  3. Batch upload support for multiple files
  4. Advanced validation (virus scanning, content analysis)

Key Takeaways

Building a complete upload system touches every layer of the stack, but breaking it down into focused components makes it manageable:

  1. Start with the data flow - understand how files move through your system
  2. Design for testability - comprehensive tests give you confidence to iterate
  3. Polish the details - smooth animations and error states make users happy
  4. Plan for scale - consider file sizes, concurrent uploads, and storage early

The most satisfying part? Watching a complex feature come together piece by piece, from backend validation to frontend polish, all working in harmony. That's the magic of full-stack development.


Want to see the code in action? The complete implementation is available in our repository, and I'd love to hear about your own file upload adventures in the comments below.