[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"academy-blogs-en-1-1-all-golang-the-series-ep150-workshop-ai-chatbot-gin-framework-all--*":3,"academy-blog-translations-uz642au85qm6tzz":88},{"data":4,"page":74,"perPage":74,"totalItems":74,"totalPages":74},[5],{"alt":6,"collectionId":7,"collectionName":8,"content":9,"cover_image":10,"cover_image_path":11,"created":12,"created_by":13,"expand":14,"id":82,"keywords":83,"locale":54,"published_at":84,"scheduled_at":70,"school_blog":78,"short_description":85,"status":76,"title":86,"updated":87,"updated_by":13,"slug":79,"views":81},"Architecture of an AI Chatbot Streaming System using Gin Framework and OpenAI API","sclblg987654321","school_blog_translations","\u003Cp>We have finally reached EP.150, Gophers! Congratulations on gathering all the essential puzzle pieces—from managing environments with Docker and streaming with Channels to budget control via Token Management.\u003C\u002Fp>\u003Cp>In this episode, it's time to assemble everything we've learned into our first practical workshop. We will build a Simple AI Chatbot Server that supports real-time data streaming using the \u003Cstrong>Gin Framework\u003C\u002Fstrong> and the \u003Cstrong>OpenAI SDK\u003C\u002Fstrong>.\u003C\u002Fp>\u003Ch2>Project Structure\u003C\u002Fh2>\u003Cp>To keep our codebase clean and maintainable, we will adhere to a standard, lightweight Go project structure:\u003C\u002Fp>\u003Cp>Plaintext\u003C\u002Fp>\u003Cpre>\u003Ccode>ai-chatbot-server\u002F\n├── main.go\n├── handlers\u002F\n│   └── chat.go\n├── go.mod\n└── go.sum\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cp>Initialize the project and install the necessary dependencies via your terminal:\u003C\u002Fp>\u003Cp>Bash\u003C\u002Fp>\u003Cpre>\u003Ccode>go mod init ai-chatbot-server\ngo get github.com\u002Fgin-gonic\u002Fgin\ngo get github.com\u002Fsashabaranov\u002Fgo-openai\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>The Main Driver: \u003Ccode>main.go\u003C\u002Fcode>\u003C\u002Fh2>\u003Cp>This file handles retrieving the API Key from environment variables, initializing the OpenAI Client, and setting up Gin routes to prepare the data streaming pipeline.\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>package main\n\nimport (\n\t\"log\"\n\t\"os\"\n\n\t\"ai-chatbot-server\u002Fhandlers\"\n\t\"github.com\u002Fgin-gonic\u002Fgin\"\n\t\"github.com\u002Fsashabaranov\u002Fgo-openai\"\n)\n\nfunc main() {\n\t\u002F\u002F 1. Retrieve the API Key from environment variables for security\n\tapiKey := os.Getenv(\"OPENAI_API_KEY\")\n\tif apiKey == \"\" {\n\t\tlog.Fatal(\"ERROR: OPENAI_API_KEY env variable is required\")\n\t}\n\n\t\u002F\u002F 2. Initialize the OpenAI Client\n\taiClient := openai.NewClient(apiKey)\n\n\t\u002F\u002F 3. Set up the Gin Engine\n\tr := gin.Default()\n\n\t\u002F\u002F Inject the dependency (AI Client) into the handler using a Closure\n\tr.POST(\"\u002Fapi\u002Fchat\u002Fstream\", handlers.HandleChatStream(aiClient))\n\n\tlog.Println(\"🚀 AI Chatbot Server starting on :8080...\")\n\tr.Run(\":8080\")\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>The Streaming Pipeline: \u003Ccode>handlers\u002Fchat.go\u003C\u002Fcode>\u003C\u002Fh2>\u003Cp>We will implement \u003Cstrong>Server-Sent Events (SSE)\u003C\u002Fstrong> alongside OpenAI's streaming capability to push text chunks to the client immediately as the AI generates them. Gin provides the \u003Ca rel=\"noopener noreferrer\" href=\"http:\u002F\u002Fc.Stream\">\u003Ccode>c.Stream\u003C\u002Fcode>\u003C\u002Fa>\u003Ccode>()\u003C\u002Fcode> function, making this elegant and straightforward to implement.\u003C\u002Fp>\u003Cp>Go\u003C\u002Fp>\u003Cpre>\u003Ccode>package handlers\n\nimport (\n\t\"context\"\n\t\"errors\"\n\t\"io\"\n\t\"net\u002Fhttp\"\n\n\t\"github.com\u002Fgin-gonic\u002Fgin\"\n\t\"github.com\u002Fsashabaranov\u002Fgo-openai\"\n)\n\n\u002F\u002F ChatRequest defines the incoming payload from the client\ntype ChatRequest struct {\n\tMessage string `json:\"message\" binding:\"required\"`\n}\n\nfunc HandleChatStream(client *openai.Client) gin.HandlerFunc {\n\treturn func(c *gin.Context) {\n\t\tvar req ChatRequest\n\t\tif err := c.ShouldBindJSON(&amp;req); err != nil {\n\t\t\tc.JSON(http.StatusBadRequest, gin.H{\"error\": \"Invalid request body\"})\n\t\t\treturn\n\t\t}\n\n\t\tctx := c.Request.Context()\n\n\t\t\u002F\u002F 1. Configure the request payload with streaming enabled\n\t\tstreamReq := openai.ChatCompletionRequest{\n\t\t\tModel:  openai.GPT4o, \u002F\u002F Ensure you are using the latest go-openai version, or use the \"gpt-4o\" string directly\n\t\t\tStream: true,\n\t\t\tMessages: []openai.ChatCompletionMessage{\n\t\t\t\t{\n\t\t\t\t\tRole:    openai.ChatMessageRoleUser,\n\t\t\t\t\tContent: req.Message,\n\t\t\t\t},\n\t\t\t},\n\t\t}\n\n\t\tstream, err := client.CreateChatCompletionStream(ctx, streamReq)\n\t\tif err != nil {\n\t\t\tc.JSON(http.StatusInternalServerError, gin.H{\"error\": err.Error()})\n\t\t\treturn\n\t\t}\n\t\tdefer stream.Close()\n\n\t\t\u002F\u002F Production Essential: Set headers for Server-Sent Events (SSE)\n\t\t\u002F\u002F This prevents reverse proxies (like Nginx or Cloudflare) from buffering the response.\n\t\tc.Header(\"Content-Type\", \"text\u002Fevent-stream\")\n\t\tc.Header(\"Cache-Control\", \"no-cache\")\n\t\tc.Header(\"Connection\", \"keep-alive\")\n\t\tc.Header(\"Transfer-Encoding\", \"chunked\")\n\n\t\t\u002F\u002F 2. Utilize Gin's c.Stream to flush real-time tokens continuously\n\t\tc.Stream(func(w io.Writer) bool {\n\t\t\tselect {\n\t\t\tcase &lt;-ctx.Done():\n\t\t\t\t\u002F\u002F Best Practice: If the client disconnects or cancels, exit the loop immediately\n\t\t\t\treturn false\n\n\t\t\tdefault:\n\t\t\t\tresponse, err := stream.Recv()\n\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\t\u002F\u002F Notify the client that the stream has finished successfully\n\t\t\t\t\tc.SSEvent(\"message\", \"[DONE]\")\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\tif err != nil {\n\t\t\t\t\t\u002F\u002F Broadcast the error event to the client and stop streaming\n\t\t\t\t\tc.SSEvent(\"error\", err.Error())\n\t\t\t\t\treturn false\n\t\t\t\t}\n\n\t\t\t\t\u002F\u002F Extract and stream individual text chunks\n\t\t\t\tif len(response.Choices) &gt; 0 {\n\t\t\t\t\tcontent := response.Choices[0].Delta.Content\n\t\t\t\t\tif content != \"\" {\n\t\t\t\t\t\t\u002F\u002F Deliver data chunks to the client instantly via SSE format\n\t\t\t\t\t\tc.SSEvent(\"message\", content)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn true \u002F\u002F Keep looping to fetch the next data chunk\n\t\t\t}\n\t\t})\n\t}\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Ch2>🎯 Daily Mission\u003C\u002Fh2>\u003Cp>Once your server is running (remember to set your environment variable using \u003Ccode>export OPENAI_API_KEY=\"your-key\"\u003C\u002Fcode>), you can test your streaming backend using \u003Ccode>curl\u003C\u002Fcode> in your terminal:\u003C\u002Fp>\u003Cp>Bash\u003C\u002Fp>\u003Cpre>\u003Ccode># The -N flag is crucial here; it disables curl's internal buffering, letting you see the characters flow in real-time.\ncurl -N -X POST http:\u002F\u002Flocalhost:8080\u002Fapi\u002Fchat\u002Fstream \\\n     -H \"Content-Type: application\u002Fjson\" \\\n     -d '{\"message\": \"Give me 3 concise reasons why we should write Go in an AI-First era.\"}'\n\u003C\u002Fcode>\u003C\u002Fpre>\u003Cblockquote>\u003Cp>\u003Cstrong>Pro-Tip Challenge:\u003C\u002Fstrong> Right now, our bot is stateless (it answers prompt-by-prompt). Try leveraging Go slices to implement a persistent Chat History memory array before sending the payload to OpenAI. This will make your chatbot capable of fluid, contextual conversations!\u003C\u002Fp>\u003C\u002Fblockquote>\u003Ch2>FAQ\u003C\u002Fh2>\u003Ch3>Q: Why do we explicitly set \u003Cstrong>\u003Ccode>Cache-Control: no-cache\u003C\u002Fcode>\u003C\u002Fstrong> and \u003Cstrong>\u003Ccode>Transfer-Encoding: chunked\u003C\u002Fcode>\u003C\u002Fstrong> before executing \u003Cstrong>\u003Ccode>c.Stream()\u003C\u002Fcode>\u003C\u002Fstrong>?\u003C\u002Fh3>\u003Cp>\u003Cstrong>A:\u003C\u002Fstrong> When running a Go application behind a reverse proxy (such as Nginx, Apache, or Cloudflare), these proxies naturally attempt to \"buffer\" data until they receive the entire response payload to optimize network distribution. Setting these headers explicitly tells the proxy: \u003Cem>\"Do not buffer this data. Let it pass through piece-by-piece immediately.\"\u003C\u002Fem> This guarantees smooth, low-latency rendering on the client interface.\u003C\u002Fp>\u003Ch3>Q: How does Gin’s \u003Cstrong>\u003Ccode>c.Stream()\u003C\u002Fcode>\u003C\u002Fstrong> function operate under the hood, and is it memory-safe?\u003C\u002Fh3>\u003Cp>\u003Cstrong>A:\u003C\u002Fstrong> Under the hood, \u003Ca rel=\"noopener noreferrer\" href=\"http:\u002F\u002Fc.Stream\">\u003Ccode>c.Stream\u003C\u002Fcode>\u003C\u002Fa>\u003Ccode>()\u003C\u002Fcode> continuously executes the anonymous function you provide as long as that function returns \u003Ccode>true\u003C\u002Fcode> and the underlying HTTP connection remains open. This architecture maintains an incredibly \u003Cstrong>low memory footprint\u003C\u002Fstrong> because individual chunks are serialized into bytes and immediately flushed to the network buffer rather than accumulating inside the server's RAM.\u003C\u002Fp>\u003Ch3>Q: What happens if a user abruptly closes their browser tab mid-stream?\u003C\u002Fh3>\u003Cp>\u003Cstrong>A:\u003C\u002Fstrong> The \u003Ccode>ctx\u003C\u002Fcode> variable inherited from \u003Ccode>c.Request.Context()\u003C\u002Fcode> will capture a cancellation signal instantly. Thanks to our \u003Ccode>select\u003C\u002Fcode> block guarding the operation, the routine detects \u003Ccode>&lt;-ctx.Done()\u003C\u002Fcode> on the next loop cycle, returns \u003Ccode>false\u003C\u002Fcode>, and triggers a \u003Cstrong>graceful termination\u003C\u002Fstrong> of the Goroutine, completely eliminating potential Goroutine leaks.\u003C\u002Fp>\u003Cdiv data-type=\"horizontalRule\">\u003Chr>\u003C\u002Fdiv>\u003Ch2>Conclusion\u003C\u002Fh2>\u003Cp>Combining the Gin Framework with Go Concurrency and OpenAI's streaming API empowers us to build incredibly efficient, resilient, and high-performance streaming servers on the backend without exhausting infrastructure resources. That is the true engineering beauty of Go!\u003C\u002Fp>\u003Cp>\u003Cstrong>Next Episode (EP.151):\u003C\u002Fstrong> No matter how smart or fast our AI is, it remains blind to internal company data, private assets, or real-time documentation updates. Next time, we're stepping into an advanced paradigm: \u003Cem>\"What is RAG? Why Your AI Needs a Private Knowledge Base.\"\u003C\u002Fem> Get ready to unpack Retrieval-Augmented Generation!\u003C\u002Fp>\u003Cp>\u003Cstrong>Follow Superdev Academy on all platforms:\u003C\u002Fstrong>\u003C\u002Fp>\u003Cul>\u003Cli>\u003Cp>\u003Cstrong>🔵 Facebook: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.facebook.com\u002Fsuperdev.academy.th\">\u003Cstrong>Superdev Academy Thailand\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 YouTube: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.youtube.com\u002F@SuperdevAcademy\">\u003Cstrong>Superdev Academy Channel\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>📸 Instagram: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.instagram.com\u002Fsuperdevacademy\u002F\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🎬 TikTok: \u003C\u002Fstrong>\u003Ca target=\"_blank\" rel=\"noopener\" class=\"ng-star-inserted\" href=\"https:\u002F\u002Fwww.tiktok.com\u002F@superdevacademy?lang=th-TH\">\u003Cstrong>@superdevacademy\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003Cli>\u003Cp>\u003Cstrong>🌐 Website: \u003C\u002Fstrong>\u003Ca rel=\"noopener noreferrer\" href=\"https:\u002F\u002Fsuperdevacademy.com\">\u003Cstrong>superdevacademy.com\u003C\u002Fstrong>\u003C\u002Fa>\u003C\u002Fp>\u003C\u002Fli>\u003C\u002Ful>\u003Cp>\u003C\u002Fp>","204nf2kiii7c_ef6xwj8vps.png","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclblg987654321\u002Fct2ogq16ois7wk5\u002F204nf2kiii7c_ef6xwj8vps.png","2026-06-09 03:38:36.311Z","76qprkevbgfdps8",{"keywords":15,"locale":48,"school_blog":58},[16,22,26,30,34,38,43],{"collectionId":17,"collectionName":18,"created":19,"created_by":13,"id":20,"name":21,"updated":19,"updated_by":13},"sclkey987654321","school_keywords","2026-06-09 02:45:44.652Z","h420uh9497imwwg","Gin Framework",{"collectionId":17,"collectionName":18,"created":23,"created_by":13,"id":24,"name":25,"updated":23,"updated_by":13},"2026-06-09 02:45:54.612Z","mwdzo7snibz38p4","Go Web Server",{"collectionId":17,"collectionName":18,"created":27,"created_by":13,"id":28,"name":29,"updated":27,"updated_by":13},"2026-06-09 02:46:00.725Z","rgplhhj7796zaqg","AI Chatbot Backend",{"collectionId":17,"collectionName":18,"created":31,"created_by":13,"id":32,"name":33,"updated":31,"updated_by":13},"2026-06-09 02:46:06.302Z","728qxum2gfiormu","Real-time Streaming",{"collectionId":17,"collectionName":18,"created":35,"created_by":13,"id":36,"name":37,"updated":35,"updated_by":13},"2026-06-09 02:46:11.702Z","y9xcv17enwjxdcw","Server-Sent Events",{"collectionId":17,"collectionName":18,"created":39,"created_by":13,"id":40,"name":41,"updated":42,"updated_by":13},"2026-03-04 08:20:11.547Z","ey3puyme01a9bsw","Go","2026-06-07 06:45:07.798Z",{"collectionId":17,"collectionName":18,"created":44,"created_by":13,"id":45,"name":46,"updated":47,"updated_by":13},"2026-03-04 08:20:14.253Z","ah6lvy4x8qe08l5","Golang","2026-06-07 06:45:08.193Z",{"code":49,"collectionId":50,"collectionName":51,"created":52,"flag":53,"id":54,"is_default":55,"label":56,"updated":57},"en","pbc_1989393366","locales","2026-01-22 11:00:02.726Z","twemoji:flag-united-states","qv9c1llfov2d88z",false,"English","2026-04-10 15:42:46.825Z",{"category":59,"collectionId":60,"collectionName":61,"created":62,"expand":63,"id":78,"slug":79,"updated":80,"views":81},"wqxt7ag2gn7xcmk","pbc_2105096300","school_blogs","2026-06-09 03:32:23.973Z",{"category":64},{"blogIds":65,"collectionId":66,"collectionName":67,"created":68,"created_by":13,"id":59,"image":69,"image_alt":70,"image_path":71,"label":72,"name":73,"priority":74,"publish_at":75,"scheduled_at":70,"status":76,"updated":77,"updated_by":13},[],"sclcatblg987654321","school_category_blogs","2026-03-04 08:33:53.210Z","59ty92ns80w_15oc1implw.png","","https:\u002F\u002Ftwsme-r2.tumwebsme.com\u002Fsclcatblg987654321\u002Fwqxt7ag2gn7xcmk\u002F59ty92ns80w_15oc1implw.png",{"en":73,"th":73},"Golang The Series",1,"2026-03-16 04:39:38.440Z","published","2026-06-07 06:45:03.856Z","uz642au85qm6tzz","golang-the-series-ep150-workshop-ai-chatbot-gin-framework","2026-06-13 13:55:14.069Z",154,"ct2ogq16ois7wk5",[20,24,28,32,36,40,45],"2026-06-09 04:21:25.840Z","It's time for some real action! In this workshop, we'll build an AI Chatbot Server that supports real-time streaming (SSE) using Gin Framework and Go Concurrency. Get ready to deploy production-grade code!","Golang The Series EP.150: Workshop 1: Building a Simple AI Chatbot Server with Gin Framework","2026-06-09 04:21:25.841Z",{"th":79,"en":79}]