What you will build
This MERN stack generative AI tutorial for beginners walks you through a small AI application with a React interface, an Express and Node.js API, MongoDB persistence, and a hosted language model. The same foundation can support a study assistant, customer-support tool, document Q&A product, or Indic-language content workflow.
The key design rule is simple: the browser never receives your model-provider API key. React sends a request to your server, the server validates it, calls the model, stores the interaction when needed, and returns a controlled response.
You do not need to train a model for this project. You need JavaScript fundamentals, basic HTTP knowledge, and a clear product use case. If you want project ideas before coding, compare this tutorial with machine learning portfolio projects for beginners in India.
MERN generative AI architecture
A conventional MERN request looks like React → Express → MongoDB. An AI request adds a model service and usually looks like:
React → Express API → validation and policy checks → model API → MongoDB
Each layer has a distinct responsibility:
- React: captures prompts, displays loading and error states, and renders responses.
- Express and Node.js: authenticate users, validate input, enforce quotas, call the model, and shape the output.
- MongoDB: stores users, conversations, usage records, feedback, and—if required—retrieved document chunks.
- Model provider: generates text or structured output. You can use a hosted API, a compatible gateway, or a local runtime such as Ollama.
- Observability: records latency, token usage, failures, and request IDs without storing sensitive prompt content unnecessarily.
For a multi-step workflow, do not put every capability into one oversized prompt. Separate tools and permissions into explicit server-side functions. The principles in how to build generative AI agents become relevant when your app needs tool calls, planning, or persistent state.
Prerequisites and project setup
Install Node.js 20 or later, Git, and a MongoDB Atlas database or local MongoDB instance. Create an API key with your chosen model provider and keep it in a password manager or deployment secret store.
A modern setup can use Vite rather than Create React App:
mkdir mern-ai-app && cd mern-ai-app
npm create vite@latest client -- --template react
mkdir server
cd server
npm init -y
npm install express mongoose cors dotenv openai helmet express-rate-limit zod
npm install -D nodemon
cd ../client
npm installAdd a server/.env file and never commit it:
PORT=5000
MONGO_URI=mongodb+srv://...
OPENAI_API_KEY=your_server_only_key
CLIENT_ORIGIN=http://localhost:5173Use the provider's current model catalogue and pricing when choosing a model. Avoid copying retired model names from older tutorials; model availability, limits, and prices change.
Build a safer Node.js backend
Create server/index.js:
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import rateLimit from 'express-rate-limit';
import { z } from 'zod';
import OpenAI from 'openai';
const app = express();
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
app.use(helmet());
app.use(cors({ origin: process.env.CLIENT_ORIGIN }));
app.use(express.json({ limit: '20kb' }));
const generateLimit = rateLimit({ windowMs: 60_000, limit: 10 });
const requestSchema = z.object({
prompt: z.string().trim().min(1).max(4000)
});
app.post('/api/generate', generateLimit, async (req, res) => {
const parsed = requestSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Enter a prompt between 1 and 4,000 characters.' });
}
try {
const completion = await client.chat.completions.create({
model: process.env.AI_MODEL,
messages: [
{ role: 'system', content: 'You are a concise, helpful assistant. State uncertainty clearly.' },
{ role: 'user', content: parsed.data.prompt }
],
max_tokens: 600,
temperature: 0.3
});
const result = completion.choices[0]?.message?.content ?? '';
res.json({ result });
} catch (error) {
console.error('generation_failed', error);
res.status(502).json({ error: 'The AI service is temporarily unavailable.' });
}
});
app.listen(process.env.PORT || 5000, () => console.log('API listening'));Set "type": "module" in server/package.json and add AI_MODEL to .env. The route deliberately returns a generic error to users while logging details on the server. Do not expose provider error messages, prompts, stack traces, or keys in production.
Connect the React frontend
In client/src/App.jsx, keep the first interface deliberately small:
import { useState } from 'react';
export default function App() {
const [prompt, setPrompt] = useState('');
const [result, setResult] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
async function submit(event) {
event.preventDefault();
setLoading(true); setError(''); setResult('');
try {
const response = await fetch('http://localhost:5000/api/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'Request failed');
setResult(data.result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
return (
<main>
<h1>MERN AI assistant</h1>
<form onSubmit={submit}>
<textarea value={prompt} onChange={e => setPrompt(e.target.value)} />
<button disabled={loading || !prompt.trim()}>
{loading ? 'Generating…' : 'Generate'}
</button>
</form>
{error && <p role="alert">{error}</p>}
<article aria-live="polite">{result}</article>
</main>
);
}Use aria-live, a disabled submit button, visible errors, and a character counter. In a real product, replace the hard-coded API URL with a Vite environment variable and configure HTTPS in deployment.
Save conversations in MongoDB
A conversation document should include ownership and a bounded message structure, not just two unindexed strings:
const conversationSchema = new mongoose.Schema({
userId: { type: mongoose.Schema.Types.ObjectId, required: true, index: true },
messages: [{
role: { type: String, enum: ['user', 'assistant'], required: true },
content: { type: String, required: true, maxlength: 12000 },
createdAt: { type: Date, default: Date.now }
}]
}, { timestamps: true });Add authentication before storing personal conversations, enforce document ownership on every read, and define retention rules. For a knowledge-base product, retrieve relevant passages before generation rather than asking the model to invent facts. MongoDB Atlas Vector Search can fit this architecture; treat low-resource Indic natural language processing as a useful direction when your product serves Indian languages and regional data.
Streaming, cost, and quality controls
A blocking request is acceptable for a prototype, but streaming improves perceived responsiveness. Implement Server-Sent Events or a framework-supported streaming response only after the basic route works. The server must still handle client disconnects, provider timeouts, partial output, and cancellation.
Control cost and reliability with:
- Input and output limits: cap prompt length and generated tokens.
- Per-user quotas: track daily requests and estimated token usage.
- Caching: cache only responses that are safe to reuse; never leak one user's private data.
- Model routing: use a smaller model for classification and drafting, and a stronger model for difficult tasks.
- Structured output: request JSON schemas for data used by application code, then validate the result before saving it.
- Evaluation sets: maintain representative prompts in English and relevant Indian languages, with expected quality criteria.
- Fallbacks: show a useful failure state instead of silently retrying expensive requests.
If your product later needs voice input, interruption handling, or low-latency speech output, plan that as a separate real-time architecture; the real-time voice agent build guide covers the additional constraints.
Security and production checklist
Before deployment, verify the following:
- Keep model keys only in server-side secrets.
- Use authentication, authorization, HTTPS, CORS allowlists, Helmet, rate limits, and request-size limits.
- Redact API keys, phone numbers, Aadhaar numbers, health information, and other sensitive data from logs.
- Add moderation and prompt-injection defences appropriate to your use case.
- Do not treat model output as trusted HTML, SQL, shell commands, or executable code.
- Add timeouts, retries with backoff, circuit breakers, and provider usage alerts.
- Test prompt injection, oversized inputs, malformed JSON, duplicate submissions, and provider outages.
- Publish a privacy notice explaining collection, retention, model processing, and deletion.
For India-focused applications, consider data residency, contractual terms, consent, grievance handling, and the Digital Personal Data Protection Act, 2023. Obtain qualified legal advice for regulated sectors rather than relying on a generic chatbot policy.
Deployment path for beginners
Deploy the React client to a static host and the Node API to a managed service that supports environment secrets and logs. Use MongoDB Atlas for the database, restrict network access, and create separate development and production projects. Configure the production client origin, health checks, database indexes, and a spending alert before inviting users.
Start with one narrow workflow, measure completion and factual error rates, and only then add RAG, agents, voice, or multi-user collaboration. A working MERN AI application is not defined by the number of model features; it is defined by predictable behaviour, clear limits, and a useful outcome for its users.