gold

package module
v0.0.0-...-b3677b0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 4, 2026 License: MIT Imports: 22 Imported by: 0

README

gold-go

gold-go is a Go-first backend framework that generates a type-safe TypeScript client and serves your frontend through the backend in both development and production.

Key features:

  • Zero manual entrypoint: APIs auto-register via init() functions, no central configuration
  • Type-safe clients: TypeScript client auto-generated at startup with full type information
  • Request operations: POST /gold_request for immediate request/response
  • Stream operations: POST /gold_sse for Server-Sent Events (SSE) subscriptions
  • File uploads: Native multipart form parsing with temp-file helpers
  • Dev/Prod modes: Seamless Vite HMR in dev, static SPA serving in production

Installation

go get github.com/dotmonk/gold-go@<version>

Quick Start

1. Create backend entrypoint — backend/main.go
package main

import (
    "log"
    "os"
    "strconv"

    gold "github.com/dotmonk/gold-go"
    _ "your-module/backend/api"
)

func main() {
    opts := gold.DefaultOptions()
    opts.Dev = os.Getenv("ENV") != "production"
    opts.WorkDir = "."
    opts.GeneratedClientPath = "frontend/client.ts"
    opts.FrontendDir = "static"
    opts.ViteConfig = "vite.config.ts"

    if portStr := os.Getenv("PORT"); portStr != "" {
        if p, err := strconv.Atoi(portStr); err == nil {
            opts.Port = p
        }
    }

    app := gold.New(opts)

    if err := app.ListenAndServe(); err != nil {
        log.Fatal(err)
    }
}
2. Define API operations — backend/api/users.go

Each API file automatically registers itself via init():

package api

import (
    "context"
    gold "github.com/dotmonk/gold-go"
)

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

type CreateUserInput struct {
    User User `json:"user"`
}

func init() {
    gold.RegisterAPIFunc(func(app *gold.App) {
        app.Register(gold.Request(
            gold.OperationMeta{Namespace: "Users", Name: "createUser"},
            func(_ context.Context, in CreateUserInput) (User, error) {
                return in.User, nil
            },
        ))
    })
}
3. Use generated client in frontend

When the backend starts, gold-go writes frontend/client.ts with fully typed operations:

import { Users } from "./client";

// Request operation
const user = await Users.createUser({ user: { name: "Alice", age: 30 } });

// Stream operation (SSE)
const stop = Clock.ticking((tick) => console.log(tick));
// later…
stop();

How It Works

Automatic API Discovery
  1. Each *.go file in backend/api/ defines operations with gold.Register()
  2. Each file calls gold.RegisterAPIFunc() in an init() function
  3. When the app imports backend/api, all init() functions run and register themselves
  4. On startup, gold.New() wires up all registered operations and generates the TypeScript client
Operation Patterns

Request operation — Request/response handled immediately:

app.Register(gold.Request(
    gold.OperationMeta{Namespace: "Users", Name: "createUser"},
    func(_ context.Context, in CreateUserInput) (User, error) {
        return user, nil
    },
))

Stream operation — Server-Sent Events (SSE):

app.Register(gold.Stream(
    gold.OperationMeta{Namespace: "Clock", Name: "ticking"},
    func(ctx context.Context, _ struct{}, out chan<- int64) error {
        out <- time.Now().UnixMilli()
        ticker := time.NewTicker(time.Second)
        defer ticker.Stop()
        for {
            select {
            case <-ctx.Done():
                return nil
            case t := <-ticker.C:
                out <- t.UnixMilli()
            }
        }
    },
))

File Uploads

Use gold.UploadedFile inside the operation input struct.

type SetImageInput struct {
    File gold.UploadedFile `json:"file"`
}

app.Register(gold.Request(
    gold.OperationMeta{Namespace: "Chat", Name: "setImage"},
    func(_ context.Context, in SetImageInput) (gold.Void, error) {
        data, err := in.File.ReadAsBuffer()
        if err != nil {
            return gold.Void{}, err
        }
        // use data...
        _ = data
        return gold.Void{}, nil
    },
))

Options

DefaultOptions() provides these defaults:

  • Port: 3000
  • Dev: false
  • GeneratedClientPath: frontend/client.ts
  • FrontendDir: static
  • WorkDir: .
  • ViteConfig: vite.config.ts
  • VitePort: 5173
  • MaxMultipartBytes: 8 GB
  • MaxMultipartFieldBytes: 1 MB
  • MultipartTmpDir: /tmp/gold-uploads

Interceptors

Interceptors provide a generic way to inject cross-cutting concerns like authentication, logging, request/response transformation, or proxying:

RequestInterceptor — Runs before operation handlers. Use for auth, validation, adding context, etc:

