DEV Community

Cover image for Let’s Break Down a Full-Stack RAG Pipeline With React, Node.js & MongoDB
Soumyajit Mukherjee
Soumyajit Mukherjee

Posted on

Let’s Break Down a Full-Stack RAG Pipeline With React, Node.js & MongoDB

If you've built a normal full-stack application, you probably know this architecture:

React → Express → MongoDB
Enter fullscreen mode Exit fullscreen mode

Now let's add an AI-powered RAG pipeline.

Our architecture becomes:

React
  ↓
Express API
  ↓
RAG Service
  ↓
Embedding Model
  ↓
MongoDB Vector Search
  ↓
Relevant Context
  ↓
LLM
  ↓
Response
Enter fullscreen mode Exit fullscreen mode

Let's break it down.


What Are We Building?

Imagine a developer documentation assistant.

Users upload technical documentation, and later ask:

How do I refresh an expired JWT?
Enter fullscreen mode Exit fullscreen mode

Our application searches the uploaded documentation and gives the relevant information to an LLM before generating the response.

This is Retrieval-Augmented Generation (RAG).


1. Document Ingestion

First, documents need to enter our system.

PDF / Markdown / HTML
        ↓
Text Extraction
        ↓
Cleaning
        ↓
Chunking
        ↓
Embeddings
        ↓
MongoDB
Enter fullscreen mode Exit fullscreen mode

Why chunk the documents?

Because retrieving a small relevant section is usually more useful than sending an entire document to the LLM.

Example:

authentication.md

├── Chunk 1
├── Chunk 2
├── Chunk 3
└── Chunk 4
Enter fullscreen mode Exit fullscreen mode

2. Generate Embeddings

Each chunk is converted into a vector.

const embedding = await embeddingModel.embed(chunk);
Enter fullscreen mode Exit fullscreen mode

Conceptually:

"Refresh tokens are used..."
             ↓
[0.12, -0.43, 0.77, ...]
Enter fullscreen mode Exit fullscreen mode

The vector represents the semantic meaning of the text.


3. Store the Data

A MongoDB document might look like:

{
  documentId: "auth-guide",
  content: "Refresh tokens are used...",
  embedding: [0.12, -0.43, 0.77],
  metadata: {
    page: 10,
    source: "auth-guide.pdf"
  }
}
Enter fullscreen mode Exit fullscreen mode

MongoDB Atlas Vector Search can then be used to search these embeddings.


4. User Sends a Question

React sends:

POST /api/chat
Enter fullscreen mode Exit fullscreen mode
{
  "question": "How do I refresh an expired JWT?"
}
Enter fullscreen mode Exit fullscreen mode

Express receives the request.

app.post("/api/chat", async (req, res) => {
  const { question } = req.body;

  const answer = await ragService.ask(question);

  res.json({ answer });
});
Enter fullscreen mode Exit fullscreen mode

5. Embed the Query

The question is converted into an embedding.

User Question
     ↓
Embedding Model
     ↓
Query Vector
Enter fullscreen mode Exit fullscreen mode

Now the query vector can be compared with the document vectors.


6. Retrieve Relevant Chunks

Our vector search might return:

Refresh Token Documentation    0.94
JWT Authentication             0.89
Session Management             0.81
Database Configuration         0.31
Enter fullscreen mode Exit fullscreen mode

We take the most relevant chunks.

This is the retrieval stage.


7. Build the Context

Now we combine the retrieved chunks:

const context = results
  .map(result => result.content)
  .join("\n\n");
Enter fullscreen mode Exit fullscreen mode

Our application now has:

Question
+
Relevant Documentation
Enter fullscreen mode Exit fullscreen mode

8. Build the Prompt

For example:

You are a developer documentation assistant.

Use the context below to answer the question.

Context:
[retrieved documents]

Question:
How do I refresh an expired JWT?

If the context doesn't contain the answer,
say that the information is unavailable.
Enter fullscreen mode Exit fullscreen mode

