How to Build a RAG Chat App: Chat with Your Documents Using AI
Retrieval-Augmented Generation (RAG) is one of the most practical applications of large language models. Instead of relying solely on the model's training data, RAG allows your AI to query your own documents and provide answers based on your specific content.
In this guide, we will build a RAG-powered chat application using Next.js, LangChain, and OpenAI. Users can upload documents, ask questions, and get answers with citations.
Understanding the RAG Architecture
RAG works in three phases:
- Ingestion: Documents are split into chunks, embedded into vectors, and stored in a vector database
- Retrieval: When a user asks a question, the system converts it to a vector and finds the most similar document chunks
- Generation: The retrieved chunks are injected into the LLM prompt as context, and the model generates an answer with citations
Setting Up Document Ingestion
import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'
import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
import { MemoryVectorStore } from 'langchain/vectorstores/memory'
export async function ingestDocument(content: string) {
const splitter = new RecursiveCharacterTextSplitter({
chunkSize: 1000,
chunkOverlap: 200,
})
const chunks = await splitter.createDocuments([content])
const embeddings = new OpenAIEmbeddings({
openAIApiKey: process.env.OPENAI_API_KEY,
})
const vectorStore = await MemoryVectorStore.fromDocuments(chunks, embeddings)
return vectorStore
}Building the Chat Interface
'use client'
import { useState, useRef } from 'react'
export function RagChat({ vectorStore }: { vectorStore: any }) {
const [messages, setMessages] = useState<{ role: string; content: string }[]>([])
const [input, setInput] = useState('')
const [loading, setLoading] = useState(false)
const handleSend = async () => {
if (!input.trim() || loading) return
const userMessage = input.trim()
setInput('')
setMessages(prev => [...prev, { role: 'user', content: userMessage }])
setLoading(true)
const response = await fetch('/api/rag-chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: userMessage }),
})
const data = await response.json()
setMessages(prev => [...prev, { role: 'assistant', content: data.answer }])
setLoading(false)
}
return (
<div className="flex flex-col h-[600px] rounded-xl bg-gray-900">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((msg, i) => (
<div key={i} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'}`}>
<div className={`max-w-[80%] rounded-xl px-4 py-2 ${
msg.role === 'user'
? 'bg-brand-600 text-white'
: 'bg-gray-800 text-gray-100'
}`}>{msg.content}</div>
</div>
))}
{loading && (
<div className="flex items-center gap-2 text-gray-400">
<span className="animate-pulse">...</span>
<span className="text-sm">AI is thinking</span>
</div>
)}
</div>
<div className="border-t border-gray-800 p-4">
<div className="flex gap-2">
<input
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSend()}
placeholder="Ask a question about your documents..."
className="flex-1 rounded-lg bg-gray-800 px-4 py-2 text-sm text-white placeholder-gray-500 border border-gray-700"
/>
<button onClick={handleSend} className="rounded-lg bg-brand-600 px-4 py-2 text-sm text-white">Send</button>
</div>
</div>
</div>
)
}The API Route
import { NextRequest, NextResponse } from 'next/server'
import { OpenAIEmbeddings } from 'langchain/embeddings/openai'
import { ChatOpenAI } from 'langchain/chat_models/openai'
import { createStuffDocumentsChain } from 'langchain/chains/combine_documents'
import { createRetrievalChain } from 'langchain/chains/retrieval'
export async function POST(req: NextRequest) {
const { message } = await req.json()
const vectorStore = await getVectorStore()
const retriever = vectorStore.asRetriever()
const llm = new ChatOpenAI({ modelName: 'gpt-4o' })
const chain = await createRetrievalChain({
retriever,
combineDocsChain: await createStuffDocumentsChain({ llm }),
})
const result = await chain.invoke({ input: message })
return NextResponse.json({ answer: result.answer })
}Cross-Selling: Build Your RAG App Faster
Our AI RAG Chat template provides a complete, production-ready implementation of this architecture. It includes document upload, vector search, source citations, conversation history, and multi-document support. Deploy in hours and customize for your use case.
Combine with AI Document Parser for advanced document processing, or AI Form Builder to create smart data collection forms that feed into your knowledge base.
Related Template
Try this production-ready starter kit to build your project faster.
What are the Q3 priorities?
Based on the Product Roadmap Q3 document, the main priorities are: 1. Dashboard redesign (target: Aug 15) 2. API v3 migration (target: Sep 1) 3. Mobile app beta launch 4. AI feature integration across the platform
How many engineers are allocated?
According to the roadmap, 8 engineers are allocated across Q3 projects — 3 for dashboard, 3 for API migration, and 2 for mobile.
AI RAG Chat
Chat with your documents using RAG
Ready to Build?
Get started with our production-ready starter kits and ship your project faster.
Browse Starter Kits