A production-ready, fully typed HTTP backend framework for Go 1.25.6+ using only the Go standard library.
GoCharge eliminates boilerplate and provides compile-time type safety for your HTTP handlers without external dependencies. Requires Go 1.25.6+ to take advantage of all features including method-aware routing. Build fast, maintainable APIs with confidence.
// Type-safe handlers with automatic error handling
func createUserHandler(ctx context.Context, w gocharge.Response[User], r gocharge.Request[CreateUserRequest]) error {
req, err := r.JSON()
if err != nil {
return gocharge.NewAppError(gocharge.ErrBadRequest, "Invalid JSON")
}
// Business logic...
user := createUser(req)
_, err = w.Status(gocharge.StatusCreated).JSON(user)
return err
}
// Register with type-safe path parameters
gc.RegisterHandler(server, "POST /api/users", createUserHandler)
gc.RegisterHandler(server, "GET /api/users/{id}", getUserHandler)
gc.RegisterHandler(server, "PUT /api/users/{id}", updateUserHandler)
gc.RegisterHandler(server, "DELETE /api/users/{id}", deleteUserHandler)- Typed Handlers: Generic request/response types with compile-time safety
- Typed Errors: Strongly-typed error codes with automatic HTTP status mapping
- Typed Context: Type-safe context value storage and retrieval
- Path Parameters: Type-safe extraction with built-in converters (string, int, int64, UUID)
- Zero External Dependencies: Only Go stdlib (except tests)
- Structured Logging: Built-in slog integration with custom logger support
- Error Handling: Automatic error encoding with customizable error responses
- Middleware System: Composable middleware chain with built-in logging and recovery
- Method-Aware Routes: Go 1.22+ native HTTP method support in paths
- Minimal Boilerplate: No interfaces to implement, just write handlers
- Response Chaining: Fluent API for building responses
- Automatic JSON: Built-in JSON encoding/decoding
- Request Validation: Type-safe request parsing with error propagation
- Comprehensive Documentation: Inline examples for every feature
- 54+ Tests: Comprehensive test coverage
- Zero Linting Issues: golangci-lint clean
- Security Scanned: gosec validation
- Multi-Version Testing: Tested on Go 1.21, 1.22, 1.25
go get github.com/huijiro/go-chargeRequirements:
- Go 1.25.6 or later (for method-aware paths and full feature support)
- No external dependencies
package main
import (
"context"
gc "github.com/huijiro/go-charge"
)
func main() {
server := gc.New(":8080")
// Register handlers
gc.RegisterHandler(server, "GET /health", healthHandler)
gc.RegisterHandler(server, "POST /api/todos", createTodoHandler)
// Start server
server.ListenAndServe()
}type CreateTodoRequest struct {
Title string `json:"title"`
}
type TodoResponse struct {
ID string `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}func createTodoHandler(ctx context.Context, w gc.Response[TodoResponse], r gc.Request[CreateTodoRequest]) error {
logger := gc.GetLogger()
// Parse and validate request
req, err := r.JSON()
if err != nil {
logger.Warn(ctx, "invalid json")
return gc.NewAppError(gc.ErrBadRequest, "Invalid request body")
}
if req.Title == "" {
return gc.NewAppError(gc.ErrValidation, "Title is required")
}
// Business logic
todo := createTodo(req.Title)
logger.Info(ctx, "todo created", "id", todo.ID)
// Send response
_, err = w.Status(gc.StatusCreated).JSON(TodoResponse{
ID: todo.ID,
Title: todo.Title,
Done: todo.Done,
})
return err
}// Request[T] wraps http.Request with typed body
type Request[T any] struct {
Data T
http.Request
}
// Response[T] wraps http.ResponseWriter with typed JSON
type Response[T any] struct {
ResponseWriter http.ResponseWriter
Data T
StatusCode int
}// Create typed errors
err := gc.NewAppError(gc.ErrBadRequest, "Invalid email")
err := gc.NewAppError(gc.ErrNotFound, "User not found")
err := gc.NewAppError(gc.ErrValidation, "Password too short")
// Built-in error codes with automatic HTTP status mapping
gc.ErrBadRequest // 400
gc.ErrUnauthorized // 401
gc.ErrForbidden // 403
gc.ErrNotFound // 404
gc.ErrValidation // 400
gc.ErrInternalError // 500func getUserHandler(ctx context.Context, w gc.Response[User], r gc.Request[string]) error {
// Extract string parameter
userID, err := r.PathParam().String("id")
if err != nil {
return gc.NewAppError(gc.ErrBadRequest, err.Error())
}
user, found := getUser(userID)
if !found {
return gc.NewAppError(gc.ErrNotFound, "User not found")
}
_, err = w.JSON(user)
return err
}
// Supported parameter types:
id, err := r.PathParam().String("id") // Extract string
count, err := r.PathParam().Int("count") // Extract int
offset, err := r.PathParam().Int64("offset") // Extract int64
uuid, err := r.PathParam().UUID("id") // Extract & validate RFC 4122 UUID
// Register with method-aware paths
gc.RegisterHandler(server, "GET /api/users/{id}", getUserHandler)
gc.RegisterHandler(server, "PUT /api/users/{id}", updateUserHandler)
gc.RegisterHandler(server, "DELETE /api/users/{id}", deleteUserHandler)logger := gc.GetLogger()
// Log with context and structured fields
logger.Info(ctx, "user created", "user_id", user.ID, "email", user.Email)
logger.Warn(ctx, "invalid input", "reason", "email_taken")
logger.Error(ctx, "database error", err)
// Custom logger support
type CustomLogger struct{}
func (l *CustomLogger) Info(ctx context.Context, msg string, fields ...interface{}) { }
// Implement Logger interface...
gc.SetLogger(customLogger)// Access built-in middleware
chain := server.Middleware()
chain.Use(middleware.LoggingMiddleware) // Logs all requests/responses
chain.Use(middleware.RecoveryMiddleware) // Catches panics, returns 500
// Create custom middleware
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Missing token", http.StatusUnauthorized)
return
}
// Verify token and add to context...
next.ServeHTTP(w, r)
})
}
chain.Use(authMiddleware)// Fluent API for building responses
_, err = w
.Status(gc.StatusCreated)
.JSON(TodoResponse{
ID: todo.ID,
Title: todo.Title,
Done: todo.Done,
})
return err
// Or write raw content
_, err = w
.Status(gc.StatusOK)
.Write([]byte("Hello, World!"))
return err// Store values in request context
ctx = gc.WithUser(ctx, user)
ctx = gc.WithTraceID(ctx, traceID)
// Retrieve values with type safety
user, hasUser := gc.UserFromContext(ctx)
traceID, hasTraceID := gc.TraceIDFromContext(ctx)
// Custom typed values
ctx = gc.WithValue(ctx, "key", value)
val, hasVal := gc.Value[MyType](ctx, "key")A complete, production-ready TODO API example demonstrating all GoCharge features:
cd examples/todo-api
go run main.goEndpoints:
POST /api/todos- Create todoGET /api/todos- List todosGET /api/todos/{id}- Get single todoPUT /api/todos/{id}- Update todoDELETE /api/todos/{id}- Delete todo
Features:
- Type-safe handlers
- Path parameter extraction
- Request validation
- Structured logging
- Error handling
- Middleware integration
- Integration tests
See examples/todo-api/README.md for detailed documentation and curl examples.
A minimal example showing the basics:
cd examples/hello-world
go run main.goSee examples/hello-world for the complete example.
gocharge/
├── gocharge.go # Server initialization and core types
├── handler.go # Handler registration and wrapping
├── request.go # Request wrapper with JSON decoding & path params
├── response.go # Response wrapper with chaining
├── errors.go # Error types and encoding
├── context.go # Context helpers and typed values
├── logger.go # Logger interface and default implementation
├── middleware.go # Middleware chain system
└── middleware/
├── logger.go # Built-in logging middleware
└── recovery.go # Built-in panic recovery middleware
1. HTTP Request
↓
2. RegisterHandler wraps with type safety
↓
3. Extract path parameters (Go 1.22+ PathValue)
↓
4. Create typed Request[T] with body data
↓
5. Create typed Response[W]
↓
6. Call user handler with context
↓
7. Handler returns error or nil
↓
8. Automatic error encoding (if error returned)
↓
9. HTTP Response
GoCharge provides automatic error handling with customizable encoders:
// Default error encoder produces:
{
"code": "BAD_REQUEST",
"message": "Invalid email format"
}
// Customize error responses
type CustomErrorEncoder struct{}
func (e *CustomErrorEncoder) Encode(ctx context.Context, err error) (interface{}, int) {
// Custom error formatting logic
return response, statusCode
}
gc.SetErrorEncoder(server, customEncoder)Error Response Example:
{
"code": "VALIDATION_ERROR",
"message": "Title must be between 1 and 100 characters"
}GoCharge applications are straightforward to test:
func TestCreateTodo(t *testing.T) {
server := gc.New(":8081")
gc.RegisterHandler(server, "POST /api/todos", createTodoHandler)
// Start server
go server.ListenAndServe()
defer server.Close()
// Make request
resp, err := http.Post(
"http://localhost:8081/api/todos",
"application/json",
bytes.NewBuffer([]byte(`{"title":"Test"}`)),
)
// Assert response
assert.Equal(t, http.StatusCreated, resp.StatusCode)
}See examples/todo-api/main_test.go for comprehensive integration tests.
GoCharge is built for performance:
- No reflection overhead: Types resolved at compile time
- Minimal allocations: Efficient request/response wrapping
- Direct stdlib integration: No abstraction layers
- Zero dependencies: Direct http.ServeMux usage
Contributions are welcome! Please ensure:
- ✅ All tests pass:
go test ./... - ✅ No linting issues:
golangci-lint run - ✅ Security scanning passes:
gosec ./... - ✅ Code is well-documented
MIT License - see LICENSE.md for details
- Framework Documentation: Package docs
- Todo API Example:
examples/todo-api/README.md - Hello World Example:
examples/hello-world
Built with ❤️ for developers who value type safety and simplicity.