9. Call the LLM

The backend sends the prompt to the LLM.

const answer = await llm.generate(prompt);
Enter fullscreen mode Exit fullscreen mode

The model generates the response.

Then:

LLM
 ↓
Express
 ↓
React
Enter fullscreen mode Exit fullscreen mode

React displays the answer.


Complete RAG Flow

                 USER
                   ↓
              React UI
                   ↓
             Express API
                   ↓
             RAG Service
                   ↓
          Query Embedding
                   ↓
          Vector Retrieval
                   ↓
           Relevant Chunks
                   ↓
          Context Construction
                   ↓
          Prompt Construction
                   ↓
                  LLM
                   ↓
             Final Answer
                   ↓
                React
Enter fullscreen mode Exit fullscreen mode

The Ingestion Side

Don't forget that there are actually two important pipelines.

Ingestion pipeline

Document
 ↓
Extract
 ↓
Clean
 ↓
Chunk
 ↓
Embed
 ↓
Store
Enter fullscreen mode Exit fullscreen mode

Query pipeline

Question
 ↓
Embed
 ↓
Search
 ↓
Retrieve
 ↓
Build Context
 ↓
LLM
 ↓
Answer
Enter fullscreen mode Exit fullscreen mode

This distinction makes RAG much easier to understand.


Why Chunking Is Important

Imagine a 200-page API reference.

The user asks:

How does refresh-token rotation work?
Enter fullscreen mode Exit fullscreen mode

Sending the entire document to the LLM is inefficient.

Instead, retrieval might identify:

Chunk 47
Chunk 51
Chunk 52
Enter fullscreen mode Exit fullscreen mode

as the relevant sections.

That's what makes the "retrieval" part valuable.


Production Improvements

A basic RAG demo isn't enough for a production application.

We could add:

  • Authentication
  • Rate limiting
  • Caching
  • Metadata filtering
  • Hybrid search
  • Reranking
  • Query rewriting
  • Citations
  • Observability
  • Token/cost tracking
  • Prompt-injection defenses

For example:

Query
 ↓
Validation
 ↓
Query Rewriting
 ↓
Hybrid Search
 ↓
Metadata Filtering
 ↓
Reranking
 ↓
Context
 ↓
LLM
 ↓
Citations
Enter fullscreen mode Exit fullscreen mode

Example MERN Structure

server/

├── controllers/
│
├── routes/
│
├── models/
│
├── middleware/
│
└── services/
    ├── embeddingService.js
    ├── retrievalService.js
    ├── ragService.js
    └── llmService.js
Enter fullscreen mode Exit fullscreen mode

Keeping these services separate makes the architecture easier to maintain.


RAG Isn't Just "An LLM + Vector DB"

That's probably the most important lesson.

A useful RAG system depends on:

Document quality
       +
Chunking
       +
Embeddings
       +
Retrieval
       +
Context selection
       +
Prompt design
       +
LLM
Enter fullscreen mode Exit fullscreen mode

If retrieval returns irrelevant information, even a powerful LLM can produce a poor response.


Final Architecture

A simple version:

React
 ↓
Node.js
 ↓
MongoDB Vector Search
 ↓
Relevant Context
 ↓
Gemini / OpenAI-style LLM
 ↓
React
Enter fullscreen mode Exit fullscreen mode

A more advanced version:

React
 ↓
API Gateway
 ↓
Authentication
 ↓
Query Processing
 ↓
Hybrid Retrieval
 ↓
Reranking
 ↓
Context Compression
 ↓
LLM
 ↓
Citation Layer
 ↓
Response
Enter fullscreen mode Exit fullscreen mode

And that's where a simple AI demo starts becoming a real full-stack engineering project.


Final Thought

The LLM is only one part of a RAG application.

The real engineering work happens around it:

ingestion → retrieval → context → generation → security → observability → optimization.

That's what makes RAG such an interesting architecture for full-stack developers.

Top comments (0)