RAG (Retrieval-Augmented Generation) lets you ground an LLM's answers in your own data — documents, databases, knowledge bases — instead of relying solely on its training. In this guide I'll walk through building a production-grade RAG chatbot using Node.js, LangChain.js, Pinecone as the vector store, and OpenAI for embeddings and generation.
What you'll build: An Express API endpoint that accepts a user question, retrieves the most relevant document chunks from Pinecone, and streams a grounded answer back from GPT-4o.
The RAG pipeline has two phases:
mkdir rag-chatbot && cd rag-chatbot
npm init -y
npm install express @langchain/openai @langchain/pinecone @pinecone-database/pinecone langchain dotenv
Create a .env file:
OPENAI_API_KEY=sk-...
PINECONE_API_KEY=...
PINECONE_INDEX=rag-index
This script loads text files, splits them into chunks, generates embeddings, and upserts them into Pinecone.
// ingest.js
import { PineconeStore } from "@langchain/pinecone";
import { OpenAIEmbeddings } from "@langchain/openai";
import { RecursiveCharacterTextSplitter } from "langchain/text_splitter";
import { Pinecone } from "@pinecone-database/pinecone";
import { readFileSync } from "fs";
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pc.index(process.env.PINECONE_INDEX);
const rawText = readFileSync("./data/knowledge-base.txt", "utf-8");
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 150,
});
const docs = await splitter.createDocuments([rawText]);
const embeddings = new OpenAIEmbeddings({ model: "text-embedding-3-small" });
await PineconeStore.fromDocuments(docs, embeddings, {
pineconeIndex: index,
namespace: "default",
});
console.log(`Upserted ${docs.length} chunks to Pinecone.`);
// rag.js
import { ChatOpenAI } from "@langchain/openai";
import { OpenAIEmbeddings } from "@langchain/openai";
import { PineconeStore } from "@langchain/pinecone";
import { Pinecone } from "@pinecone-database/pinecone";
import { createRetrievalChain } from "langchain/chains/retrieval";
import { createStuffDocumentsChain } from "langchain/chains/combine_documents";
import { ChatPromptTemplate } from "@langchain/core/prompts";
const pc = new Pinecone({ apiKey: process.env.PINECONE_API_KEY });
const index = pc.index(process.env.PINECONE_INDEX);
const vectorStore = await PineconeStore.fromExistingIndex(
new OpenAIEmbeddings({ model: "text-embedding-3-small" }),
{ pineconeIndex: index, namespace: "default" }
);
const retriever = vectorStore.asRetriever({ k: 5 });
const llm = new ChatOpenAI({ model: "gpt-4o", streaming: true });
const prompt = ChatPromptTemplate.fromTemplate(`
You are a helpful assistant. Answer the question based only on the context below.
If the answer is not in the context, say "I don't have that information."
Context:
{context}
Question: {input}
`);
const documentChain = await createStuffDocumentsChain({ llm, prompt });
export const ragChain = await createRetrievalChain({ retriever, combineDocsChain: documentChain });
// server.js
import express from "express";
import "dotenv/config";
import { ragChain } from "./rag.js";
const app = express();
app.use(express.json());
app.post("/chat", async (req, res) => {
const { question } = req.body;
if (!question) return res.status(400).json({ error: "question is required" });
res.setHeader("Content-Type", "text/event-stream");
res.setHeader("Cache-Control", "no-cache");
res.setHeader("Connection", "keep-alive");
const stream = await ragChain.stream({ input: question });
for await (const chunk of stream) {
if (chunk.answer) {
res.write(`data: ${JSON.stringify({ text: chunk.answer })}\n\n`);
}
}
res.write("data: [DONE]\n\n");
res.end();
});
app.listen(3000, () => console.log("RAG server running on port 3000"));
curl -X POST http://localhost:3000/chat \
-H "Content-Type: application/json" \
-d '{"question": "What are the refund policies?"}'
You now have a streaming RAG chatbot backed by Pinecone vector search. The same pattern scales to hundreds of thousands of documents by using Pinecone's serverless tier and batching your ingestion pipeline. For enterprise deployments I recommend adding a Redis cache layer for frequently asked questions to reduce OpenAI API costs significantly.
Need help building a RAG system for your product? Get in touch or hire me on Upwork.