📚 roamer - Awesome Go Library for Forms

Go Gopher mascot for roamer

Eliminates boilerplate code for parsing HTTP requests by binding cookies, headers, query params, path params, body to structs and more by using simple tags

🏷️ Forms
📂 Forms
0 stars
View on GitHub 🔗

Detailed Description of roamer

roamer

Go Report Card Build Status Coverage Status Go Reference Go Version GitHub release Mentioned in Awesome Go

Roamer is a flexible, extensible HTTP request parser for Go that makes handling and extracting data from HTTP requests effortless. It provides a declarative way to map HTTP request data to Go structs using struct tags.

graph TD
    subgraph "Input"
        A[HTTP Request]
    end

    subgraph "Data Sources"
        B1[Headers]
        B2[Cookies]
        B3[Query Params]
        B4[Path Variables]
        B5[Request Body]
        B6[Custom]
    end

    subgraph "Roamer Core Engine"
        direction LR
        P[Parsers]
        D[Decoders]
        F[Formatters]
    end

    subgraph "Output"
        E[Populated Go Struct]
    end

    A --> B1 & B2 & B3 & B4 & B5 & B6

    B1 & B2 & B3 & B4 & B6 -- values for --> P
    B5 -- content for --> D

    P -- parsed data --> F
    D -- decoded data --> F

    F -- formatted values --> E

    classDef source stroke:#d4ac0d,stroke-width:4px
    classDef core stroke:#0097c0,stroke-width:4px
    classDef io stroke:#333,stroke-width:4px
    class A,E io
    class B1,B2,B3,B4,B5,B6 source
    class P,D,F core

Features

  • Multiple data sources: Parse data from HTTP headers, cookies, query parameters, path variables, and request body
  • Content-type based decoding: Automatically decode JSON, XML, form data, and multipart forms
  • Default Values: Set default values for fields using the default tag
  • Formatters: Transform parsed data (trim strings, apply numeric constraints, handle time zones, manipulate slices)
  • Router integration: Built-in support for Chi, Gorilla Mux, and HttpRouter
  • Type conversion: Automatic conversion of string values to appropriate Go types
  • Extensibility: Easily create custom parsers, decoders, and formatters
  • Middleware support: Convenient middleware for integrating with HTTP handlers
  • Body preservation: Read request body multiple times when needed

Installation

go get -u github.com/slipros/roamer@latest

For router integrations:

# Chi router
go get -u github.com/slipros/roamer/pkg/chi@latest

# Gorilla Mux
go get -u github.com/slipros/roamer/pkg/gorilla@latest

# HttpRouter
go get -u github.com/slipros/roamer/pkg/httprouter@latest

Quick Start

package main

import (
	"encoding/json"
	"log"
	"net/http"

	"github.com/slipros/roamer"
	"github.com/slipros/roamer/decoder"
	"github.com/slipros/roamer/formatter"
	"github.com/slipros/roamer/parser"
)

// Define request struct with tags
type CreateUserRequest struct {
	Name      string `json:"name" string:"trim_space"`
	Email     string `json:"email" string:"trim_space,lower"`
	Age       int    `query:"age" numeric:"min=18"`
	UserAgent string `header:"User-Agent"`
}

func main() {
	// Initialize roamer
	r := roamer.NewRoamer(
		roamer.WithDecoders(decoder.NewJSON()),
		roamer.WithParsers(
			parser.NewHeader(),
			parser.NewQuery(),
		),
		roamer.WithFormatters(
			formatter.NewString(),
			formatter.NewNumeric(),
		),
	)

	// Create handler
	http.HandleFunc("/users", func(w http.ResponseWriter, req *http.Request) {
		var userReq CreateUserRequest

		// Parse request
		if err := r.Parse(req, &userReq); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		// Use parsed data
		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(map[string]any{
			"name":       userReq.Name,
			"email":      userReq.Email,
			"age":        userReq.Age,
			"user_agent": userReq.UserAgent,
		}); err != nil {
			log.Printf("Failed to encode response: %v", err)
			http.Error(w, "Failed to encode response", http.StatusInternalServerError)
			return
		}
	})

	log.Fatal(http.ListenAndServe(":8080", nil))
}

Examples

Comprehensive examples are available in the examples/ directory:

Basic Usage

Router Integration

Advanced Features

See the examples README for a complete list and how to run them.

Struct Tags Reference

Data Source Tags

TagDescriptionExample
jsonParse from JSON bodyjson:"name"
xmlParse from XML bodyxml:"name"
formParse from URL-encoded formform:"name"
multipartParse from multipart formmultipart:"file"
queryParse from query parametersquery:"page"
headerParse from HTTP headersheader:"User-Agent"
cookieParse from cookiescookie:"session_id"
pathParse from path variablespath:"id"
defaultDefault value if not presentdefault:"1"

Formatter Tags

TagOperationsExample
stringtrim_space, lower, upper, title, snake, camel, kebab, slugstring:"trim_space,lower"
numericmin=N, max=N, abs, round, ceil, floornumeric:"min=0,max=100"
timetimezone=TZ, truncate=UNIT, start_of_day, end_of_daytime:"timezone=UTC"
sliceunique, sort, sort_desc, compact, limit=Nslice:"unique,sort"

Creating Extensions

Custom Parser

type CustomParser struct{}

func (p *CustomParser) Parse(r *http.Request, tag reflect.StructTag, _ parser.Cache) (any, bool) {
	tagValue, ok := tag.Lookup("custom")
	if !ok {
		return "", false
	}
	// Extract and return value
	return value, true
}

func (p *CustomParser) Tag() string {
	return "custom"
}

Custom Decoder

type CustomDecoder struct{}

func (d *CustomDecoder) Decode(r *http.Request, ptr any) error {
	// Decode request body into ptr
	return nil
}

func (d *CustomDecoder) ContentType() string {
	return "application/custom"
}

Custom Formatter

type CustomFormatter struct{}

func (f *CustomFormatter) Format(tag reflect.StructTag, ptr any) error {
	tagValue, ok := tag.Lookup("custom_format")
	if !ok {
		return nil
	}
	// Format the value pointed to by ptr
	return nil
}

func (f *CustomFormatter) Tag() string {
	return "custom_format"
}

See the examples/ directory for complete custom extension examples.

Performance

Roamer is designed for production use with:

  • Efficient reflection techniques
  • Caching for improved performance
  • Optional sync.Pool support for high-throughput applications
  • Minimal allocations in hot paths

Best Practices

  1. Separate request and response structs - Use dedicated structs for parsing requests
  2. Endpoint-specific structs - Create tailored structs for each endpoint to minimize overhead
  3. Use formatters - Let roamer handle common transformations (trimming, case conversion, etc.)
  4. Combine with validation - Use roamer for parsing, then validate with libraries like validator.go

Documentation

Contributing

Contributions are welcome! Please submit issues or pull requests.

License

Roamer is licensed under the MIT License. See the LICENSE file for details.