gokart

package module
v0.6.0 Latest Latest
Warning

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

Go to latest
Published: Mar 31, 2026 License: MIT Imports: 9 Imported by: 0

README

GoKart Logo

GoKart

Opinionated Go service toolkit. Thin wrappers around battle-tested packages that hand you the real types back — no lock-in, no hidden runtime.

Note: Not affiliated with Praetorian's GoKart (static security scanner). This GoKart is a service/CLI toolkit.

Install

go install github.com/dotcommander/gokart/cmd/gokart@latest

60 Seconds to a Working Project

$ gokart new myapi --postgres

  Created myapi/
  ✓ go mod init
  ✓ go get dependencies
  ✓ CLAUDE.md written

$ tree myapi/
myapi/
├── cmd/
│   └── main.go
├── internal/
│   ├── app/
│   │   └── context.go
│   └── commands/
│       ├── root.go
│       └── greet.go
├── go.mod
├── .gokart-manifest.json
├── CLAUDE.md
└── README.md

$ cd myapi && go run ./cmd
myapi 0.0.0

Add integrations later without re-scaffolding:

gokart add sqlite
gokart add ai
gokart add postgres --dry-run   # preview before applying

What You Get Back

GoKart's factory functions return the underlying types directly. You call pgx, chi, and database/sql as if you wrote the setup yourself — because you effectively did.

// postgres — returns *pgxpool.Pool, use pgx directly
pool, err := postgres.Open(ctx, os.Getenv("DATABASE_URL"))
rows, _ := pool.Query(ctx, "SELECT id, name FROM users WHERE active = $1", true)

// sqlite — returns *sql.DB, use database/sql directly
db, err := sqlite.Open("app.db")
db.QueryContext(ctx, "SELECT id FROM sessions WHERE expires_at > ?", time.Now())

// web — returns chi.Router, use chi directly
router := web.NewRouter(web.RouterConfig{Middleware: web.StandardMiddleware})
router.Get("/health", func(w http.ResponseWriter, r *http.Request) { ... })
router.Use(middleware.RealIP)

// cache — returns *redis.Client, use go-redis directly
client, err := cache.Open(ctx, "localhost:6379")
client.Set(ctx, "key", value, 5*time.Minute)

// config — typed, generics, auto env binding (DB_HOST → db.host)
cfg, err := gokart.LoadConfig[AppConfig]("config.yaml")

No wrapper types. No .Unwrap(). If GoKart's defaults don't fit, reach past them.

Packages

Package You get Wraps
gokart typed config, state persistence, logger aliases viper, slog
gokart/cli *cli.App, styled output, tables, spinners, editor bridge cobra, lipgloss
gokart/web chi.Router, graceful server, response helpers, templ, CSRF, pagination, health checks, rate limiting, auth middleware chi/v5, a-h/templ, validator/v10
gokart/postgres *pgxpool.Pool, transaction helper pgx/v5
gokart/sqlite *sql.DB, WAL mode, transaction helper modernc.org/sqlite
gokart/migrate schema migrations, embedded FS support goose/v3
gokart/cache *redis.Client, Remember pattern, distributed locks go-redis/v9
gokart/kv *KV expanded Redis (hash, sorted set, set, list, counters) go-redis/v9
gokart/fs atomic writes, config dir, read-or-create stdlib only
gokart/ai *openai.Client factory openai-go v3
gokart/logger JSON/text slog, file logger for TUI tools log/slog

Import only what you need — each is a separate Go module.

The Scaffolder

gokart new generates a structured CLI project wired to your chosen integrations:

gokart new mycli                    # Structured, global config (~/.config/mycli/)
gokart new mycli --local            # Structured, no global config
gokart new mycli --flat             # Single main.go
gokart new mycli --sqlite           # With SQLite wiring
gokart new mycli --postgres         # With PostgreSQL wiring
gokart new mycli --ai               # With OpenAI client
gokart new mycli --redis            # With Redis cache
gokart new mycli --postgres --ai    # Combined

gokart add surgically adds integrations to an existing project. It re-renders only the affected files (internal/app/context.go, internal/commands/root.go), runs go get, and updates the manifest.

gokart add sqlite
gokart add ai --force       # overwrite modified files
gokart add redis
gokart add postgres --dry-run

Philosophy

  • Modular — import only what you need, no forced dependencies.
  • Thin wrappers — factory functions, sensible defaults, real types returned.
  • Fight for inclusion — if stdlib or the underlying package already solves it, GoKart stays out of the way.
  • Web looks big, isn'tgokart/web lists many features but it's 17 small files (~1300 lines total), each ≤144 lines, each returning standard types. It's a toolkit of HTTP helpers sharing an import path, not a framework.

Examples

See examples/ for complete working projects:

  • http-service/ — Minimal HTTP API with chi router
  • cli-app/ — CLI with commands, tables, and spinners

Compatibility

Minimum Go version: 1.22+ (1.26+ recommended)

API stability: Library packages (gokart, gokart/cli, etc.) follow semver. Generator templates may evolve between minor versions — generated code is yours to modify.

License

MIT

Documentation

Overview

Package gokart provides thin wrappers around best-in-class Go packages with sensible defaults.

GoKart is an opinionated service toolkit. Every component must justify its existence - we wrap battle-tested packages, not reinvent them.

Components

Root module (github.com/dotcommander/gokart):

  • Config: viper wrapper for config files + env vars
  • State: JSON state persistence for CLI tools
  • Server: HTTP server with graceful shutdown
  • Logger: slog aliases (use gokart/logger directly)

