{"slug": "building-a-streaming-chatbot-with-node-js", "title": "Building a Streaming Chatbot with Node.js", "summary": "A developer published a tutorial showing how to build a streaming LLM chatbot in under 60 lines of Node.js using the OpenAI SDK pointed at Groq's API endpoint. The guide demonstrates the stream: true pattern, in which the server sends response fragments as delta objects token by token, and uses process.stdout.write to render text live rather than waiting for the full completion. It also covers credential handling via .env files, adding a system prompt, and swapping models or providers with minimal code changes.", "body_md": "Most backend tutorials show synchronous calls: the API generates the full response and only then prints it, after several seconds of waiting. That’s not how real chat products work — in interfaces like ChatGPT or Claude, the response starts appearing word by word, before it’s even complete. That’s called **streaming**, and it’s what we’re going to build today.\n\n**What you’ll learn**\n\n**Requirements**\n\n**Step 1: Project setup**\n\n```\nmkdir llm-chatbot-tutorial\ncd llm-chatbot-tutorial\nnpm init -y\n...\n```\n\nInstall the dependencies. We use the openai package because Groq’s API is compatible with its format — the same SDK, just pointed at a different URL:\n\n```\nnpm install openai dotenv\n```\n\n**Step 2: Protect your credentials from the start**\n\nCreate .gitignore :\n\n```\nnode_modules/\n.env\n```\n\nCreate .env.example (this one does get committed):\n\n```\nGROQ_API_KEY=your_api_key_here\n```\n\nAnd your real .env (this one is never committed):\n\n```\nGROQ_API_KEY=your_api_key_here\n```\n\n**Step 3: The chatbot code**\n\nCreate chatbot.js :\n\n``` js\nrequire(\"dotenv\").config();\nconst OpenAI = require(\"openai\");\n\nconst client = new OpenAI({\n  apiKey: process.env.GROQ_API_KEY,\n  baseURL: \"https://api.groq.com/openai/v1\",\n});\n\n// Check console.groq.com/docs/models for the current model list —\n// providers rotate models periodically.\nconst MODEL = \"openai/gpt-oss-120b\";\n\nasync function chat(userMessage) {\n  console.log(`\\n📝 User: ${userMessage}\\n`);\n  console.log(\"🤖 Assistant: \");\n\n  try {\n    const stream = await client.chat.completions.create({\n      model: MODEL,\n      stream: true,\n      messages: [\n        { role: \"user\", content: userMessage },\n      ],\n    });\n\n    for await (const chunk of stream) {\n      const text = chunk.choices[0]?.delta?.content || \"\";\n      process.stdout.write(text);\n    }\n\n    console.log(\"\\n\");\n  } catch (error) {\n    console.error(\"Error:\", error.message);\n  }\n}\n\nasync function main() {\n  console.log(\"=== Streaming Chatbot with Node.js ===\\n\");\n  await chat(\"What is the capital of Argentina?\");\n  await chat(\"Explain what Node.js is in two sentences.\");\n  await chat(\"Give me 3 tips for learning to code.\");\n  console.log(\"\\n✅ Done!\");\n}\n\nmain();\n```\n\n**What stream: true actually does**\n\nWithout streaming, the API waits until the full response is generated and only then returns it as a single block. With stream: true , the server instead sends the response as a sequence of small fragments ( delta objects) as the model generates each token.\n\nThe for await loop processes each fragment as soon as it arrives. We use\n\nprocess.stdout.write(text) instead of console.log(text) on purpose: console.log adds a line break after every call, which would break the live-typing visual effect.\n\nThis pattern stays the same across providers — only the SDK import and a couple of field names change. Once you understand this shape, you can swap providers with minimal effort.\n\n**Step 4: Run it**\n\n```\nnode chatbot.js\n```\n\nYou should see the three questions answered one after another, with text streaming in real time —not appearing all at once.\n\n**If you hit an error: **- Check that .env is in the project root - Confirm the variable is named exactly GROQ_API_KEY - Verify the configured model is still available at console.groq.com/docs/models\n\n**Step 5: Customize it**\n\n**Change the questions** — edit the chat(...) calls inside main() .\n\n**Add a system prompt** to control the assistant’s tone or role:\n\n```\nmessages: [\n  { role: \"system\", content: \"You are a concise, friendly coding tutor.\" },\n  { role: \"user\", content: userMessage },\n],\n```\n\n**Swap the model** — just change the MODEL constant. Always check the provider’s current model list before publishing or deploying, since free-tier models get deprecated and replaced over time.\n\n**Conclusion**\n\nYou now have a working, streaming LLM chatbot in under 60 lines of Node.js — and, more importantly, you understand the streaming pattern that powers every modern AI chat interface. This is the foundation for anything more advanced: a web-based chat UI, a Slack bot, or a backend service that talks to an LLM.\n\n**Next in this series:** containerizing this app with Docker and deploying it to the cloud.", "url": "https://wpnews.pro/news/building-a-streaming-chatbot-with-node-js", "canonical_source": "https://dev.to/whoismarce/building-a-streaming-chatbot-with-nodejs-250g", "published_at": "2026-09-18 21:45:22+00:00", "updated_at": "2026-09-18 22:22:51.069454+00:00", "lang": "en", "topics": ["large-language-models", "ai-tools", "developer-tools", "generative-ai"], "entities": ["Node.js", "Groq", "OpenAI", "ChatGPT", "Claude"], "alternates": {"html": "https://wpnews.pro/news/building-a-streaming-chatbot-with-node-js", "markdown": "https://wpnews.pro/news/building-a-streaming-chatbot-with-node-js.md", "text": "https://wpnews.pro/news/building-a-streaming-chatbot-with-node-js.txt", "jsonld": "https://wpnews.pro/news/building-a-streaming-chatbot-with-node-js.jsonld"}}