Forgekit: An Open-Source Next.js AI Starter Kit with RAG and pgvector A developer released Forgekit, an MIT-licensed open-source Next.js AI starter kit that ships with streaming chat, a five-provider model picker, Better Auth sign-in, per-user rate limiting, token usage tracking, and retrieval-augmented generation over uploaded PDFs, Markdown and text using PostgreSQL with pgvector and an HNSW cosine index. The kit also exposes an MCP endpoint at POST /api/mcp with a search_knowledge tool behind a bearer key, and includes Docker deployment with migrations on boot, Vitest unit tests, a Playwright smoke test and GitHub Actions CI. Cross-posted from my blog. The original, with any updates, lives on fabwaseem.com https://www.fabwaseem.com/blog/nextjs-ai-starter-kit . Repo: github.com/fabwaseem/forgekit https://github.com/fabwaseem/forgekit , MIT licensed. Clone it, run one setup script, and you have a working AI chat app on your own machine. A Next.js AI starter kit is a ready-to-run Next.js project that already wires up the hard parts of an AI app: streaming chat, model providers, auth, a database, retrieval over your own documents and a deploy path. Forgekit is my open-source Next.js AI starter kit. It is built on Next.js 16, the AI SDK, Better Auth, Drizzle and PostgreSQL with pgvector, and it runs on Docker, Coolify or Vercel. This post explains what Forgekit includes, how it is put together, how it compares with other options, and how to run, extend and deploy it. Every number here comes from building and running the repo myself. A Next.js AI starter kit is a template repository that gives you a working AI application on day one instead of a blank create-next-app . A good one includes streaming chat UI, a server route that calls a model, saved conversations, authentication, a database, and a clear way to add providers and deploy. You start from working code and change it, rather than gluing libraries together yourself. The value is in the boring parts. Streaming a reply is easy. Saving the conversation correctly, limiting abuse, scoping documents to a user, tracking token usage and shipping a container that migrates its own database take most of the time. Forgekit includes a streaming chat with saved threads, a model picker across five providers, three built-in tools, document upload with retrieval-augmented generation RAG on pgvector, email and GitHub sign-in, per-user rate limiting, token usage by model, and an MCP endpoint that exposes your knowledge base to other AI clients. It ships with Docker, tests and CI. | Feature | What you get | Built with | |---|---|---| | Streaming chat | Saved threads, message parts, stop button | AI SDK UI useChat , streamText | | Model picker | OpenAI, Anthropic, Gemini, xAI, Ollama. A provider shows only when its key or URL is set | @ai-sdk/ , ollama-ai-provider-v2 | | Tools | Weather Open-Meteo , public page fetch, calculator, each rendered as a card | AI SDK tool with Zod schemas | | RAG | PDF, Markdown and text uploads, chunked, embedded and searched with numbered citations | pgvector, HNSW cosine index, Drizzle | | Auth | Email and password, plus GitHub OAuth when its env vars are set | Better Auth | | Limits and usage | Per-user sliding window, token totals per model | Upstash Redis if set, Postgres otherwise | | MCP | POST /api/mcp with a search knowledge tool behind an fk bearer key | MCP TypeScript SDK | | Ops | Health check, migrations on boot, standalone output | Docker, output: 'standalone' | | Quality | Unit tests, Playwright smoke test, ESLint, GitHub Actions | Vitest, Playwright | Pinned versions at the time of writing: Next.js 16.4.2, React 19.3, ai 7.0.142, Better Auth 1.7.10, Drizzle ORM 0.45.4 and the MCP SDK 1.32.1. To run Forgekit locally, install Node.js 22, pnpm and Docker, then clone the repo and run the setup script. It copies .env.example to .env , generates an auth secret, starts PostgreSQL with pgvector through Docker Compose, installs dependencies and applies the Drizzle migrations. Then pnpm dev serves the app on port 3000. git clone https://github.com/fabwaseem/forgekit.git cd forgekit bash scripts/setup.sh pnpm dev Open http://localhost:3000 , create an account, and add at least one provider to .env . The cheapest way to try it is a local model: .env OLLAMA BASE URL=http://127.0.0.1:11434 OLLAMA MODELS=llama3.2,qwen2.5 or any hosted provider OPENAI API KEY=sk-... The model picker only lists providers you have configured, so you never see a model that would fail. On a clean checkout I ran pnpm install --frozen-lockfile , pnpm typecheck , pnpm lint , pnpm test 6 files, 15 tests and pnpm build , and all passed. I then ran the app against a pgvector Postgres and a stubbed Ollama endpoint. Sign-up, chat creation, a streamed reply, saved history, usage totals and a document upload all worked, both with pnpm start and in the Docker image. That smoke test found two bugs, which are now fixed on main . Assistant messages were saved with an empty id, so the second reply in a thread failed to save. And pnpm db:migrate and pnpm start did not read .env . That is what testing a starter is for. Forgekit's architecture is a single Next.js App Router app and a single PostgreSQL database. The browser streams messages to one route handler, /api/chat . It checks the session and rate limit, then calls streamText with the chosen model and tools. Tool calls, including retrieval over pgvector, run on the server. When the stream ends, the messages and token usage are written to Postgres. Keeping everything in Postgres is a deliberate choice. Chats, messages, documents, vector chunks, API keys, usage and the rate-limit fallback all live in one database you already know how to back up. pgvector is an extension, not another service. The chat route loads the saved thread, appends the new user message, streams the model's reply and saves the result when the stream ends. Here is the core of app/api/chat/route.ts : js const result = streamText { model: resolved.model, system: SYSTEM PROMPT, messages: await convertToModelMessages messages , tools, stopWhen: isStepCount 6 , onEnd: async { totalUsage } = { await recordUsage { userId: user.id, modelId: body.modelId , inputTokens: totalUsage.inputTokens, outputTokens: totalUsage.outputTokens, totalTokens: totalUsage.totalTokens, } ; }, } ; return createUIMessageStreamResponse { stream: toUIMessageStream { stream: result.stream, originalMessages: messages, sendSources: true, generateMessageId: = crypto.randomUUID , onEnd: async { messages: nextMessages, isAborted } = { if isAborted return; await saveMessages chat.id, nextMessages, body.modelId ; }, } , } ; stopWhen: isStepCount 6 caps how many tool-call rounds a single answer can take, so a looping model cannot run up your bill. generateMessageId is the fix mentioned above. Without it, assistant messages have no stable id to persist. The AI SDK's message persistence guide https://ai-sdk.dev/docs/ai-sdk-ui/chatbot-message-persistence covers the same pattern. RAG in Forgekit works in two steps. On upload, the text is split into 800-character chunks with 120 characters of overlap, embedded into 1536-dimension vectors and stored in a pgvector column with an HNSW cosine index. At question time, the searchKnowledge tool embeds the query, finds the closest chunks for that user only and returns them as numbered citations. The search itself is a short Drizzle query using cosineDistance , following Drizzle's vector similarity guide https://orm.drizzle.team/docs/guides/vector-similarity-search : js const embedding = await embedQuery query ; const similarity = sql