go-httpx

A lightweight and expressive HTTP client library for Go, inspired by Axios. go-httpx provides a simple and powerful API for making HTTP requests with features like automatic JSON handling, interceptors, and comprehensive configuration options.
Features
- 🚀 Simple API: Easy-to-use methods similar to Axios
- 📦 Auto JSON: Automatic JSON serialization/deserialization
- 🔧 Flexible Configuration: Base URL, headers, timeouts, and more
- 🔄 Interceptors: Request and response interceptors for middleware functionality
- ⚡ Performance: Keep-alive connections and connection pooling
- 🛡️ Error Handling: Comprehensive error handling with detailed error information
- 🔒 Type Safety: Full Go type safety and IDE support
Installation
go get github.com/vanduc0209/go-httpx
Quick Start
Basic Usage
package main
import (
"fmt"
"log"
"github.com/vanduc0209/go-httpx"
)
func main() {
// Simple GET request
resp, err := httpx.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Status: %d\n", resp.Status)
fmt.Printf("Data: %+v\n", resp.Data)
}
Creating a Client Instance
import (
"time"
"github.com/vanduc0209/go-httpx"
)
// Create a configured client
config := &httpx.Config{
BaseURL: "https://api.example.com",
Timeout: 30 * time.Second,
Headers: map[string]string{
"Authorization": "Bearer your-token",
"Content-Type": "application/json",
},
KeepAlive: true,
MaxIdleConns: 100,
}
client := httpx.NewHttpX(config)
// Use the client
resp, err := client.Get("/users")
API Reference
HTTP Methods
GET Request
// Simple GET
resp, err := httpx.Get("https://api.example.com/users")
// GET with query parameters
resp, err := httpx.Get("https://api.example.com/users", &httpx.Request{
Params: map[string]string{
"page": "1",
"limit": "10",
},
})
POST Request
// POST with JSON data
user := map[string]interface{}{
"name": "John Doe",
"email": "[email protected]",
}
resp, err := httpx.Post("https://api.example.com/users", user)
// POST with custom headers
resp, err := httpx.Post("https://api.example.com/users", user, &httpx.Request{
Headers: map[string]string{
"Authorization": "Bearer token",
},
})
PUT Request
// PUT with data
updateData := map[string]interface{}{
"name": "Jane Doe",
}
resp, err := httpx.Put("https://api.example.com/users/1", updateData)
DELETE Request
// DELETE request
resp, err := httpx.Delete("https://api.example.com/users/1")
PATCH Request
// PATCH with partial data
patchData := map[string]interface{}{
"email": "[email protected]",
}
resp, err := httpx.Patch("https://api.example.com/users/1", patchData)
Configuration Options
Client Configuration
config := &httpx.Config{
BaseURL: "https://api.example.com",
Timeout: 30 * time.Second,
MaxRedirects: 5,
Headers: map[string]string{
"Authorization": "Bearer token",
"User-Agent": "MyApp/1.0",
},
KeepAlive: true,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
KeepAliveTimeout: 30 * time.Second,
DisableCompression: false,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 30 * time.Second,
}
client := httpx.NewHttpX(config)
Request Configuration
resp, err := httpx.Get("https://api.example.com/users", &httpx.Request{
Headers: map[string]string{
"Authorization": "Bearer token",
},
Params: map[string]string{
"page": "1",
"limit": "10",
},
Timeout: 10 * time.Second,
})
Interceptors
Request Interceptors
client := httpx.NewHttpX(nil)
// Add request interceptor
client.interceptors.Request = append(client.interceptors.Request, func(req *httpx.Request) error {
// Add authentication header
if req.Headers == nil {
req.Headers = make(map[string]string)
}
req.Headers["Authorization"] = "Bearer " + getToken()
return nil
})
Response Interceptors
// Add response interceptor
client.interceptors.Response = append(client.interceptors.Response, func(resp *httpx.Response) error {
// Log response
fmt.Printf("Response: %d - %s\n", resp.Status, resp.StatusText)
return nil
})
Error Handling
resp, err := httpx.Get("https://api.example.com/users")
if err != nil {
if httpxErr, ok := err.(*httpx.Error); ok {
fmt.Printf("Error Code: %s\n", httpxErr.Code)
fmt.Printf("Error Message: %s\n", httpxErr.Message)
fmt.Printf("HTTP Status: %d\n", httpxErr.Response.Status)
} else {
fmt.Printf("Network Error: %v\n", err)
}
return
}
Utility Methods
client := httpx.NewHttpX(nil)
// Set base URL
client.SetBaseURL("https://api.example.com")
// Set timeout
client.SetTimeout(30 * time.Second)
// Set default headers
client.SetDefaultHeader("Authorization", "Bearer token")
client.SetDefaultHeaders(map[string]string{
"Content-Type": "application/json",
"User-Agent": "MyApp/1.0",
})
Examples
REST API Client
package main
import (
"fmt"
"log"
"time"
"github.com/vanduc0209/go-httpx"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
// Create API client
config := &httpx.Config{
BaseURL: "https://jsonplaceholder.typicode.com",
Timeout: 30 * time.Second,
Headers: map[string]string{
"Content-Type": "application/json",
},
}
client := httpx.NewHttpX(config)
// Get users
resp, err := client.Get("/users")
if err != nil {
log.Fatal(err)
}
// Parse response
var users []User
if userData, ok := resp.Data.([]interface{}); ok {
// Convert to User structs
for _, user := range userData {
if userMap, ok := user.(map[string]interface{}); ok {
users = append(users, User{
ID: int(userMap["id"].(float64)),
Name: userMap["name"].(string),
Email: userMap["email"].(string),
})
}
}
}
fmt.Printf("Found %d users\n", len(users))
}
File Upload
package main
import (
"fmt"
"log"
"os"
"github.com/vanduc0209/go-httpx"
)
func main() {
// Read file
fileData, err := os.ReadFile("example.txt")
if err != nil {
log.Fatal(err)
}
// Upload file
resp, err := httpx.Post("https://httpbin.org/post", fileData, &httpx.Request{
Headers: map[string]string{
"Content-Type": "text/plain",
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Upload successful: %d\n", resp.Status)
}
Authentication with Interceptors
package main
import (
"fmt"
"github.com/vanduc0209/go-httpx"
)
func main() {
client := httpx.NewHttpX(nil)
// Add authentication interceptor
client.interceptors.Request = append(client.interceptors.Request, func(req *httpx.Request) error {
if req.Headers == nil {
req.Headers = make(map[string]string)
}
req.Headers["Authorization"] = "Bearer " + getAuthToken()
return nil
})
// Add logging interceptor
client.interceptors.Response = append(client.interceptors.Response, func(resp *httpx.Response) error {
fmt.Printf("[%s] %s - %d\n", resp.Config.Method, resp.Config.URL, resp.Status)
return nil
})
// All requests will now include authentication and logging
resp, err := client.Get("https://api.example.com/protected")
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
fmt.Printf("Response: %+v\n", resp.Data)
}
func getAuthToken() string {
// Implement your token retrieval logic
return "your-auth-token"
}
Configuration Reference
Config Struct
type Config struct {
BaseURL string // Base URL for all requests
Timeout time.Duration // Default timeout
Headers map[string]string // Default headers
MaxRedirects int // Maximum redirects to follow
// Keep-Alive configuration
KeepAlive bool // Enable/disable keep-alive
MaxIdleConns int // Maximum idle connections
MaxIdleConnsPerHost int // Maximum idle connections per host
MaxConnsPerHost int // Maximum connections per host
IdleConnTimeout time.Duration // Idle connection timeout
KeepAliveTimeout time.Duration // Keep-alive timeout
// Additional transport settings
DisableCompression bool // Disable gzip compression
TLSHandshakeTimeout time.Duration // TLS handshake timeout
ResponseHeaderTimeout time.Duration // Response header timeout
}
Request Struct
type Request struct {
Method string // HTTP method
URL string // Request URL
Headers map[string]string // Request headers
Data interface{} // Request body data
Params map[string]string // Query parameters
Timeout time.Duration // Request timeout
}
Response Struct
type Response struct {
Data interface{} // Response data (parsed JSON or string)
Status int // HTTP status code
StatusText string // HTTP status text
Headers map[string]string // Response headers
Config *Request // Original request config
}
Error Types
The library provides detailed error information through the Error struct:
type Error struct {
Message string // Error message
Code string // Error code
Config *Request // Request configuration
Request *http.Request // Original HTTP request
Response *Response // Response (if available)
}
Common error codes:
JSON_MARSHAL_ERROR: Failed to serialize request data
REQUEST_CREATION_ERROR: Failed to create HTTP request
NETWORK_ERROR: Network/connection error
RESPONSE_READ_ERROR: Failed to read response body
HTTP_ERROR: HTTP error (4xx, 5xx status codes)
Keep-Alive Connections
config := &httpx.Config{
KeepAlive: true,
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
KeepAliveTimeout: 30 * time.Second,
}
Connection Pooling
The library automatically manages connection pooling when keep-alive is enabled. This is especially beneficial for:
- High-frequency requests to the same host
- API clients making multiple requests
- Microservices communicating with each other
Timeout Configuration
config := &httpx.Config{
Timeout: 30 * time.Second, // Client timeout
TLSHandshakeTimeout: 10 * time.Second, // TLS handshake timeout
ResponseHeaderTimeout: 30 * time.Second, // Response header timeout
}
Best Practices
1. Use Client Instances for Repeated Requests
// Good: Create a client instance
client := httpx.NewHttpX(&httpx.Config{
BaseURL: "https://api.example.com",
Timeout: 30 * time.Second,
})
// Use the same client for multiple requests
resp1, _ := client.Get("/users")
resp2, _ := client.Get("/posts")
2. Handle Errors Properly
resp, err := httpx.Get("https://api.example.com/users")
if err != nil {
if httpxErr, ok := err.(*httpx.Error); ok {
switch httpxErr.Code {
case "HTTP_ERROR":
// Handle HTTP errors (4xx, 5xx)
fmt.Printf("HTTP Error: %d\n", httpxErr.Response.Status)
case "NETWORK_ERROR":
// Handle network errors
fmt.Printf("Network Error: %s\n", httpxErr.Message)
default:
// Handle other errors
fmt.Printf("Error: %s\n", httpxErr.Message)
}
}
return
}
3. Use Interceptors for Cross-Cutting Concerns
client := httpx.NewHttpX(nil)
// Add logging interceptor
client.interceptors.Request = append(client.interceptors.Request, func(req *httpx.Request) error {
fmt.Printf("Making request: %s %s\n", req.Method, req.URL)
return nil
})
client.interceptors.Response = append(client.interceptors.Response, func(resp *httpx.Response) error {
fmt.Printf("Received response: %d\n", resp.Status)
return nil
})
config := &httpx.Config{
Timeout: 30 * time.Second, // Overall request timeout
TLSHandshakeTimeout: 10 * time.Second, // TLS timeout
ResponseHeaderTimeout: 30 * time.Second, // Header timeout
}
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature)
- Commit your changes (
git commit -m 'Add some amazing feature')
- Push to the branch (
git push origin feature/amazing-feature)
- Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by Axios for JavaScript
- Built with Go's standard
net/http package
- Designed for simplicity and performance