This project implements a Retrieval-Augmented Generation (RAG) system where:
- Users upload a file (PDF, DOCX, TXT, etc.)
- The file is stored in object storage (Supabase / S3)
- Text is extracted, chunked, embedded, and stored
- Users can ask questions about the uploaded file
- Answers are generated using retrieved chunks + LLM
The system is designed with clear separation of concerns between:
- File storage
- Extraction & embedding
- Chat / query handling
- Frontend navigation
User
↓
Upload File (Frontend)
↓
POST /upload
↓
Store file in Supabase/S3
↓
POST /extract
↓
Text Extraction + Chunking + Embeddings
↓
Store extraction result (id)
↓
Navigate to Chat Page
↓
User asks questions
↓
RAG retrieval + LLM response
| Identifier | Purpose |
|---|---|
file_id |
Identifies the uploaded raw file |
extraction_id (or id) |
Identifies extracted + chunked data |
session_id (optional) |
Identifies a chat session |
Important: The
extraction_idis the primary ID used by the chat system.
POST /extract{
"file_id": "uuid",
"publicUrl": "https://storage.url/file.pdf",
"fileType": "application/pdf"
}- Fetch file from storage
- Extract raw text
- Split text into chunks
- Generate embeddings
- Store metadata + chunks in DB
- Return the extraction record
app.post("/extract", async (req, res) => {
try {
const { file_id, publicUrl, fileType } = req.body;
if (!file_id || !publicUrl || !fileType) {
return res.status(400).json({
error: "file_id, publicUrl, and fileType are required",
});
}
const result = await extractAndStore(file_id, publicUrl, fileType);
return res.status(201).json({
success: true,
data: result,
});
} catch (err: any) {
return res.status(500).json({
error: err.message || "Failed to extract text from file",
});
}
});{
"success": true,
"data": {
"id": "cce79fce-ba4d-42c4-ba05-de812dc54eca",
"file_id": "2b059c2f-3261-4519-bdf8-f52a2a1c270d",
"totalChunks": 2,
"totalCharacters": 1383,
"metadata": {
"totalChunks": 2,
"totalCharacters": 1383,
"fileType": "application/pdf",
"extractedAt": "2025-12-19T05:31:31.968Z"
}
}
}- Keeps API responses consistent
- Avoids duplicated fields
- Scales better as response grows
const response = await fetch("/extract", {
method: "POST",
body: JSON.stringify(payload),
});
const json = await response.json();
// ✅ Correct access
const extractionId = json.data.id;
// Navigate to chat
router.push(`/chat/${extractionId}`);Property 'id' does not exist on type '{ success: boolean; data: {...} }'You tried to access:
response.idBut the API returns:
response.data.idresponse.data.idtype ExtractResponse = {
success: boolean;
data: {
id: string;
file_id: string;
totalChunks: number;
totalCharacters: number;
metadata: {
totalChunks: number;
totalCharacters: number;
fileType: string;
extractedAt: string;
};
};
};Usage:
const response: ExtractResponse = await res.json();
router.push(`/chat/${response.data.id}`);/chat/[extractionId]
- Read
extractionIdfrom route params - Maintain chat messages
- Send user queries to
/chat/query - Display streaming or static answers
User Question
↓
POST /chat/query
↓
Fetch chunks using extractionId
↓
Vector similarity search
↓
Top-K relevant chunks
↓
Prompt = Context + Question
↓
LLM generates answer
↓
Return response
POST /chat/query{
"extraction_id": "cce79fce-ba4d-42c4-ba05-de812dc54eca",
"question": "What is the eligibility criteria?"
}┌───────────────┐
│ Frontend │
│ (Next.js) │
└───────┬───────┘
│ Upload
▼
┌───────────────┐
│ File Store │
│ Supabase / S3 │
└───────┬───────┘
│ publicUrl
▼
┌───────────────┐
│ /extract API │
│ Text Extract │
│ Chunk + Embed │
└───────┬───────┘
│ extraction_id
▼
┌───────────────┐
│ Chat Page │
│ /chat/:id │
└───────┬───────┘
│ question
▼
┌───────────────┐
│ RAG Engine │
│ Vector Search │
│ LLM Response │
└───────────────┘
- ✅ Strong typing with TypeScript
- ✅ Clear API contracts
- ✅ Single source of truth (
data.id) - ✅ Scalable RAG design
- ✅ Clean navigation using route params
- Chat sessions per file
- Streaming LLM responses
- Multi-file RAG support
- Citation highlighting per chunk
- Access control per user
This design ensures:
- Clean frontend–backend integration
- Predictable navigation
- Scalable RAG workflows
- Minimal TypeScript friction