I Built My Own ChatGPT in Under 2,000 Lines of Code. The Hard Part Wasn’t the AI.

If you’ve used ChatGPT, you’ve had the thought. The one that creeps in around 2am when you’re staring at yet another conversation you can’t organize, another model you can’t switch to, another feature you wish existed but don’t control.

“I should just build my own.”

Most people leave it there. A passing fantasy. Because building an AI chat product sounds like something that requires a team, a budget, and a quarterly OKR cycle. Right?

Wrong. I did it in an afternoon. Frontend and backend combined: under 2,000 lines of code. And the thing that almost broke me wasn’t the AI — it was the UX details nobody talks about.

When you control the data, the models, and the interface, you’re not just using AI — you’re wielding it.

Here’s what actually happened.

Why I Refused to Settle

The market is flooded with AI chat products. So why build one? Three reasons, and if you’re a developer or product manager, you already feel them.

First: data. Every conversation lives in my own database. No quiet training on my prompts. No data leakage I can’t audit. Second: model freedom. I plugged into six different models through Alibaba Cloud’s Bailian platform — Qwen, DeepSeek, GLM, Kimi — and switch between them with a dropdown. Third: customization. When you want to build an internal team assistant or serve a vertical use case, off-the-shelf products hit a wall fast.

The convenience of a managed product always comes with a tax: someone else holds the steering wheel.

I wanted the wheel.

The Stack: Boring on Purpose

Here’s where most people overthink it. They chase the newest framework, the hottest library, the architecture that looks impressive in a blog post.

I did the opposite.

Backend: FastAPI — because it’s the fastest way to write APIs in Python and handles async streaming beautifully. Database: SQLite — zero config, and if I ever need PostgreSQL, the migration is seamless. Frontend: Next.js with Tailwind, state managed by Zustand (lighter than Redux by a mile). AI integration through Bailian’s OpenAI-compatible API, meaning one codebase handles every model.

Mature and stable beats novel and fragile. Every time. You’re building a product, not auditioning for a tech demo.

The architecture is simple: two layers, connected by Server-Sent Events (SSE) for streaming. That’s it. No microservices. No Kubernetes. No over-engineering.

Where It Almost Killed Me

Here’s the part nobody puts in the tutorial.

React 18’s Strict Mode runs state update callbacks twice. My streaming-end callback saved each AI message — twice. Every response was duplicated in the database. I spent hours on this before using a ref to track content synchronously and dodge the double-fire.

Then there was the “first conversation” detection. I was saving the message before counting the conversation length, which meant every conversation registered as “not the first” — so auto-title generation never triggered. The order of operations matters more than the operations themselves.

And the sneakiest bug: when using reasoning models (the ones that show a “thinking process”), I was sending the previous conversation’s thinking steps back to the model as context. The model’s response quality tanked. The fix? Only send the formal answers as history. Keep the thinking process for the frontend display only.

Every polished product you love is just someone who suffered through a thousand small details you’ll never see.

The reasoning model’s “thinking” and “answering” come back as separate SSE events. You have to parse them into two different streams, render the thinking in a collapsible block, and make it feel natural. That single UX detail — separating thought from answer — was harder than wiring up the AI itself.

What This Actually Means

The finished product has a sidebar for conversation history, a model dropdown at the top (reasoning models flagged), streaming responses with visible thinking processes, syntax-highlighted code blocks, dark mode, and mobile support. It looks and feels like ChatGPT. Zero learning curve.

If you’re building an internal AI assistant, integrating specific models for a vertical, or adding custom features to a chat system — this architecture is your blueprint. Every module is independently replaceable and extensible.

But here’s the bigger point.

Technology stops being a black box the moment you build one yourself. The AI models aren’t the moat. The infrastructure isn’t the moat. The moat is in the thousand tiny UX decisions that make a product feel effortless — and those are accessible to anyone willing to sweat the details.

You don’t need a team. You don’t need a budget. You need an afternoon, a stable stack, and the willingness to debug the unglamorous stuff.

The AI revolution isn’t just for companies with billion-dollar valuations. It’s for anyone who decides to pick up the tools and build.

FAQ

Q: Isn't this just a toy project that breaks under real load?

A: The architecture is production-ready. SQLite handles thousands of conversations, and swapping to PostgreSQL is a config change. SSE streaming scales horizontally. The only real limit is your API rate limits — which is true whether you're using this or ChatGPT itself.

Q: Why not just use ChatGPT's API directly and skip the whole frontend?

A: Because then you're locked into one model, one vendor's UX decisions, and zero control over your data. If you're building internal tools or serving a specific vertical, you need model switching, custom history management, and data sovereignty. The frontend IS the product.

Q: Is the AI layer actually commoditized at this point?

A: Absolutely. With OpenAI-compatible APIs, switching models is literally changing one ID string. The real differentiator in AI products is now UX polish, data control, and workflow integration — not the model itself. The model is plumbing. The experience is the moat.

📎 Source: View Source