{"slug": "forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector", "title": "Forgekit: An Open-Source Next.js AI Starter Kit with RAG and pgvector", "summary": "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.", "body_md": "*Cross-posted from my blog. The original, with any updates, lives on [fabwaseem.com](https://www.fabwaseem.com/blog/nextjs-ai-starter-kit).*\n\n**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.\n\nA **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.\n\nThis 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.\n\nA 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.\n\nThe 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.\n\nForgekit 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.\n\n| Feature | What you get | Built with | \n|---|---|---|\n| Streaming chat | Saved threads, message parts, stop button | AI SDK UI ( `useChat` ),`streamText` | \n| Model picker | OpenAI, Anthropic, Gemini, xAI, Ollama. A provider shows only when its key or URL is set | `@ai-sdk/*` ,`ollama-ai-provider-v2` | \n| Tools | Weather (Open-Meteo), public page fetch, calculator, each rendered as a card | AI SDK `tool()` with Zod schemas | \n| RAG | PDF, Markdown and text uploads, chunked, embedded and searched with numbered citations | pgvector, HNSW cosine index, Drizzle | \n| Auth | Email and password, plus GitHub OAuth when its env vars are set | Better Auth | \n| Limits and usage | Per-user sliding window, token totals per model | Upstash Redis if set, Postgres otherwise | \n| MCP | `POST /api/mcp` with a`search_knowledge` tool behind an`fk_` bearer key | MCP TypeScript SDK | \n| Ops | Health check, migrations on boot, standalone output | Docker, `output: 'standalone'` | \n| Quality | Unit tests, Playwright smoke test, ESLint, GitHub Actions | Vitest, Playwright | \n\nPinned 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.\n\nTo 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.\n\n```\ngit clone https://github.com/fabwaseem/forgekit.git\ncd forgekit\nbash scripts/setup.sh\npnpm dev\n```\n\nOpen `http://localhost:3000`, create an account, and add at least one provider to `.env`. The cheapest way to try it is a local model:\n\n```\n# .env\nOLLAMA_BASE_URL=http://127.0.0.1:11434\nOLLAMA_MODELS=llama3.2,qwen2.5\n# or any hosted provider\nOPENAI_API_KEY=sk-...\n```\n\nThe model picker only lists providers you have configured, so you never see a model that would fail.\n\nOn 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.\n\nThat 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.\n\nForgekit'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.\n\nKeeping 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.\n\nThe 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`:\n\n``` js\nconst result = streamText({\n  model: resolved.model,\n  system: SYSTEM_PROMPT,\n  messages: await convertToModelMessages(messages),\n  tools,\n  stopWhen: isStepCount(6),\n  onEnd: async ({ totalUsage }) => {\n    await recordUsage({\n      userId: user.id,\n      modelId: body.modelId!,\n      inputTokens: totalUsage.inputTokens,\n      outputTokens: totalUsage.outputTokens,\n      totalTokens: totalUsage.totalTokens,\n    });\n  },\n});\n\nreturn createUIMessageStreamResponse({\n  stream: toUIMessageStream({\n    stream: result.stream,\n    originalMessages: messages,\n    sendSources: true,\n    generateMessageId: () => crypto.randomUUID(),\n    onEnd: async ({ messages: nextMessages, isAborted }) => {\n      if (isAborted) return;\n      await saveMessages(chat.id, nextMessages, body.modelId!);\n    },\n  }),\n});\n```\n\n`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.\n\nRAG 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.\n\nThe search itself is a short Drizzle query using `cosineDistance`, following [Drizzle's vector similarity guide](https://orm.drizzle.team/docs/guides/vector-similarity-search):\n\n``` js\nconst embedding = await embedQuery(query);\nconst similarity = sql<number>`1 - (${cosineDistance(chunk.embedding, embedding)})`;\n\nconst rows = await db\n  .select({ content: chunk.content, documentName: document.name, score: similarity })\n  .from(chunk)\n  .innerJoin(document, eq(document.id, chunk.documentId))\n  .where(eq(chunk.userId, userId))\n  .orderBy(desc(similarity))\n  .limit(Math.min(Math.max(limit, 1), 8));\n```\n\nTwo details matter. First, the `where(eq(chunk.userId, userId))` filter means one user can never retrieve another user's documents. Second, the index is HNSW with `vector_cosine_ops`, the approximate index type recommended in the [pgvector README](https://github.com/pgvector/pgvector) for fast search without a training step.\n\nEmbeddings use the first available of OpenAI (`text-embedding-3-small`), Google (` gemini-embedding-001`, with `outputDimensionality` set to 1536) or Ollama, unless `EMBEDDING_PROVIDER` picks one. The column is fixed at 1536 dimensions. For fully local RAG, pick an Ollama embedding model that outputs 1536 dimensions, or change the column and its migration together. The common `nomic-embed-text` model outputs 768.\n\nTo add a provider to Forgekit, install its AI SDK package, add its models to the catalog in `lib/ai/models.ts` with the env var that enables them, and add one `case` to `languageModelFor` in `lib/ai/providers.ts`. The model picker, the `/api/models` endpoint and the chat route pick it up automatically, because they all read the same catalog.\n\nHere is the whole provider switch:\n\n```\nexport function languageModelFor(option: ModelOption) {\n  switch (option.provider) {\n    case \"openai\":\n      return openai(option.model);\n    case \"anthropic\":\n      return anthropic(option.model);\n    case \"google\":\n      return google(option.model);\n    case \"xai\":\n      return xai(option.model);\n    case \"ollama\":\n      return createOllama({\n        baseURL: ollamaBaseURL(process.env.OLLAMA_BASE_URL ?? \"http://127.0.0.1:11434\"),\n      }).chat(option.model);\n  }\n}\n```\n\nFor example, to add Mistral you would run `pnpm add @ai-sdk/mistral`, add `\"mistral\"` to `PROVIDERS`, add `{ provider: \"mistral\", model: \"mistral-large-latest\", label: \"Mistral Large\", env: \"MISTRAL_API_KEY\" }` to `CATALOG`, and add `case \"mistral\": return mistral(option.model);`. TypeScript then flags any place that does not handle the new provider. The [AI SDK docs](https://ai-sdk.dev/docs/introduction) list every official provider package.\n\nA tool is a description, a Zod input schema and an `execute` function, defined with the AI SDK's `tool()` helper inside `createChatTools` in `lib/ai/tools.ts`. The model decides when to call it, the server runs it, and the result is streamed back as a message part that the UI can render as a card.\n\n```\nweather: tool({\n  description: \"Get the current weather for a city or place using a public forecast.\",\n  inputSchema: z.object({\n    location: z.string().min(1).describe(\"City or place name\"),\n  }),\n  execute: async ({ location }) => {\n    // geocode with Open-Meteo, then fetch current conditions\n  },\n}),\n```\n\nTools receive the `userId`, so anything they read or write can be scoped to the signed-in user. The built-in `webFetch` tool also refuses private network addresses, which you should keep in mind for any tool that fetches URLs a model chose.\n\nForgekit's MCP endpoint lets other AI clients, such as Claude Desktop, Cursor or your own agent, search your Forgekit knowledge base. `POST /api/mcp` speaks the [Model Context Protocol](https://modelcontextprotocol.io/introduction) and exposes one tool, `search_knowledge`. Callers authenticate with a personal `fk_` bearer key created on the Usage page, and only the hash of each key is stored.\n\nIf you want to build one of these from scratch, I wrote a full guide on [how to build an MCP server in Next.js](https://www.fabwaseem.com/blog/build-mcp-server-nextjs).\n\nForgekit's closest alternative is Vercel's open-source [Chatbot template](https://github.com/vercel/chatbot), a full-featured, hackable Next.js AI chatbot that is a great choice on Vercel. Forgekit differs mainly in being self-hosting first: direct provider SDKs plus local Ollama, retrieval in your own Postgres, and one Docker image. Both are good starting points. The right one depends on where you deploy.\n\n|  | Forgekit | Vercel Chatbot template | Build from scratch | \n|---|---|---|---|\n| Model access | Direct provider SDKs (5) plus local Ollama | Many providers through Vercel AI Gateway | Whatever you wire up | \n| Database | Your PostgreSQL with pgvector | Neon serverless Postgres | Your choice | \n| RAG over your documents | Built in, pgvector with citations | Not the template's focus | You build it | \n| Auth | Better Auth (email, GitHub) | Auth.js | You build it | \n| File storage | Text extracted into Postgres | Vercel Blob | Your choice | \n| Best fit | Docker, Coolify or any VPS, plus Vercel | Vercel with its managed services | Full control, most work | \n| License | MIT | See the repo | Yours | \n\nComparison based on each project's README as of October 2026. Check the repos for current details.\n\nIf you are all-in on Vercel's platform, the Vercel template integrates its services seamlessly. If you want to own the whole stack on your own server, or run models locally, Forgekit is designed for that.\n\nTo deploy Forgekit, build the included multi-stage Dockerfile, run it next to a PostgreSQL server that has the `vector` extension, and set `DATABASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, your provider keys and `RUN_MIGRATIONS=true`. The container applies the Drizzle migrations on boot and serves Next.js standalone output on port 3000, with a `/api/health` check that pings the database.\n\n```\ndocker compose up -d --build   # app + pgvector/pgvector:pg17\ncurl http://localhost:3000/api/health   # {\"ok\":true}\n```\n\nThe production image I built measured **284 MB**. It runs as a non-root user and contains only the [standalone output](https://nextjs.org/docs/app/api-reference/config/next-config-js/output), static assets and the migration files. I explain each layer in my [production Next.js Dockerfile guide](https://www.fabwaseem.com/blog/nextjs-dockerfile).\n\nOn **Coolify**, create a PostgreSQL resource using the `pgvector/pgvector` image, add the repo as a Dockerfile application, paste the env vars and deploy. My [step-by-step Coolify guide](https://www.fabwaseem.com/blog/self-host-nextjs-with-coolify) covers domains, TLS and webhooks. One gotcha: inside a container, `127.0.0.1` is the container itself, so point `OLLAMA_BASE_URL` at a reachable Ollama host.\n\nOn **Vercel**, use a hosted Postgres with pgvector, run `pnpm db:migrate` against it once, and use hosted providers, since Ollama on your laptop is not reachable from Vercel. Not sure which to choose? See [Coolify vs Vercel for Next.js](https://www.fabwaseem.com/blog/coolify-vs-vercel).\n\nForgekit suits developers who want to ship an AI product on infrastructure they control. That includes indie hackers building an AI SaaS, teams that need internal chat over company documents, and anyone who wants to try local models before paying for an API. It is also a readable reference if you just want to see how streaming, tools, RAG and auth fit together in Next.js 16.\n\nThe `lib` folder is under 1,000 lines of TypeScript, so you can read the whole core in an afternoon. More of my work is on the [projects page](https://www.fabwaseem.com/projects).\n\n**Get the code:** [github.com/fabwaseem/forgekit](https://github.com/fabwaseem/forgekit). Star it if it helps, and open an issue if something breaks.\n\n**Related:** Forgekit uses Better Auth and Drizzle. For a tested Better Auth setup on Next.js 16, see [Better Auth with Next.js 16](https://www.fabwaseem.com/blog/better-auth-nextjs), and for why Drizzle, see my [Drizzle vs Prisma benchmark](https://www.fabwaseem.com/blog/drizzle-vs-prisma).\n\n**Related:** wondering whether pgvector is enough or you need a dedicated vector database? I benchmarked pgvector against Qdrant on 100,000 OpenAI embeddings and compared Pinecone's limits in [pgvector vs Pinecone vs Qdrant](https://www.fabwaseem.com/blog/pgvector-vs-pinecone-vs-qdrant).\n\n**Related:** upgrading your own app from AI SDK 6? My [AI SDK 7 migration guide](https://www.fabwaseem.com/blog/ai-sdk-7-migration) shows what the codemod misses: it fixed 15 lines but only 1 of 9 failing tests, and OpenTelemetry went silent until I added @ai-sdk/otel.\n\nYes. Forgekit is MIT licensed, so you can use, modify and sell products built on it. Keep the license notice in your copy of the code.\n\nYes for chat. Set `OLLAMA_BASE_URL` and run models locally with Ollama. Local RAG needs an Ollama embedding model that outputs 1536 dimensions, or a change to the vector column size.\n\nNo. Embeddings are stored in PostgreSQL with the pgvector extension and an HNSW cosine index. Your chats, users and vectors live in one database.\n\nForgekit uses Next.js 16 with the App Router, React 19 and AI SDK 7. Exact versions are pinned in `package.json` and listed in the README.\n\nYes. Use a hosted Postgres that supports pgvector, run the migrations once, and configure hosted model providers. Ollama is only practical on Docker or a VPS.\n\nForgekit has a per-user sliding-window rate limit (30 requests per 60 seconds by default, set with `RATE_LIMIT_REQUESTS` and `RATE_LIMIT_WINDOW_SECONDS`). It also records token usage per model, so you can add hard quotas on top.\n\n*Originally published on [fabwaseem.com](https://www.fabwaseem.com/blog/nextjs-ai-starter-kit).*", "url": "https://wpnews.pro/news/forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector", "canonical_source": "https://dev.to/fabwaseem/forgekit-an-open-source-nextjs-ai-starter-kit-with-rag-and-pgvector-p1p", "published_at": "2026-10-25 22:03:33+00:00", "updated_at": "2026-10-25 22:13:22.679309+00:00", "lang": "en", "topics": ["ai-tools", "ai-agents", "agent-protocols", "developer-tools", "ai-products"], "entities": ["Forgekit", "Next.js", "AI SDK", "Better Auth", "Drizzle ORM", "pgvector", "Vercel", "GitHub"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector", "markdown": "https://wpnews.pro/news/forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector.md", "text": "https://wpnews.pro/news/forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector.txt", "jsonld": "https://wpnews.pro/news/forgekit-an-open-source-next-js-ai-starter-kit-with-rag-and-pgvector.jsonld"}}