SuperAPI AI Directory

openui-forge-go

Coding & development

View repository

Updated 10-16 03:13

About this skill

OpenUI Forge — Go

Build generative UI apps with a React frontend + Go backend. Streams OpenAI API responses directly via net/http.

Activation Triggers

  • "openui go", "openui golang", "openui go backend"
  • "generative ui go", "go streaming ui backend"

Prerequisites

  • Node.js >= 22 (24 LTS recommended) + React >= 18.3.1 (19+ recommended) (frontend)
  • Go >= 1.24 (backend; 1.23 and older are out of security support as of Go 1.26)
  • OPENAI_API_KEY environment variable set

Quick Start

  1. Create the React frontend and install OpenUI deps:
npm install @openuidev/react-ui @openuidev/react-headless @openuidev/react-lang lucide-react zod
  1. Generate the system prompt:
npx @openuidev/cli generate ./src/lib/library.ts --out backend/system-prompt.txt
  1. Create the Go backend (see Full Code below)
  2. Run: go run main.go on :8080, frontend on :3000

Full Code

Backend: backend/go.mod

module openui-backend

go 1.24

require (
    github.com/joho/godotenv v1.5.1
)

Backend: backend/main.go

package main

import (
	"bufio"
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"

	_ "github.com/joho/godotenv/autoload"
)

var systemPrompt string

func init() {
	data, err := os.ReadFile("system-prompt.txt")
	if err != nil {
		log.Fatal("system-prompt.txt not found: ", err)
	}
	systemPrompt = string(data)
}

func corsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
		w.Header().Set("Vary", "Origin")
		w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if r.Method == "OPTIONS" {
			w.WriteHeader(204)
			return
		}
		next.ServeHTTP(w, r)
	})
}

type Message struct {
	Role    string `json:"role"`
	Content string `json:"content"`
}

type ChatRequest struct {
	Messages []Message `json:"messages"`
}

func chatHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	var req ChatRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Bad request", 400)
		return
	}

	messages := append([]Message{{Role: "system", Content: systemPrompt}}, req.Messages...)
	model := os.Getenv("OPENAI_MODEL")
	if model == "" {
		model = "gpt-5.5"
	}
	body, _ := json.Marshal(map[string]interface{}{
		"model": model, "stream": true, "messages": messages,
	})

	apiReq, _ := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", bytes.NewReader(body))
	apiReq.Header.Set("Content-Type", "application/json")
	apiReq.Header.Set("Authorization", "Bearer "+os.Getenv("OPENAI_API_KEY"))

	resp, err := http.DefaultClient.Do(apiReq)
	if err != nil {
		http.Error(w, "OpenAI request failed", 502)
		return
	}
	defer resp.Body.Close()

	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming not supported", 500)
		return
	}

	// Forward upstream SSE line-by-line so the client sees tokens as they
	// arrive instead of waiting for the whole stream to complete.
	scanner := bufio.NewScanner(resp.Body)
	scanner.Buffer(make([]byte, 0, 64*1024), 1024*1024)
	for scanner.Scan() {
		line := scanner.Bytes()
		if _, err := w.Write(line); err != nil {
			return
		}
		if _, err := w.Write([]byte("\n")); err != nil {
			return
		}
		flusher.Flush()
	}
}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/chat", chatHandler)

	fmt.Println("Go backend listening on :8080")
	log.Fatal(http.ListenAndServe(":8080", corsMiddleware(mux)))
}

Frontend: app/chat/page.tsx

"use client";
import { FullScreen } from "@openuidev/react-ui";
import { openuiChatLibrary } from "@openuidev/react-ui/genui-lib";
import {
  openAIAdapter,
  openAIMessageFormat,
} from "@openuidev/react-headless";

export default function ChatPage() {
  return (
    <FullScreen
      componentLibrary={openuiChatLibrary}
      streamProtocol={openAIAdapter()}
      messageFormat={openAIMessageFormat}
      apiUrl="http://localhost:8080/api/chat"
    />
  );
}

The Go backend forwards OpenAI's SSE stream line-by-line with a bufio.Scanner loop, flushing after each line (no io.Copy), so the client sees tokens as they arrive. Pair it with openAIAdapter() on the frontend. openAIReadableStreamAdapter() is for NDJSON (no data: prefix) and will silently produce no output here.

An official OpenAI Go SDK exists (github.com/openai/openai-go/v3, ~v3.41.0, requires Go 1.22+) as an alternative to hand-rolling the HTTP call. This skill keeps raw net/http as the dependency-free default. Note the SDK still ships small, limited breaking changes between releases (per its own README, some backwards-incompatible changes land in minor versions), so pin a version if you adopt it.

System Prompt Generation

npx @openuidev/cli generate ./src/lib/library.ts --out backend/system-prompt.txt

Validation Checklist

  • system-prompt.txt exists in the Go backend directory
  • OPENAI_API_KEY is set in environment or .env
  • CORS middleware allows the frontend origin
  • Response streams SSE directly from OpenAI API (passthrough)
  • Frontend apiUrl points to http://localhost:8080/api/chat
  • Frontend uses streamProtocol={openAIAdapter()} and openAIMessageFormat
  • componentLibrary={openuiChatLibrary} prop passed to FullScreen
  • CSS import in root layout (@openuidev/react-ui/components.css)

Error Patterns

Error Cause Fix
CORS blocked Origin mismatch Update Access-Control-Allow-Origin in middleware
system-prompt.txt not found File missing from backend dir Run CLI generate command
502 Bad Gateway OpenAI API unreachable or key invalid Check OPENAI_API_KEY and network
Stream not flushing Missing http.Flusher Ensure handler calls flusher.Flush()
Empty response Body not forwarded Verify the bufio.Scanner loop writes and flushes each line