opts.RequestInterceptors = append(opts.RequestInterceptors, func(r *http.Request) (*http.Request, error) {
    token := r.Header.Get("Authorization")
    if token == "" {
        return nil, errors.New("missing token")
    }
    // Validate token and add to context
    ctx := context.WithValue(r.Context(), "user", parseToken(token))
    return r.WithContext(ctx), nil
})

ResponseInterceptor — Runs after operation handlers. Use for logging, transforming responses, etc:

opts.ResponseInterceptors = append(opts.ResponseInterceptors, func(w http.ResponseWriter, r *http.Request, result any) (any, error) {
    log.Printf("Operation completed: %v", result)
    // Can modify or reject the response
    return result, nil
})

Interceptors run in order for each request. If any interceptor returns an error, the request is rejected immediately.

Dev vs Production Frontend Serving

  • Dev mode (Dev=true): gold-go starts Vite and proxies frontend requests through the backend. This keeps one backend entrypoint while preserving HMR.
  • Production mode (Dev=false): gold-go serves static assets from FrontendDir with SPA fallback to index.html.

Example App

The gold-go-example repo shows a complete app with three API modules:

  • Chat: Bidirectional text and image updates via streams + requests
  • Clock: Server time pushed every second via stream
  • Users: Full CRUD with real-time list stream

All APIs are auto-discovered and the frontend is built with React + Vite.

Repository Layout

gold-go/
├── go.mod               # Go module definition
├── router.go            # HTTP routing, request/SSE handlers, client generation
├── types.go             # Type definitions, marshalling, TypeScript codegen
├── setup.go             # Auto-discovery registry (gold.RegisterAPIFunc, RegisterAll)
└── README.md

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RegisterAPIFunc

func RegisterAPIFunc(fn func(*App))

func RegisterAll

func RegisterAll(app *App)

Types

type App

type App struct {
	// contains filtered or unexported fields
}

App is the gold-go application runtime.

func New

func New(opts Options) *App

func (*App) ListenAndServe

func (a *App) ListenAndServe() error

func (*App) Register

func (a *App) Register(op Operation)

type Operation

type Operation struct {
	// contains filtered or unexported fields
}

Operation describes one typed backend endpoint.

func Request

func Request[In any, Out any](meta OperationMeta, handler func(context.Context, In) (Out, error)) Operation

func Stream

func Stream[In any, Ev any](meta OperationMeta, handler func(context.Context, In, chan<- Ev) error) Operation

type OperationMeta

type OperationMeta struct {
	Namespace string
	Name      string
	Summary   string
}

OperationMeta describes a registered operation.

type Options

type Options struct {
	Port                   int
	Dev                    bool
	GeneratedClientPath    string
	FrontendDir            string
	WorkDir                string
	ViteConfig             string
	VitePort               int
	MaxMultipartBytes      int64
	MaxMultipartFieldBytes int64
	MultipartTmpDir        string
	RequestInterceptors    []RequestInterceptor
	ResponseInterceptors   []ResponseInterceptor
}

Options for the gold framework.

func DefaultOptions

func DefaultOptions() Options

DefaultOptions returns sensible defaults.

type ParamInfo

type ParamInfo struct {
	Name       string
	TypeText   string
	IsOptional bool
	Kind       string // "body" or "file"
}

ParamInfo describes a function parameter.

type RequestInterceptor

type RequestInterceptor func(*http.Request) (*http.Request, error)

RequestInterceptor processes incoming requests before operation execution. It can modify the request, validate auth, add context, or reject the request.

type ResponseInterceptor

type ResponseInterceptor func(http.ResponseWriter, *http.Request, any) (any, error)

ResponseInterceptor processes outgoing responses from operations. It can transform the result, add headers, log, or reject the response.

type RouteInfo

type RouteInfo struct {
	MethodName   string
	Namespace    string
	Params       []ParamInfo
	ReturnType   string
	IsSSE        bool
	SSEEventType string
	DispatchKey  string
}

RouteInfo describes a registered route (function).

type UploadedFile

type UploadedFile struct {
	FieldName    string
	Filename     string
	ContentType  string
	Size         int64
	TempFilePath string
}

UploadedFile represents a file uploaded via multipart form data.

func (*UploadedFile) CreateReadStream

func (uf *UploadedFile) CreateReadStream() (io.ReadCloser, error)

CreateReadStream returns a new reader for the uploaded file.

func (*UploadedFile) ReadAsBuffer

func (uf *UploadedFile) ReadAsBuffer() ([]byte, error)

ReadAsBuffer reads the entire uploaded file into memory.

type Void

type Void struct{}

Void can be used as an operation return type to generate a TypeScript void return.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL