Part 1: T3 Turbo और Gemini के साथ Chatbot कैसे बनाएं
ज़्यादातर Founders 'setup hell' में फंसकर रह जाते हैं। मैंने सिर्फ एक दोपहर में एक fully type-safe AI chatbot तैयार किया। यहाँ है वो exact stack—Next.js, tRPC, और Gemini—और साथ में पूरा code, ताकि आप भी इसे खुद बना सकें।

Title: T3 Turbo + Gemini के साथ AI Chatbot बनाना: Vibe Coding का एक मास्टरक्लास
Complexity (जटिलता) शुरुआती दौर के startups के लिए एक silent killer की तरह है। आप एक साधारण विचार के साथ शुरुआत करते हैं—"मुझे एक ऐसा चैटबॉट चाहिए जो Tony Stark की तरह बात करे"—और तीन हफ्ते बाद भी, आप Webpack configure कर रहे होते हैं, Docker containers से जूझ रहे होते हैं, या किसी ऐसे authentication flow को debug कर रहे होते हैं जिसे अभी तक किसी ने इस्तेमाल भी नहीं किया है।
यह एक ऐसा जाल है जिसमें मैंने बेहद प्रतिभाशाली इंजीनियर्स को बार-बार फंसते देखा है। हमें अपने tools से प्यार है। हमें optimize करना पसंद है। लेकिन startup के खेल में, shipping (product को launch करना) ही एकमात्र metric है जो मायने रखता है।
यदि आपने हाल ही में modern TypeScript ecosystem को नहीं देखा है, तो आप हैरान हो सकते हैं। अलग-अलग APIs को एक साथ जोड़ने और उनके टिके रहने की प्रार्थना करने के दिन काफी हद तक पीछे छूट चुके हैं। हमने "Vibe Coder" के युग में प्रवेश कर लिया है—जहाँ एक विचार और एक deployed product के बीच की दूरी को हफ़्तों (sprints) में नहीं, बल्कि घंटों में मापा जाता है।
आज, मैं आपको एक ऐसे stack के बारे में बताने जा रहा हूँ जो किसी cheat code जैसा महसूस होता है: Create T3 Turbo और Google Gemini AI का कॉम्बिनेशन। यह डेटाबेस से लेकर frontend तक type-safe है, यह बहुत तेज़ है, और सच कहूँ तो, यह कोडिंग में वो खुशी वापस लाता है जो कहीं खो गई थी।
यह Stack क्यों मायने रखता है
आप सोच रहे होंगे, "Feng Liu, एक और stack क्यों? क्या मैं सिर्फ Python और Streamlit का उपयोग नहीं कर सकता?"
ज़रूर, एक prototype के लिए। लेकिन अगर आप एक product बना रहे हैं—कुछ ऐसा जिसे scale करना है, users को संभालना है, और state maintain करना है—तो आपको एक वास्तविक architecture की आवश्यकता है। समस्या यह है कि "वास्तविक architecture" का मतलब आमतौर पर "हफ़्तों का boilerplate code" होता है।
T3 Stack (Next.js, tRPC, Tailwind) इस कहानी को बदल देता है। यह आपको एक script की development speed के साथ full-stack application की मजबूती देता है। जब आप इसमें Drizzle ORM (lightweight, SQL-like) और Google Gemini (तेज़, और जिसका free tier बहुत बड़ा है) जोड़ते हैं, तो आपके पास एक ऐसा toolkit होता है जो एक अकेले founder को दस लोगों की टीम से आगे निकलने में मदद करता है।
चलिए कुछ असली (real) बनाते हैं।
Step 1: एक कमांड वाला सेटअप (The One-Command Setup)
Manually ESLint और Prettier को configure करना भूल जाइए। हम create-t3-turbo का उपयोग करने जा रहे हैं। यह एक monorepo structure सेट करता है जो एकदम सही है क्योंकि यह आपके API logic को आपके Next.js frontend से अलग करता है। यह आपको भविष्य के लिए तैयार (future-proofing) करता है जब आप बाद में अनिवार्य रूप से React Native मोबाइल ऐप लॉन्च करेंगे।
pnpm create t3-turbo@latest my-chatbot
cd my-chatbot
pnpm install
जब पूछा गया, तो मैंने Next.js, tRPC, और PostgreSQL को चुना। मैंने अभी के लिए Auth को छोड़ दिया क्योंकि, याद रखें, हम shipping के लिए optimize कर रहे हैं, perfection के लिए नहीं। आप बाद में दस मिनट में NextAuth जोड़ सकते हैं।
आपको मिलने वाला monorepo structure:
my-chatbot/
├── apps/nextjs/ # Your web app
├── packages/
│ ├── api/ # tRPC routers (shared logic)
│ ├── db/ # Database schema + Drizzle
│ └── ui/ # Shared components
इस अलगाव (separation) का मतलब है कि आपके API logic का उपयोग web, mobile, या यहाँ तक कि CLI apps में भी किया जा सकता है। मैंने टीमों को महीनों बर्बाद करते देखा है क्योंकि उन्होंने सब कुछ एक ही folder में रखकर शुरुआत की थी।
Step 2: दिमाग (Gemini)
OpenAI बहुत अच्छा है, लेकिन क्या आपने Gemini Flash आज़माया है? यह अविश्वसनीय रूप से तेज़ है और इसकी pricing बहुत aggressive है। एक चैट इंटरफ़ेस के लिए जहाँ latency (देरी) पूरे vibe को खराब कर देती है, वहाँ speed ही सबसे बड़ा feature है।
GPT-3.5/4 के बजाय Gemini Flash क्यों?
- Speed: ~800ms बनाम 2-3s response time
- Cost: GPT-4 से 60 गुना सस्ता
- Context: 1M token context window (जी हाँ, एक मिलियन)
LLMs से बात करने को standardized बनाने के लिए हमें AI SDK की आवश्यकता है।
cd packages/api
pnpm add ai @ai-sdk/google
Project root में अपना .env सेट करें। Locally डेटाबेस के बारे में ज्यादा न सोचें; एक local Postgres instance ठीक है।
POSTGRES_URL="postgresql://user:pass@localhost:5432/chatbot"
GOOGLE_GENERATIVE_AI_API_KEY="your_key_here"
Pro tip: अपनी Gemini API key यहाँ से प्राप्त करें: https://aistudio.google.com/app/apikey। इसका free tier बेतुका हद तक उदार है—60 requests प्रति मिनट। Rate limits हिट करने से पहले आप Product-Market Fit हासिल कर लेंगे।
Step 3: वास्तविकता को परिभाषित करें (The Schema)
यहाँ Drizzle का असली जादू दिखता है। पुराने दिनों में, आप हाथ से migrations लिखते थे। अब, आप अपने schema को TypeScript में define करते हैं, और डेटाबेस उसका पालन करता है।
packages/db/src/schema.ts में, हम define करते हैं कि एक "Message" क्या है। ध्यान दें कि हम drizzle-zod का उपयोग कैसे करते हैं? यह हमारे API के लिए अपने आप validation schemas बना देता है। यह "Don't Repeat Yourself" (DRY) सिद्धांत का जीता-जागता उदाहरण है।
import { pgTable } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod/v4";
// Message table for chatbot
export const Message = pgTable("message", (t) => ({
id: t.integer().primaryKey().generatedAlwaysAsIdentity(),
role: t.varchar({ length: 20 }).notNull(), // 'user' or 'assistant'
content: t.text().notNull(),
createdAt: t.timestamp().defaultNow().notNull(),
}));
// Zod schema auto-generated from table definition
export const CreateMessageSchema = createInsertSchema(Message, {
role: z.enum(["user", "assistant"]),
content: z.string().min(1).max(10000),
}).omit({ id: true, createdAt: true });
इसे push करें: pnpm db:push। हो गया। आपका डेटाबेस अब अस्तित्व में है।
अभी क्या हुआ? Drizzle ने आपकी TypeScript definition को देखा और table बना दी। कोई SQL नहीं लिखी गई। कोई migration files नहीं। यह schema-driven development का जादू है।
यदि आप verify करना चाहते हैं, तो run करें: pnpm db:studio और आपको https://local.drizzle.studio पर एक web UI दिखाई देगा जहाँ आपकी message table डेटा प्राप्त करने के लिए तैयार बैठी है।
Step 4: नर्वस सिस्टम (tRPC)
यह वह हिस्सा है जो अक्सर लोगों का दिमाग घुमा देता है। REST या GraphQL के साथ, आपको endpoints, types और fetchers को अलग-अलग define करना पड़ता है। tRPC के साथ, आपका backend function ही आपका frontend function है।
हम एक procedure बना रहे हैं जो user के message को save करता है, history (context) उठाता है (AI में context ही राजा है), इसे Gemini को भेजता है, और जवाब को save करता है।
packages/api/src/router/chat.ts बनाएँ:
import type { TRPCRouterRecord } from "@trpc/server";
import { google } from "@ai-sdk/google";
import { generateText } from "ai";
import { z } from "zod/v4";
import { desc } from "@acme/db";
import { Message } from "@acme/db/schema";
import { publicProcedure } from "../trpc";
const SYSTEM_PROMPT = "You are a helpful AI assistant.";
export const chatRouter = {
sendChat: publicProcedure
.input(z.object({ content: z.string().min(1).max(10000) }))
.mutation(async ({ ctx, input }) => {
// 1. Save User Message
await ctx.db
.insert(Message)
.values({ role: "user", content: input.content });
// 2. Get Context (Last 10 messages)
const history = await ctx.db
.select()
.from(Message)
.orderBy(desc(Message.createdAt))
.limit(10);
// 3. Ask Gemini
const { text } = await generateText({
model: google("gemini-1.5-flash"),
system: SYSTEM_PROMPT,
messages: history.reverse().map((m) => ({
role: m.role as "user" | "assistant",
content: m.content,
})),
});
// 4. Save AI Reply
return await ctx.db
.insert(Message)
.values({ role: "assistant", content: text })
.returning();
}),
getMessages: publicProcedure.query(({ ctx }) =>
ctx.db.select().from(Message).orderBy(Message.createdAt),
),
clearMessages: publicProcedure.mutation(({ ctx }) => ctx.db.delete(Message)),
} satisfies TRPCRouterRecord;
packages/api/src/root.ts में router को register करें:
import { chatRouter } from "./router/chat";
import { createTRPCRouter } from "./trpc";
export const appRouter = createTRPCRouter({
chat: chatRouter,
});
export type AppRouter = typeof appRouter;
उस flow को देखें। यह linear है, पढ़ने योग्य है, और पूरी तरह से typed है। यदि आप डेटाबेस schema बदलते हैं, तो यह कोड तुरंत लाल हो जाएगा। Runtime पर कोई सरप्राइज़ नहीं।
.reverse() क्यों? हम messages को descending order (नया पहले) में query करते हैं लेकिन LLMs chronological order (पुराना पहले) की उम्मीद करते हैं। यह एक छोटी सी detail है जो बातचीत को confusing होने से बचाती है।

