gokart

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jan 6, 2026 License: MIT Imports: 26 Imported by: 0

README

GoKart Logo

GoKart

Opinionated Go service toolkit. Thin wrappers around best-in-class packages with sensible defaults.

Why?

Every Go service has the same 50-100 lines of setup boilerplate: configure slog with JSON/text switching, set up chi with standard middleware, parse postgres URLs with pool limits, wire viper to read config + env vars. You've written this code dozens of times. It's not hard—just tedious and easy to get slightly wrong.

GoKart is your conventions, tested and packaged.

pool, _ := gokart.OpenPostgres(ctx, os.Getenv("DATABASE_URL"))
router := gokart.NewRouter(gokart.RouterConfig{Middleware: gokart.StandardMiddleware})
cache, _ := gokart.OpenCache(ctx, "localhost:6379")

It's not a framework. It doesn't hide the underlying packages. Factory functions return *pgxpool.Pool, chi.Router, *redis.Client—use them directly. If you disagree with a default, use the underlying package. GoKart doesn't lock you in.

Philosophy

  • Batteries included — One import, everything available. No sub-package juggling.
  • Thin wrappers — GoKart doesn't reinvent. It wraps battle-tested packages.
  • Sensible defaults — Zero-config works. Customize when needed.
  • Fight for inclusion — Every component must justify its existence.

GoKart is a starter kit, not a modular library. The single import "github.com/dotcommander/gokart" is intentional—you get logger, config, database, cache, HTTP, and validation ready to use. Go's compiler eliminates unused code, so you don't pay for what you don't call.

Install

go get github.com/dotcommander/gokart
go get github.com/dotcommander/gokart/cli
go install github.com/dotcommander/gokart/cmd/gokart@latest  # CLI generator

Components

Component Wraps Purpose
Logger slog Structured logging
Config viper Configuration + env vars
Router chi HTTP routing + middleware
HTTP Client retryablehttp HTTP client with retries
Validator go-playground/validator Struct validation
PostgreSQL pgx/v5 Postgres connection pool
SQLite modernc.org/sqlite SQLite (zero-CGO)
Templates a-h/templ Type-safe HTML templates
Cache go-redis/v9 Redis cache
Migrations goose/v3 Database migrations
State encoding/json JSON state persistence
CLI cobra + lipgloss CLI applications
CLI Generator text/template Project scaffolding

Logger

Wraps log/slog with configuration helpers.

log := gokart.NewLogger(gokart.LogConfig{
    Level:  "debug",  // debug|info|warn|error
    Format: "text",   // json|text
})

log.Info("server started", "port", 8080)
log.Error("request failed", "err", err, "path", "/api/users")

Config

Wraps spf13/viper for typed configuration loading.

type Config struct {
    Port int    `mapstructure:"port"`
    DB   string `mapstructure:"database_url"`
}

cfg, err := gokart.LoadConfig[Config]("config.yaml")
// Also reads PORT, DATABASE_URL from environment

Router

Wraps go-chi/chi with standard middleware.

router := gokart.NewRouter(gokart.RouterConfig{
    Middleware: gokart.StandardMiddleware,  // RequestID, RealIP, Logger, Recoverer
    Timeout:    30 * time.Second,
})

router.Get("/health", healthHandler)
router.Route("/api", func(r chi.Router) {
    r.Get("/users", listUsers)
    r.Post("/users", createUser)
})

http.ListenAndServe(":8080", router)

HTTP Client

Wraps hashicorp/go-retryablehttp for resilient HTTP calls.

// Simple - returns *http.Client
client := gokart.NewStandardClient()

// Configurable
client := gokart.NewHTTPClient(gokart.HTTPConfig{
    Timeout:   10 * time.Second,
    RetryMax:  5,
    RetryWait: 2 * time.Second,
})

resp, err := client.StandardClient().Get("https://api.example.com/data")

Validator

Wraps go-playground/validator with JSON field names and common validators.

v := gokart.NewStandardValidator()