Submodules:

  • gokart/logger: slog wrapper with JSON/text formatting
  • gokart/cli: CLI framework wrapping cobra + lipgloss
  • gokart/web: HTTP router, client, templ helpers, validator
  • gokart/postgres: pgx/v5 connection pool
  • gokart/sqlite: modernc.org/sqlite (zero CGO) with WAL mode
  • gokart/cache: go-redis/v9 with convenience methods
  • gokart/migrate: goose/v3 schema migrations
  • gokart/ai: OpenAI client factory functions

Design Principles

  • Thin wrappers: No business logic, just factory functions
  • Sensible defaults: Zero-config works for development and production
  • Best-in-class: Wrap proven packages, don't reinvent
  • Fight for inclusion: stdlib-sufficient things stay in stdlib

Quick Start

// Logger
log := logger.New(logger.Config{Level: "info", Format: "json"})

// Config
cfg, err := gokart.LoadConfig[AppConfig]("config.yaml")

// Router (gokart/web)
router := web.NewRouter(web.RouterConfig{Middleware: web.StandardMiddleware})

// PostgreSQL (gokart/postgres)
pool, err := postgres.Open(ctx, "postgres://localhost/mydb")

// Cache (gokart/cache)
c, err := cache.Open(ctx, "localhost:6379")

// Migrations (gokart/migrate)
migrate.Postgres(ctx, db, "migrations")

CLI Subpackage

For CLI applications, use the gokart/cli subpackage which wraps cobra and lipgloss:

import "github.com/dotcommander/gokart/cli"

app := cli.NewApp("myapp", "1.0.0").
    WithDescription("My application").
    WithStandardFlags()
app.Run()

Not Included

GoKart intentionally excludes things where stdlib is sufficient:

  • Error handling: use errors.Is/As and fmt.Errorf("%w", err)
  • File operations: use os, io, filepath
  • String manipulation: use strings
  • Environment variables: viper.AutomaticEnv() handles this

Domain-specific packages (AI/ML, document processing) belong in separate modules.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func LoadConfig

func LoadConfig[T any](paths ...string) (T, error)

LoadConfig loads configuration from the first available file path into type T.

Features:

  • Supports multiple config paths (first found wins)
  • Automatic environment variable binding
  • DOT to UNDERSCORE env key mapping (e.g., db.host → DB_HOST)

Supported formats: JSON, YAML, TOML, HCL, envfile, Java properties

Example:

type Config struct {
    DB struct {
        Host string `mapstructure:"host"`
        Port int    `mapstructure:"port"`
    } `mapstructure:"db"`
}
cfg, err := gokart.LoadConfig[Config]("config.yaml", "config.json")

func LoadConfigWithDefaults

func LoadConfigWithDefaults[T any](defaults T, paths ...string) (T, error)

LoadConfigWithDefaults loads configuration with default values pre-populated.

The defaults parameter provides fallback values that will be overridden by values from config files or environment variables.

Example:

defaults := Config{
    DB: struct{Host string; Port int}{
        Host: "localhost",
        Port: 5432,
    },
}
cfg, err := gokart.LoadConfigWithDefaults(defaults, "config.yaml")

func LoadState

func LoadState[T any](appName, filename string) (T, error)

LoadState loads typed state from ~/.config/{appName}/{filename}.

Returns zero value and os.ErrNotExist if the file doesn't exist. This allows callers to distinguish between missing file and parse errors.

Example:

state, err := gokart.LoadState[AppState]("myapp", "state.json")
if errors.Is(err, os.ErrNotExist) {
    // First run, use defaults
    state = AppState{WindowSize: 800}
} else if err != nil {
    return err
}

func LogPath

func LogPath(appName string) string

LogPath returns the path where file logs are written. Deprecated: Use logger.Path directly.

func NewFileLogger

func NewFileLogger(appName string) (*slog.Logger, func(), error)

NewFileLogger creates a logger that writes to a temp file. Deprecated: Use logger.NewFile directly.

func NewLogger

func NewLogger(cfg LogConfig) *slog.Logger

NewLogger creates a new structured logger with sensible defaults. Deprecated: Use logger.New directly.

Example
package main

import (
	"github.com/dotcommander/gokart"
)

func main() {
	log := gokart.NewLogger(gokart.LogConfig{
		Level:  "info",
		Format: "json",
	})
	log.Info("server started", "port", 8080)
}

func SaveState

func SaveState[T any](appName, filename string, data T) error

SaveState saves typed state to ~/.config/{appName}/{filename}.

The file is written as indented JSON for human readability. Directory is created with 0755, files with 0644 permissions.

Example:

type AppState struct {
    LastOpened string `json:"last_opened"`
    WindowSize int    `json:"window_size"`
}
err := gokart.SaveState("myapp", "state.json", AppState{
    LastOpened: "/path/to/file",
    WindowSize: 1024,
})

func StatePath

func StatePath(appName, filename string) string

StatePath returns the full path to a state file.

Returns empty string if the user config directory cannot be determined.

Example:

path := gokart.StatePath("myapp", "state.json")
// Returns: /Users/username/.config/myapp/state.json (on macOS)

Types

type LogConfig

type LogConfig = logger.Config

LogConfig is an alias for logger.Config. Deprecated: Use logger.Config directly.

Directories

Path Synopsis
cache module
cli module
cmd
gokart command
internal
logger module
migrate module
postgres module
sqlite module
web module

Jump to

Keyboard shortcuts

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