Step 5: इंटरफ़ेस (The Interface)
apps/nextjs/src/app/chat/page.tsx में, हम इसे जोड़ते हैं। चूँकि हम tRPC का उपयोग कर रहे हैं, हमें React Query मुफ्त में मिलता है। useQuery fetching, caching, और loading states को संभालता है, बिना हमारे data fetching के लिए एक भी useEffect लिखे।
(मैंने केवल नीचे scroll करने के लिए एक useEffect शामिल किया है—क्योंकि UX मायने रखता है)।
"use client";
import { useEffect, useRef, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { RouterOutputs } from "@acme/api";
import { useTRPC } from "~/trpc/react";
export default function ChatPage() {
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const endRef = useRef<HTMLDivElement>(null);
const trpc = useTRPC();
const queryClient = useQueryClient();
// Automatic data fetching with caching
const { data: messages } = useQuery(trpc.chat.getMessages.queryOptions());
// Mutation with optimistic updates
const sendMsg = useMutation(
trpc.chat.sendChat.mutationOptions({
onSuccess: async () => {
await queryClient.invalidateQueries(trpc.chat.pathFilter());
setInput("");
setLoading(false);
},
onError: (err) => {
console.error("Failed:", err);
setLoading(false);
},
}),
);
// Auto-scroll to latest message
useEffect(() => {
endRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messages]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!input.trim() || loading) return;
setLoading(true);
sendMsg.mutate({ content: input.trim() });
};
return (
<div className="flex h-screen flex-col bg-gray-50">
{/* Header */}
<div className="border-b bg-white p-4">
<h1 className="text-xl font-bold">AI Chat</h1>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto p-4">
<div className="mx-auto max-w-4xl space-y-4">
{messages?.map((m: RouterOutputs["chat"]["getMessages"][number]) => (
<div key={m.id} className={m.role === "user" ? "text-right" : ""}>
<div
className={`inline-block rounded-2xl px-4 py-3 ${
m.role === "user"
? "bg-blue-500 text-white"
: "bg-white border shadow-sm"
}`}
>
<p className="whitespace-pre-wrap">{m.content}</p>
</div>
</div>
))}
{loading && (
<div className="flex gap-2">
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400" />
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:0.2s]" />
<div className="h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:0.4s]" />
</div>
)}
<div ref={endRef} />
</div>
</div>
{/* Input */}
<form onSubmit={handleSubmit} className="border-t bg-white p-4">
<div className="mx-auto flex max-w-4xl gap-2">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="Type your message..."
className="flex-1 rounded-lg border px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:outline-none"
disabled={loading}
/>
<button
type="submit"
disabled={!input.trim() || loading}
className="rounded-lg bg-blue-500 px-6 py-3 font-medium text-white hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed"
>
Send
</button>
</div>
</form>
</div>
);
}
Homepage को न भूलें। apps/nextjs/src/app/page.tsx को update करें:
import Link from "next/link";
export default function HomePage() {
return (
<main className="flex min-h-screen items-center justify-center bg-gradient-to-b from-blue-500 to-blue-700">
<div className="text-center text-white">
<h1 className="text-5xl font-bold">AI Chatbot</h1>
<p className="mt-4 text-xl">Built with T3 Turbo + Gemini</p>
<Link
href="/chat"
className="mt-8 inline-block rounded-full bg-white px-10 py-3 font-semibold text-blue-600 hover:bg-gray-100 transition"
>
Start Chatting
</Link>
</div>
</main>
);
}
pnpm dev run करें और http://localhost:3000 पर जाएँ। "Start Chatting" पर क्लिक करें और आपके पास एक काम करने वाला AI chatbot है।
tRPC का जादू: ध्यान दें कि हमने कभी कोई API fetch नहीं लिखा? कोई fetch() calls नहीं, कोई URL strings नहीं, कोई manual error handling नहीं। TypeScript को पता है कि sendMsg.mutate() क्या expect करता है। यदि आप backend input schema बदलते हैं, तो आपका frontend एक compile error throw करेगा। यही भविष्य है।
Step 6: Soul (जान) डालना (The "Vibe" Check)
एक साधारण (generic) assistant बोरिंग होता है। एक साधारण assistant को लोग delete कर देते हैं। LLMs की खूबसूरती यह है कि वे बेहतरीन role-players होते हैं।
मैंने पाया है कि अपने bot को एक मजबूत राय (opinion) देने से वह 10 गुना अधिक engaging हो जाता है। बस "You are helpful" prompt मत दीजिए। एक personality के लिए prompt करें।
चलिए एक persona accept करने के लिए backend को modify करते हैं। packages/api/src/router/chat.ts को update करें:
const PROMPTS = {
default: "You are a helpful AI assistant. Be concise and clear.",
luffy:
"You are Monkey D. Luffy from One Piece. You're energetic, optimistic, love meat and adventure. You often say 'I'm gonna be King of the Pirates!' Speak simply and enthusiastically.",
stark:
"You are Tony Stark (Iron Man). You're a genius inventor, witty, and sarcastic. You love technology and often mention Stark Industries. Call people 'kid' or 'buddy'. Be charming but arrogant.",
};
export const chatRouter = {
sendChat: publicProcedure
.input(
z.object({
content: z.string().min(1).max(10000),
character: z.enum(["default", "luffy", "stark"]).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
// Pick the personality
const systemPrompt = PROMPTS[input.character || "default"];
await ctx.db
.insert(Message)
.values({ role: "user", content: input.content });
const history = await ctx.db
.select()
.from(Message)
.orderBy(desc(Message.createdAt))
.limit(10);
const { text } = await generateText({
model: google("gemini-1.5-flash"),
system: systemPrompt, // ← Dynamic prompt
messages: history.reverse().map((m) => ({
role: m.role as "user" | "assistant",
content: m.content,
})),
});
return await ctx.db
.insert(Message)
.values({ role: "assistant", content: text })
.returning();
}),
// ... rest stays the same
};
Character selection पास करने के लिए frontend को update करें:
// In ChatPage component, add state for character
const [character, setCharacter] = useState<"default" | "luffy" | "stark">("default");
// Update the mutation call
sendMsg.mutate({ content: input.trim(), character });
// Add a dropdown before the input:
<select
value={character}
onChange={(e) => setCharacter(e.target.value as any)}
className="rounded-lg border px-3 py-2"
>
<option value="default">🤖 Default</option>
<option value="luffy">👒 Luffy</option>
<option value="stark">🦾 Tony Stark</option>
</select>
अब आपने सिर्फ एक chatbot नहीं बनाया है; आपने एक character interaction platform बनाया है। वह एक product है।
तकनीकी विवरण जिनकी आपको वास्तव में परवाह है
Prisma का उपयोग क्यों नहीं किया?
Prisma बहुत अच्छा है, लेकिन Drizzle तेज़ है। हम 2-3 गुना query performance की बात कर रहे हैं। जब आप एक solo founder होते हैं, तो हर मिलीसेकंड compound होता है। साथ ही, Drizzle के SQL-जैसे syntax का मतलब है कम मानसिक बोझ।
Streaming responses के बारे में क्या?
Vercel AI SDK बॉक्स से बाहर ही streaming को support करता है। generateText को streamText से बदलें और frontend पर useChat hook का उपयोग करें। मैंने इसे यहाँ छोड़ दिया क्योंकि एक tutorial के लिए, request/response सरल है। लेकिन production में? सब कुछ stream करें। Users streaming को "तेज़" मानते हैं, भले ही कुल समय समान हो।
Context window management
अभी हम पिछले 10 messages ले रहे हैं। यह तब तक काम करता है जब तक यह नहीं करता। यदि आप एक गंभीर product बना रहे हैं, तो एक token counter implement करें और history को dynamically adjust करें। AI SDK में इसके लिए utilities हैं।
import { anthropic } from "@ai-sdk/anthropic";
const { text } = await generateText({
model: anthropic("claude-3-5-sonnet-20241022"),
maxTokens: 1000, // Control costs
// ...
});
Database connection pooling
Local Postgres dev के लिए ठीक है। Production के लिए, Vercel Postgres या Supabase का उपयोग करें। वे connection pooling को अपने आप संभालते हैं। Serverless + database connections एक जाल है—इसे खुद manage न करें।
व्यावहारिक निष्कर्ष (Practical Takeaways)
यदि आप इसे पढ़ रहे हैं और आपको कोड करने की खुजली हो रही है, तो यहाँ मेरी सलाह है:
- शून्य से शुरुआत न करें। Boilerplate गति (momentum) का दुश्मन है। T3 Turbo या इसी तरह के scaffolding का उपयोग करें।
- Type safety ही स्पीड है। यह पहले घंटे के लिए धीमा लगता है, और अगले दस वर्षों के लिए तेज़। यह उन bugs को पकड़ता है जो आमतौर पर demo के दौरान होते हैं।
- Context ही कुंजी है। History के बिना एक chatbot सिर्फ एक फैंसी सर्च बार है। हमेशा पिछले कुछ messages को LLM को पास करें।
- Personality > features. एक bot जो Tony Stark जैसा लगता है, उसे 10 अतिरिक्त features वाले generic bot की तुलना में अधिक engagement मिलेगा।
गन्दी वास्तविकता (The Messy Reality)
इसे बनाना पूरी तरह से आसान नहीं था। मैंने शुरू में database connection string को गड़बड़ कर दिया और 20 मिनट यह सोचने में बिताए कि Drizzle मुझ पर क्यों चिल्ला रहा है। मैंने Gemini पर एक rate limit भी हिट की क्योंकि मैं शुरू में बहुत अधिक history भेज रहा था (सबक: हमेशा .limit(5) के साथ शुरू करें और scale up करें)।
Loading animation? उसे सही करने में मुझे तीन कोशिशें लगीं क्योंकि CSS animations आज भी, किसी तरह, 2024 में भी काले जादू (black magic) से कम नहीं हैं।
लेकिन बात यह है: क्योंकि मैं एक मजबूत stack का उपयोग कर रहा था, वे logic की समस्याएं थीं, structural समस्याएं नहीं। नींव मजबूत रही। मुझे कभी भी पूरे API को refactor नहीं करना पड़ा क्योंकि मैंने गलत abstraction चुना था।
Ship It
हम building के सुनहरे दौर में जी रहे हैं। Tools शक्तिशाली हैं, AI स्मार्ट है, और प्रवेश की बाधा (barrier to entry) पहले कभी इतनी कम नहीं थी।
अब आपके पास कोड है। आपके पास stack है। आप tradeoffs को समझते हैं।
जाओ कुछ ऐसा बनाओ जो अस्तित्व में नहीं होना चाहिए, और इसे डिनर से पहले ship कर दो।
कुल build time: ~2 घंटे लिखी गई वास्तविक कोड की लाइनें: ~200 Production में मिले Bugs: 0 (अब तक)
T3 stack + Gemini सिर्फ तेज़ नहीं है—यह सबसे अच्छे तरीके से boring है। कोई सरप्राइज़ नहीं। कोई "works on my machine" नहीं। बस building।
Happy coding.
Resources:
Full code: github.com/giftedunicorn/my-chatbot
Excerpt: जटिलता startups को मार देती है। यहाँ बताया गया है कि कैसे T3 Stack और Google Gemini का उपयोग करके घंटों में (हफ़्तों में नहीं) एक production-ready AI ऐप बनाया जाए।
इसे साझा करें

Feng Liu
shenjian8628@gmail.com