type User struct {
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"gte=0,lte=130"`
    Name  string `json:"name" validate:"required,notblank"`
}

if err := v.Struct(user); err != nil {
    for field, msg := range gokart.ValidationErrors(err) {
        fmt.Printf("%s: %s\n", field, msg)
    }
}

PostgreSQL

Wraps jackc/pgx/v5 with connection pooling.

// Simple
pool, err := gokart.OpenPostgres(ctx, "postgres://user:pass@localhost:5432/mydb")
defer pool.Close()

// From DATABASE_URL env
pool, err := gokart.PostgresFromEnv(ctx)

// Custom config
pool, err := gokart.OpenPostgresWithConfig(ctx, gokart.PostgresConfig{
    URL:      "postgres://...",
    MaxConns: 50,
    MinConns: 10,
})

// Query
var name string
err = pool.QueryRow(ctx, "SELECT name FROM users WHERE id = $1", 1).Scan(&name)

// Transaction
err := gokart.WithTransaction(ctx, pool, func(tx pgx.Tx) error {
    _, err := tx.Exec(ctx, "INSERT INTO users (name) VALUES ($1)", "John")
    return err
})

SQLite

Wraps modernc.org/sqlite (pure Go, zero CGO) with production defaults.

// Simple
db, err := gokart.OpenSQLite("app.db")
defer db.Close()

// In-memory (for tests)
db, err := gokart.SQLiteInMemory()

// Custom config
db, err := gokart.OpenSQLiteWithConfig(ctx, gokart.SQLiteConfig{
    Path:         "app.db",
    WALMode:      true,
    ForeignKeys:  true,
    MaxOpenConns: 25,
})

// Transaction
err := gokart.SQLiteTransaction(ctx, db, func(tx *sql.Tx) error {
    _, err := tx.ExecContext(ctx, "INSERT INTO users (name) VALUES (?)", "John")
    return err
})

Templates

Wraps a-h/templ for type-safe HTML rendering.

// In handler
func handleHome(w http.ResponseWriter, r *http.Request) {
    gokart.Render(w, r, views.HomePage("Welcome"))
}

// With status code
gokart.RenderWithStatus(w, r, http.StatusNotFound, views.NotFoundPage())

// As handler
router.Get("/about", gokart.TemplHandler(views.AboutPage()))

// Dynamic handler
router.Get("/user/{id}", gokart.TemplHandlerFunc(func(r *http.Request) templ.Component {
    id := chi.URLParam(r, "id")
    return views.UserPage(getUser(id))
}))

Note: Write .templ files and run templ generate - gokart provides HTTP integration.


Cache

Wraps redis/go-redis/v9 for Redis caching.

// Simple
cache, err := gokart.OpenCache(ctx, "localhost:6379")
defer cache.Close()

// From URL
cache, err := gokart.OpenCacheURL(ctx, "redis://:password@localhost:6379/0")

// With prefix
cache, err := gokart.OpenCacheWithConfig(ctx, gokart.CacheConfig{
    Addr:      "localhost:6379",
    KeyPrefix: "myapp:",
})

// String operations
cache.Set(ctx, "key", "value", time.Hour)
val, err := cache.Get(ctx, "key")
cache.Delete(ctx, "key")

// JSON operations
cache.SetJSON(ctx, "user:1", user, time.Hour)
cache.GetJSON(ctx, "user:1", &user)

// Counters
cache.Incr(ctx, "views")
cache.IncrBy(ctx, "views", 10)

// Distributed lock
ok, err := cache.SetNX(ctx, "lock:job", "worker-1", time.Minute)

// Remember pattern (get or compute) - returns string
val, err := cache.Remember(ctx, "expensive", time.Hour, func() (interface{}, error) {
    return computeExpensiveValue()
})

// RememberJSON for typed data - preserves type for GetJSON retrieval
var user User
err := cache.RememberJSON(ctx, "user:1", time.Hour, &user, func() (interface{}, error) {
    return db.GetUser(ctx, 1)
})

// Check cache miss
if gokart.IsNil(err) {
    // Key doesn't exist
}

Migrations

Wraps pressly/goose/v3 for database schema migrations.

// PostgreSQL
pool, _ := gokart.OpenPostgres(ctx, url)
db := stdlib.OpenDBFromPool(pool)
err := gokart.PostgresMigrate(ctx, db, "migrations")

// SQLite
db, _ := gokart.OpenSQLite("app.db")
err := gokart.SQLiteMigrate(ctx, db, "migrations")

// Embedded migrations
//go:embed migrations/*.sql
var migrations embed.FS

err := gokart.Migrate(ctx, db, gokart.MigrateConfig{
    FS:      migrations,
    Dir:     "migrations",
    Dialect: "postgres",
})

// Operations
gokart.MigrateUp(ctx, db, cfg)       // Run pending
gokart.MigrateDown(ctx, db, cfg)     // Rollback one
gokart.MigrateDownTo(ctx, db, cfg, 5) // Rollback to version
gokart.MigrateReset(ctx, db, cfg)    // Rollback all
gokart.MigrateStatus(ctx, db, cfg)   // Print status

// Create new migration
gokart.MigrateCreate("migrations", "add_users_table", "sql")

Migration file format (migrations/001_create_users.sql):

-- +goose Up
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL UNIQUE
);

-- +goose Down
DROP TABLE users;

CLI

Subpackage gokart/cli wraps spf13/cobra + charmbracelet/lipgloss.

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

func main() {
    app := cli.NewApp("myapp", "1.0.0").
        WithDescription("My application").
        WithEnvPrefix("MYAPP").
        WithStandardFlags()

    app.AddCommand(cli.Command("serve", "Start server", runServe))
    app.AddCommand(cli.Command("migrate", "Run migrations", runMigrate))

    if err := app.Run(); err != nil {
        os.Exit(1)
    }
}

func runServe(cmd *cobra.Command, args []string) error {
    cli.Info("Starting server...")
    return server.Run()
}
Output Styling
cli.Success("Operation completed")  // ✓ green
cli.Error("Operation failed")       // ✗ red
cli.Warning("Deprecated feature")   // ⚠ yellow
cli.Info("Processing...")           // → blue
cli.Dim("Debug info")               // gray

cli.Fatal("Cannot continue")        // prints + os.Exit(1)
cli.FatalErr("Failed", err)         // prints error + os.Exit(1)
Tables
t := cli.NewTable("ID", "Name", "Status")
t.AddRow("1", "Alice", "Active")
t.AddRow("2", "Bob", "Inactive")
t.Print()

// Quick table
cli.SimpleTable(
    []string{"Key", "Value"},
    [][]string{{"Host", "localhost"}, {"Port", "8080"}},
)

// Key-value list
cli.KeyValue(map[string]string{"Host": "localhost", "Port": "8080"})

// Bulleted list
cli.List("First item", "Second item", "Third item")
Spinners & Progress
// Spinner
s := cli.NewSpinner("Loading...")
s.Start()
// do work
s.StopSuccess("Loaded")

// With helper
err := cli.WithSpinner("Processing...", func() error {
    return doSomething()
})

// Progress bar
p := cli.NewProgress("Importing", 100)
for i := 0; i < 100; i++ {
    p.Increment()
    processItem(i)
}
p.Done()
Editor Input

Capture long-form input by opening $EDITOR:

// Opens vim/nano, returns edited text
text, err := cli.CaptureInput("# Enter description here", "md")

// With specific editor
text, err := cli.CaptureInputWithEditor("code --wait", "", "json")

CLI Generator

Scaffold new CLI projects with gokart new:

# Install the generator
go install github.com/dotcommander/gokart/cmd/gokart@latest

# Create a structured project (default)
gokart new mycli

# Create a flat single-file project
gokart new mycli --flat

# With SQLite database wiring
gokart new mycli --sqlite

# With OpenAI client wiring
gokart new mycli --ai

# With both
gokart new mycli --sqlite --ai

# Custom module path
gokart new mycli --module github.com/myorg/mycli
Structured Output (default)
mycli/
├── cmd/main.go                    # Entry point
├── internal/
│   ├── app/context.go             # App context (if --sqlite or --ai)
│   ├── commands/
│   │   ├── root.go                # CLI setup
│   │   └── greet.go               # Example command
│   └── actions/
│       └── greet.go               # Business logic (testable)
└── go.mod
Flat Output (--flat)
mycli/
├── main.go
└── go.mod

State Persistence

Save/load typed state for CLI tools. Separate from config (viper handles config, this handles runtime state).

// Define your state
type AppState struct {
    LastTarget string    `json:"last_target"`
    RunCount   int       `json:"run_count"`
}

// Save state to ~/.config/myapp/state.json
state := AppState{LastTarget: "prod", RunCount: 42}
err := gokart.SaveState("myapp", "state.json", state)

// Load state (returns zero value if not found)
state, err := gokart.LoadState[AppState]("myapp", "state.json")
if errors.Is(err, os.ErrNotExist) {
    // First run, use defaults
}

// Get the state file path
path := gokart.StatePath("myapp", "state.json")
// ~/.config/myapp/state.json

File Logger

Create a logger that writes to a temp file, keeping stdout clean for spinners and tables.

// Creates logger writing to /tmp/myapp.log
logger, cleanup, err := gokart.NewFileLogger("myapp")
if err != nil {
    log.Fatal(err)
}
defer cleanup()

// Use the logger
logger.Info("processing started", "file", filename)
logger.Error("validation failed", "err", err)

// Get the log file path
path := gokart.LogPath("myapp")
// /tmp/myapp.log

Debug your CLI with: tail -f /tmp/myapp.log


Not Included

GoKart intentionally excludes:

What Why Use Instead
Error helpers stdlib sufficient errors.Is/As, fmt.Errorf("%w")
File utilities stdlib sufficient os, io, filepath
String utilities stdlib sufficient strings
Env helpers viper handles it viper.AutomaticEnv()
DI container architecture choice Constructor injection
AI/LLM clients domain-specific Separate packages
Document processing domain-specific Separate packages

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

  • Logger: slog wrapper with JSON/text formatting
  • Config: viper wrapper for config files + env vars
  • Router: chi wrapper with standard middleware
  • HTTP Client: retryablehttp wrapper with automatic retries
  • Validator: go-playground/validator with JSON field names
  • PostgreSQL: pgx/v5 connection pool
  • SQLite: modernc.org/sqlite (zero CGO) with WAL mode
  • Templates: a-h/templ HTTP integration helpers
  • Cache: go-redis/v9 with convenience methods
  • Migrations: goose/v3 schema migrations

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 := gokart.NewLogger(gokart.LogConfig{Level: "info", Format: "json"})

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

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

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

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

// Migrations
gokart.PostgresMigrate(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

StandardMiddleware provides production-ready middleware stack:

  • RequestID: Injects request ID for tracing
  • RealIP: Extracts real client IP from proxies
  • Logger: Structured request/response logging
  • Recoverer: Panic recovery

Functions

func IsNil

func IsNil(err error) bool

IsNil returns true if the error is a cache miss.

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 Migrate

func Migrate(ctx context.Context, db *sql.DB, cfg MigrateConfig) error

Migrate runs all pending migrations.

Example with file-based migrations:

db, _ := gokart.OpenPostgres(ctx, url)
err := gokart.Migrate(ctx, db.Config().ConnConfig.Database, gokart.MigrateConfig{
    Dir:     "migrations",
    Dialect: "postgres",
})

Example with embedded migrations:

//go:embed migrations/*.sql
var migrations embed.FS

err := gokart.Migrate(ctx, db, gokart.MigrateConfig{
    FS:      migrations,
    Dir:     "migrations",
    Dialect: "postgres",
})

func MigrateCreate

func MigrateCreate(dir, name, migrationType string) error

MigrateCreate creates a new migration file.

Example:

err := gokart.MigrateCreate("migrations", "add_users_table", "sql")

func MigrateDown

func MigrateDown(ctx context.Context, db *sql.DB, cfg MigrateConfig) error

MigrateDown rolls back the last migration.

func MigrateDownTo

func MigrateDownTo(ctx context.Context, db *sql.DB, cfg MigrateConfig, version int64) error

MigrateDownTo rolls back to a specific version.

func MigrateReset

func MigrateReset(ctx context.Context, db *sql.DB, cfg MigrateConfig) error

MigrateReset rolls back all migrations.

func MigrateStatus

func MigrateStatus(ctx context.Context, db *sql.DB, cfg MigrateConfig) error

MigrateStatus prints the status of all migrations.

func MigrateUp

func MigrateUp(ctx context.Context, db *sql.DB, cfg MigrateConfig) error

MigrateUp runs all pending migrations.

func MigrateVersion

func MigrateVersion(ctx context.Context, db *sql.DB, cfg MigrateConfig) (int64, error)

MigrateVersion returns the current migration version.

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 NewHTTPClient

func NewHTTPClient(cfg HTTPConfig) *retryablehttp.Client

NewHTTPClient creates a retryable HTTP client with exponential backoff.

Default configuration:

  • Timeout: 30s
  • RetryMax: 3 attempts
  • RetryWait: 1s base delay

The client automatically retries on network errors and 5xx responses.

Example:

client := gokart.NewHTTPClient(gokart.HTTPConfig{
    Timeout:   10 * time.Second,
    RetryMax:  5,
    RetryWait: 2 * time.Second,
})
resp, err := client.Get("https://api.example.com/data")
Example
package main

import (
	"time"

	"github.com/dotcommander/gokart"
)

func main() {
	client := gokart.NewHTTPClient(gokart.HTTPConfig{
		Timeout:   10 * time.Second,
		RetryMax:  3,
		RetryWait: 1 * time.Second,
	})

	_ = client // Use client.Get(), client.Post(), etc.
}

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 NewRouter

func NewRouter(cfg RouterConfig) chi.Router

NewRouter creates a new chi router with configured middleware.

Example:

router := gokart.NewRouter(gokart.RouterConfig{
    Middleware: gokart.StandardMiddleware,
    Timeout:    30 * time.Second,
})

router.Get("/health", func(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
})

http.ListenAndServe(":8080", router)
Example
package main

import (
	"net/http"
	"time"

	"github.com/dotcommander/gokart"
)

func main() {
	router := gokart.NewRouter(gokart.RouterConfig{
		Middleware: gokart.StandardMiddleware,
		Timeout:    30 * time.Second,
	})

	router.Get("/api/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	})

	// http.ListenAndServe(":8080", router)
}

func NewStandardClient

func NewStandardClient() *http.Client

NewStandardClient creates a standard http.Client with retry logic.

This is a convenience wrapper around NewHTTPClient that returns a standard library http.Client interface for drop-in compatibility.

Uses default configuration:

  • Timeout: 30s
  • RetryMax: 3 attempts
  • RetryWait: 1s base delay

Example:

client := gokart.NewStandardClient()
resp, err := client.Get("https://api.example.com/data")

func NewStandardValidator

func NewStandardValidator() *validator.Validate

NewStandardValidator creates a validator with default settings.

Convenience wrapper around NewValidator with zero configuration.

Example:

v := gokart.NewStandardValidator()
err := v.Struct(myStruct)

func NewValidator

func NewValidator(cfg ValidatorConfig) *validator.Validate

NewValidator creates a configured validator instance.

Default configuration:

  • Uses JSON tag names for field identification
  • Registers common custom validators (notblank)

Example:

v := gokart.NewValidator(gokart.ValidatorConfig{})

type User struct {
    Email string `json:"email" validate:"required,email"`
    Age   int    `json:"age" validate:"gte=0,lte=130"`
}

if err := v.Struct(user); err != nil {
    // handle validation errors
}

func OpenPostgres

func OpenPostgres(ctx context.Context, url string) (*pgxpool.Pool, error)

OpenPostgres opens a PostgreSQL connection pool with default settings. Deprecated: Use postgres.Open directly.

func OpenPostgresWithConfig

func OpenPostgresWithConfig(ctx context.Context, cfg PostgresConfig) (*pgxpool.Pool, error)

OpenPostgresWithConfig opens a PostgreSQL connection pool with custom settings. Deprecated: Use postgres.OpenWithConfig directly.

func OpenSQLite

func OpenSQLite(path string) (*sql.DB, error)

OpenSQLite opens a SQLite database with default settings. Deprecated: Use sqlite.Open directly.

func OpenSQLiteContext

func OpenSQLiteContext(ctx context.Context, path string) (*sql.DB, error)

OpenSQLiteContext opens a SQLite database with context. Deprecated: Use sqlite.OpenContext directly.

func OpenSQLiteWithConfig

func OpenSQLiteWithConfig(ctx context.Context, cfg SQLiteConfig) (*sql.DB, error)

OpenSQLiteWithConfig opens a SQLite database with custom settings. Deprecated: Use sqlite.OpenWithConfig directly.

func PostgresFromEnv

func PostgresFromEnv(ctx context.Context) (*pgxpool.Pool, error)

PostgresFromEnv opens a PostgreSQL pool using DATABASE_URL environment variable. Deprecated: Use postgres.FromEnv directly.

func PostgresMigrate

func PostgresMigrate(ctx context.Context, db *sql.DB, dir string) error

PostgresMigrate is a convenience function for PostgreSQL migrations.

Example:

pool, _ := gokart.OpenPostgres(ctx, url)
db := stdlib.OpenDBFromPool(pool)
err := gokart.PostgresMigrate(ctx, db, "migrations")

func Render

func Render(w http.ResponseWriter, r *http.Request, component templ.Component) error

Render renders a templ component to an http.ResponseWriter.

Sets Content-Type to text/html and handles errors.

Example:

func handleHome(w http.ResponseWriter, r *http.Request) {
    gokart.Render(w, r, views.HomePage("Welcome"))
}

func RenderCtx

func RenderCtx(ctx context.Context, w http.ResponseWriter, component templ.Component) error

RenderCtx renders a templ component with a custom context.

Example:

ctx := context.WithValue(r.Context(), "user", currentUser)
gokart.RenderCtx(ctx, w, views.Dashboard(data))

func RenderWithStatus

func RenderWithStatus(w http.ResponseWriter, r *http.Request, status int, component templ.Component) error

RenderWithStatus renders a templ component with a custom status code.

Example:

func handleNotFound(w http.ResponseWriter, r *http.Request) {
    gokart.RenderWithStatus(w, r, http.StatusNotFound, views.NotFoundPage())
}

func SQLiteInMemory

func SQLiteInMemory() (*sql.DB, error)

SQLiteInMemory creates an in-memory SQLite database for testing. Deprecated: Use sqlite.InMemory directly.

func SQLiteMigrate

func SQLiteMigrate(ctx context.Context, db *sql.DB, dir string) error

SQLiteMigrate is a convenience function for SQLite migrations.

Example:

db, _ := gokart.OpenSQLite("app.db")
err := gokart.SQLiteMigrate(ctx, db, "migrations")

func SQLiteTransaction

func SQLiteTransaction(ctx context.Context, db *sql.DB, fn func(tx *sql.Tx) error) error

SQLiteTransaction executes a function within a SQLite transaction. Deprecated: Use sqlite.Transaction directly.

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)

func TemplHandler

func TemplHandler(component templ.Component) http.Handler

TemplHandler creates an http.Handler from a templ component.

Useful for static pages or when you don't need request data.

Example:

router.Get("/about", gokart.TemplHandler(views.AboutPage()))

func TemplHandlerFunc

func TemplHandlerFunc(fn func(r *http.Request) templ.Component) http.HandlerFunc

TemplHandlerFunc creates an http.HandlerFunc from a function that returns a component.

Useful when the component needs data from the request.

Example:

router.Get("/user/{id}", gokart.TemplHandlerFunc(func(r *http.Request) templ.Component {
    id := chi.URLParam(r, "id")
    user := getUser(id)
    return views.UserPage(user)
}))

func TemplHandlerFuncE

func TemplHandlerFuncE(fn func(r *http.Request) (templ.Component, error)) http.HandlerFunc

TemplHandlerFuncE creates an http.HandlerFunc from a function that can return an error.

Example:

router.Get("/dashboard", gokart.TemplHandlerFuncE(func(r *http.Request) (templ.Component, error) {
    data, err := loadDashboardData(r.Context())
    if err != nil {
        return nil, err
    }
    return views.Dashboard(data), nil
}))

func ValidationErrors

func ValidationErrors(err error) map[string]string

ValidationErrors extracts field-level errors from a validation error. Returns nil if err is not a validator.ValidationErrors.

Example:

if err := v.Struct(user); err != nil {
    for field, msg := range gokart.ValidationErrors(err) {
        fmt.Printf("%s: %s\n", field, msg)
    }
}

func WithTransaction

func WithTransaction(ctx context.Context, pool *pgxpool.Pool, fn func(tx pgx.Tx) error) error

WithTransaction executes a function within a PostgreSQL transaction. Deprecated: Use postgres.Transaction directly.

Types

type Cache

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

Cache wraps Redis client with convenience methods.

func OpenCache

func OpenCache(ctx context.Context, addr string) (*Cache, error)

OpenCache opens a Redis connection with default settings.

Example:

cache, err := gokart.OpenCache(ctx, "localhost:6379")
if err != nil {
    log.Fatal(err)
}
defer cache.Close()

func OpenCacheURL

func OpenCacheURL(ctx context.Context, url string) (*Cache, error)

OpenCacheURL opens a Redis connection using a URL.

Example:

cache, err := gokart.OpenCacheURL(ctx, "redis://:password@localhost:6379/0")

func OpenCacheWithConfig

func OpenCacheWithConfig(ctx context.Context, cfg CacheConfig) (*Cache, error)

OpenCacheWithConfig opens a Redis connection with custom settings.

Example:

cache, err := gokart.OpenCacheWithConfig(ctx, gokart.CacheConfig{
    Addr:      "localhost:6379",
    Password:  "secret",
    KeyPrefix: "myapp:",
})

func (*Cache) Client

func (c *Cache) Client() *redis.Client

Client returns the underlying Redis client.

func (*Cache) Close

func (c *Cache) Close() error

Close closes the Redis connection.

func (*Cache) Delete

func (c *Cache) Delete(ctx context.Context, keys ...string) error

Delete removes a key.

func (*Cache) Exists

func (c *Cache) Exists(ctx context.Context, key string) (bool, error)

Exists checks if a key exists.

func (*Cache) Expire

func (c *Cache) Expire(ctx context.Context, key string, ttl time.Duration) error

Expire sets a TTL on an existing key.

func (*Cache) Get

func (c *Cache) Get(ctx context.Context, key string) (string, error)

Get retrieves a string value.

func (*Cache) GetJSON

func (c *Cache) GetJSON(ctx context.Context, key string, dest interface{}) error

GetJSON retrieves and unmarshals a JSON value.

func (*Cache) Incr

func (c *Cache) Incr(ctx context.Context, key string) (int64, error)

Incr increments a counter and returns the new value.

func (*Cache) IncrBy

func (c *Cache) IncrBy(ctx context.Context, key string, value int64) (int64, error)

IncrBy increments a counter by a specific amount.

func (*Cache) Remember

func (c *Cache) Remember(ctx context.Context, key string, ttl time.Duration, fn func() (interface{}, error)) (string, error)

Remember gets a value or sets it using the provided function.

Example:

user, err := cache.Remember(ctx, "user:123", time.Hour, func() (interface{}, error) {
    return db.GetUser(ctx, 123)
})

func (*Cache) RememberJSON

func (c *Cache) RememberJSON(ctx context.Context, key string, ttl time.Duration, dest interface{}, fn func() (interface{}, error)) error

RememberJSON gets a value or computes and caches it as JSON. Unlike Remember, this preserves type information for GetJSON retrieval.

Example:

var user User
err := cache.RememberJSON(ctx, "user:123", time.Hour, &user, func() (interface{}, error) {
    return db.GetUser(ctx, 123)
})

func (*Cache) Set

func (c *Cache) Set(ctx context.Context, key string, value string, ttl time.Duration) error

Set stores a string value with expiration.

func (*Cache) SetJSON

func (c *Cache) SetJSON(ctx context.Context, key string, value interface{}, ttl time.Duration) error

SetJSON marshals and stores a value as JSON.

func (*Cache) SetNX

func (c *Cache) SetNX(ctx context.Context, key string, value string, ttl time.Duration) (bool, error)

SetNX sets a value only if the key doesn't exist (for distributed locks).

func (*Cache) TTL

func (c *Cache) TTL(ctx context.Context, key string) (time.Duration, error)

TTL returns the remaining TTL of a key.

type CacheConfig

type CacheConfig struct {
	// URL is the Redis connection string.
	// Format: redis://:password@host:port/db or redis://host:port
	URL string

	// Addr is the Redis server address (alternative to URL).
	// Default: localhost:6379
	Addr string

	// Password for Redis authentication.
	Password string

	// DB is the Redis database number.
	// Default: 0
	DB int

	// PoolSize is the maximum number of connections.
	// Default: 10
	PoolSize int

	// MinIdleConns is the minimum number of idle connections.
	// Default: 2
	MinIdleConns int

	// DialTimeout is the timeout for establishing new connections.
	// Default: 5 seconds
	DialTimeout time.Duration

	// ReadTimeout is the timeout for socket reads.
	// Default: 3 seconds
	ReadTimeout time.Duration

	// WriteTimeout is the timeout for socket writes.
	// Default: 3 seconds
	WriteTimeout time.Duration

	// KeyPrefix is prepended to all keys.
	KeyPrefix string
}

CacheConfig configures Redis connection.

func DefaultCacheConfig

func DefaultCacheConfig() CacheConfig

DefaultCacheConfig returns production-ready defaults.

type HTTPConfig

type HTTPConfig struct {
	Timeout   time.Duration // request timeout (default: 30s)
	RetryMax  int           // max retry attempts (default: 3)
	RetryWait time.Duration // wait between retries (default: 1s)
}

HTTPConfig configures HTTP client behavior.

type LogConfig

type LogConfig = logger.Config

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

type MigrateConfig

type MigrateConfig struct {
	// Dir is the directory containing migration files.
	// Default: "migrations"
	Dir string

	// Table is the name of the migrations tracking table.
	// Default: "goose_db_version"
	Table string

	// Dialect is the database dialect (postgres, sqlite3, mysql).
	// Auto-detected if not specified.
	Dialect string

	// FS is an optional filesystem for embedded migrations.
	FS fs.FS

	// AllowMissing allows applying missing (out-of-order) migrations.
	// Default: false
	AllowMissing bool

	// NoVersioning disables version tracking (for one-off scripts).
	// Default: false
	NoVersioning bool
}

MigrateConfig configures database migrations.

func DefaultMigrateConfig

func DefaultMigrateConfig() MigrateConfig

DefaultMigrateConfig returns sensible defaults.

type PostgresConfig

type PostgresConfig = postgres.Config

PostgresConfig is an alias for postgres.Config. Deprecated: Use postgres.Config directly.

func DefaultPostgresConfig

func DefaultPostgresConfig(url string) PostgresConfig

DefaultPostgresConfig returns production-ready defaults. Deprecated: Use postgres.DefaultConfig directly.

type RouterConfig

type RouterConfig struct {
	Middleware []func(http.Handler) http.Handler
	Timeout    time.Duration // request timeout (default: none)
}

RouterConfig configures HTTP router behavior.

type SQLiteConfig

type SQLiteConfig = sqlite.Config

SQLiteConfig is an alias for sqlite.Config. Deprecated: Use sqlite.Config directly.

func DefaultSQLiteConfig

func DefaultSQLiteConfig(path string) SQLiteConfig

DefaultSQLiteConfig returns production-ready defaults. Deprecated: Use sqlite.DefaultConfig directly.

type ValidatorConfig

type ValidatorConfig struct {
	// UseJSONNames uses json tag names in error messages instead of struct field names.
	// Default: true (more useful for API error responses)
	UseJSONNames bool
}

ValidatorConfig configures validation behavior.

Directories

Path Synopsis
cache module
Package cli provides CLI application utilities wrapping Cobra and Lipgloss.
Package cli provides CLI application utilities wrapping Cobra and Lipgloss.
cmd
gokart command
Package logger provides structured logging utilities wrapping log/slog.
Package logger provides structured logging utilities wrapping log/slog.
migrate module
Package postgres provides PostgreSQL utilities wrapping pgx/v5.
Package postgres provides PostgreSQL utilities wrapping pgx/v5.
Package sqlite provides SQLite database utilities wrapping modernc.org/sqlite.
Package sqlite provides SQLite database utilities wrapping modernc.org/sqlite.
web module

Jump to

Keyboard shortcuts

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