Next.js + FastAPI PDF Upload MVP (Implementation Guide)
This guide shows the minimum working setup to connect a Next.js (React) frontend with a FastAPI backend for uploading a PDF.
Goal: Browser selects a PDF → sends multipart/form-data → FastAPI receives the file → reads bytes → returns JSON.
0) Project Structure
Recommended layout:
pdf-assistant/
backend/
main.py
requirements.txt (optional)
frontend/
(Next.js app)
1) Backend: FastAPI (Receive and Read Uploaded PDF)
1.1 Create a virtual environment + install dependencies
cd pdf-assistant
mkdir backend
cd backend
python3 -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn python-multipartImportant:
python-multipartis required to parsemultipart/form-datauploads.
Optional (if you want to parse PDF text):
pip install pypdf(Optional) Save dependencies:
pip freeze > requirements.txt1.2 Create backend/main.py (minimal working)
from fastapi import FastAPI, UploadFile, File
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Allow Next.js dev server (CORS)
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/health")
def health():
return {"status": "ok"}
@app.post("/upload")
async def upload_pdf(file: UploadFile = File(...)):
contents = await file.read() # bytes in RAM for this request
return {
"filename": file.filename,
"content_type": file.content_type,
"bytes": len(contents),
}1.3 Run the backend
uvicorn main:app --reload --port 8000Verify:
http://localhost:8000/healthhttp://localhost:8000/docs
2) Frontend: Next.js (Upload UI)
2.1 Create a Next.js app
cd ../
npx create-next-app@latest frontend
cd frontend
npm run devOpen:
http://localhost:3000
2.2 Configure backend base URL (recommended)
Create frontend/.env.local:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000Restart Next.js after editing .env.local:
npm run dev2.3 Implement upload UI (App Router)
Edit frontend/src/app/page.tsx:
"use client";
import { useState, type ChangeEvent } from "react";
const API_BASE =
process.env.NEXT_PUBLIC_API_BASE_URL ?? "http://localhost:8000";
export default function Home() {
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<any>(null);
const [error, setError] = useState<string>("");
const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
setError("");
setResult(null);
setFile(e.target.files?.[0] ?? null);
};
const handleUpload = async () => {
if (!file) {
setError("Please select a PDF file.");
return;
}
setLoading(true);
setError("");
setResult(null);
try {
const form = new FormData();
form.append("file", file); // MUST match FastAPI param name: file
const res = await fetch(`${API_BASE}/upload`, {
method: "POST",
body: form,
// IMPORTANT: do NOT set Content-Type manually for FormData
});
if (!res.ok) {
const text = await res.text();
throw new Error(text || "Upload failed");
}
setResult(await res.json());
} catch (e: any) {
setError(e?.message ?? "Unknown error");
} finally {
setLoading(false);
}
};
return (
<main style={{ maxWidth: 720, margin: "40px auto", padding: 20 }}>
<h1>PDF Upload MVP</h1>
<input type="file" accept="application/pdf" onChange={handleFileChange} />
<div style={{ marginTop: 12 }}>
<button onClick={handleUpload} disabled={!file || loading}>
{loading ? "Uploading..." : "Upload"}
</button>
</div>
{error && <p style={{ color: "crimson" }}>Error: {error}</p>}
{result && (
<pre style={{ marginTop: 16, background: "#f6f6f6", padding: 12 }}>
{JSON.stringify(result, null, 2)}
</pre>
)}
</main>
);
}3) End-to-end verification checklist
-
Start backend:
cd backend source .venv/bin/activate uvicorn main:app --reload --port 8000 -
Start frontend:
cd frontend npm run dev -
Open the frontend and upload a PDF.
- You should see JSON response like:
{ "filename": "...", "bytes": 12345 }
- You should see JSON response like:
4) Common issues (quick fixes)
A) Backend error: python-multipart is required
python-multipart is a library FastAPI uses to parse multipart/form-data requests, which is the standard format browsers use for file uploads (via FormData)
Fix:
pip install python-multipartB) CORS error in browser
Fix in FastAPI:
- Ensure
allow_originsincludeshttp://localhost:3000 - Restart backend after changes
C) Upload breaks when setting Content-Type
Fix:
- For
FormData, do NOT setContent-Type. The browser sets it with a boundary.
D) .env.local not taking effect
Fix:
- Restart Next.js (
npm run dev) after editing env files.
5) Optional extension: Parse PDF text preview (backend)
If you installed pypdf, you can extract a preview:
from io import BytesIO
from pypdf import PdfReader
@app.post("/upload")
async def upload_pdf(file: UploadFile = File(...)):
contents = await file.read()
reader = PdfReader(BytesIO(contents))
preview = ""
if reader.pages:
preview = (reader.pages[0].extract_text() or "")[:800]
return {
"filename": file.filename,
"pages": len(reader.pages),
"text_preview": preview,
}6) Next steps
Once upload works, typical next steps are:
- Extract full text
- Summarize
- Store document state (
doc_id) - Add
/askand/translateendpoints
If you tell me your desired response format (e.g., summary, doc_id, sources), I can help you extend this MVP cleanly.