go-geolocation
August 8, 2025 ยท View on GitHub
A framework-agnostic Go module for geolocation, inspired by php-geolocation. Provides core geolocation features and adapters for popular Go web frameworks.
Features
- Extracts country from Cloudflare headers
- Parses browser, OS, device, and language from standard headers
- Local development simulation - Fake Cloudflare headers for testing without production setup
- Auto-detection of local environments (localhost, local IPs, missing Cloudflare headers)
- Advanced language negotiation - matches browser and available site languages for multi-language countries
- Comprehensive client info - browser, OS, device type (including tablet), screen resolution
- Built-in country data for 8 countries (US, CA, GB, DE, FR, JP, AU, BR)
- Middleware/adapters for net/http, Gin, Echo, Fiber
- Testable, modular design
- High test coverage and CI integration
Installation
go get go.rumenx.com/geolocation
Usage
Core Usage
import "go.rumenx.com/geolocation"
// In your handler:
loc := geolocation.FromRequest(r)
client := geolocation.ParseClientInfo(r)
languages := geolocation.ParseLanguageInfo(r)
Framework Adapters
Ready-to-use examples for popular Go web frameworks are available in the examples/ directory:
| Framework | Port | Adapter Path | Description |
|---|---|---|---|
| net/http | 8080 | adapters/nethttp | Standard library HTTP server integration |
| Gin | 8081 | adapters/gin | Gin web framework integration |
| Echo | 8082 | adapters/echo | Echo web framework integration |
| Fiber | 8083 | adapters/fiber | Fiber web framework integration |
Two Integration Approaches
- Import Adapter Packages โ For clean middleware integration:
go get go.rumenx.com/geolocation/adapters/gin # or echo, fiber, nethttp
- Copy Example Applications โ For quick start with full applications:
# Clone and run complete example servers
git clone https://github.com/RumenDamyanov/go-geolocation.git
cd go-geolocation/examples/gin-adapter && go run main.go
Quick Start with Framework Examples
# Clone the repository
git clone https://github.com/RumenDamyanov/go-geolocation.git
cd go-geolocation
# Run net/http example (port 8080)
cd examples/nethttp-adapter && go run main.go
# Run Gin example (port 8081)
cd examples/gin-adapter && go run main.go
# Run Echo example (port 8082)
cd examples/echo-adapter && go run main.go
# Run Fiber example (port 8083)
cd examples/fiber-adapter && go run main.go
Test the Examples
# Download geolocation data
curl "http://localhost:8080/" | jq
# Simulate a specific country
curl "http://localhost:8081/simulate/DE" | jq
# Get available countries
curl "http://localhost:8082/countries" | jq
Integration Pattern Example
Each framework adapter follows the same pattern:
package main
import (
"github.com/gin-gonic/gin"
"go.rumenx.com/geolocation"
ginadapter "go.rumenx.com/geolocation/adapters/gin"
)
func main() {
r := gin.Default()
r.Use(ginadapter.Middleware())
r.GET("/location", func(c *gin.Context) {
loc := ginadapter.FromContext(c)
clientInfo := geolocation.ParseClientInfo(c.Request)
c.JSON(200, gin.H{
"location": loc,
"client": clientInfo,
})
})
r.Run(":8080")
}
Gin Example
package main
import (
"github.com/gin-gonic/gin"
"go.rumenx.com/geolocation"
ginadapter "go.rumenx.com/geolocation/adapters/gin"
)
func main() {
r := gin.Default()
r.Use(ginadapter.Middleware())
r.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
loc := ginadapter.FromContext(c)
c.JSON(200, gin.H{
"user": name,
"location": loc,
"local": geolocation.IsLocalDevelopment(c.Request),
})
})
r.Run(":8080")
}
Echo Example
package main
import (
"github.com/labstack/echo/v4"
"go.rumenx.com/geolocation"
echoadapter "go.rumenx.com/geolocation/adapters/echo"
)
func main() {
e := echo.New()
e.Use(echoadapter.Middleware())
e.GET("/user/:name", func(c echo.Context) error {
name := c.Param("name")
loc := echoadapter.FromContext(c)
return c.JSON(200, map[string]interface{}{
"user": name,
"location": loc,
"local": geolocation.IsLocalDevelopment(c.Request()),
})
})
e.Start(":8080")
}
Fiber Example
package main
import (
"github.com/gofiber/fiber/v2"
fiberadapter "go.rumenx.com/geolocation/adapters/fiber"
)
func main() {
app := fiber.New()
app.Use(fiberadapter.Middleware())
app.Get("/user/:name", func(c *fiber.Ctx) error {
name := c.Params("name")
loc := fiberadapter.FromContext(c)
return c.JSON(fiber.Map{
"user": name,
"location": loc,
"local": loc.IP == "",
})
})
app.Listen(":8080")
}
net/http Example
package main
import (
"encoding/json"
"net/http"
"go.rumenx.com/geolocation"
httpadapter "go.rumenx.com/geolocation/adapters/nethttp"
)
func main() {
mux := http.NewServeMux()
mux.Handle("/user", httpadapter.HTTPMiddleware(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
loc := httpadapter.FromContext(r.Context())
clientInfo := geolocation.ParseClientInfo(r)
response := map[string]interface{}{
"location": loc,
"client": clientInfo,
"local": geolocation.IsLocalDevelopment(r),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
})))
http.ListenAndServe(":8080", mux)
}
Example Output
IP: 1.2.3.4
Country: BG
Browser: Chrome 123.0.0.0
OS: Windows NT 10.0
Device: Desktop
DefaultLang: en-US
AllLangs: [en-US en bg de]
Local Development Simulation
When developing locally where Cloudflare is not available, you can simulate its functionality:
Quick Simulation
// Create a simulated request for a specific country
req := geolocation.Simulate("DE", nil)
info := geolocation.GetGeoInfo(req)
fmt.Printf("Country: %s, IP: %s\n", info.CountryCode, info.IP)
Advanced Simulation
// Custom simulation options
req := geolocation.Simulate("JP", &geolocation.SimulationOptions{
UserAgent: "Custom User Agent",
Languages: []string{"ja", "en"},
})
Auto-Detection of Local Environment
req := getRequest() // your HTTP request
if geolocation.IsLocalDevelopment(req) {
fmt.Println("Running in local development mode")
}
Available Countries for Simulation
countries := geolocation.GetAvailableCountries()
// Returns: ["US", "CA", "GB", "DE", "FR", "JP", "AU", "BR"]
randomCountry := geolocation.RandomCountry()
Advanced Features
Comprehensive Client Information
info := geolocation.GetGeoInfo(req)
fmt.Printf("Country: %s\n", info.CountryCode)
fmt.Printf("IP: %s\n", info.IP)
fmt.Printf("Browser: %s %s\n", info.Browser, info.BrowserVersion)
fmt.Printf("OS: %s\n", info.OS)
fmt.Printf("Device: %s\n", info.Device) // Desktop, Mobile, Tablet
fmt.Printf("Languages: %v\n", info.AllLanguages)
fmt.Printf("Resolution: %dx%d\n", info.Resolution.Width, info.Resolution.Height)
Advanced Language Negotiation
cfg := &geolocation.Config{
DefaultLanguage: "en",
CountryToLanguageMap: map[string][]string{
"CA": {"en", "fr"}, // Canada: English (default), French
"CH": {"de", "fr", "it"}, // Switzerland: German, French, Italian
},
}
// Sophisticated language selection based on:
// 1. Browser preferred language matching country languages and available site languages
// 2. All browser languages for matches with available languages
// 3. First country language as fallback
availableSiteLanguages := []string{"en", "fr", "de", "es"}
bestLang := geolocation.GetLanguageForCountry(req, cfg, "CH", availableSiteLanguages)
// Check if language cookie should be set
if geolocation.ShouldSetLanguage(req, "lang") {
// Set language in your application
geolocation.SetCookie(w, "lang", bestLang, &http.Cookie{MaxAge: 86400 * 30})
}
Screen Resolution Detection
// Frontend JavaScript would set these headers:
// req.Header.Set("X-Screen-Width", "1920")
// req.Header.Set("X-Screen-Height", "1080")
resolution := geolocation.GetResolution(req)
fmt.Printf("Screen: %dx%d\n", resolution.Width, resolution.Height)
Testing
Run all tests:
go test ./...
Check coverage:
go test -cover ./...
Note on Coverage:
All error branches and edge cases in the core package are thoroughly tested. Due to Go's coverage tool behavior, a few lines in
LoadConfigmay not be counted as covered, even though all real error paths (file not found, invalid JSON/YAML, unsupported extension) are exercised in tests. The code is idiomatic and robust; further refactoring for the sake of 100% coverage is not recommended.
CI & Coverage
- GitHub Actions for CI
- Codecov integration for test coverage
License
Advanced Usage
Combining Geolocation, Client Info, and Language
You can extract all available information in a single handler:
import (
"fmt"
"net/http"
"github.com/rumendamyanov/go-geolocation"
)
func handler(w http.ResponseWriter, r *http.Request) {
loc := geolocation.FromRequest(r)
info := geolocation.ParseClientInfo(r)
lang := geolocation.ParseLanguageInfo(r)
fmt.Fprintf(w, "IP: %s\nCountry: %s\nBrowser: %s %s\nOS: %s\nDevice: %s\nDefaultLang: %s\nAllLangs: %v\n",
loc.IP, loc.Country, info.BrowserName, info.BrowserVersion, info.OS, info.Device, lang.Default, lang.Supported)
}
Custom Middleware Example (net/http)
You can create your own middleware to attach all info to the request context:
import (
"context"
"net/http"
"github.com/rumendamyanov/go-geolocation"
)
type contextKey struct{}
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
loc := geolocation.FromRequest(r)
info := geolocation.ParseClientInfo(r)
lang := geolocation.ParseLanguageInfo(r)
ctx := context.WithValue(r.Context(), contextKey{}, struct {
*geolocation.Location
*geolocation.ClientInfo
*geolocation.LanguageInfo
}{loc, info, lang})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
Using in API Responses
You can return all extracted info as JSON in your API endpoints for debugging or analytics:
import (
"encoding/json"
"net/http"
"github.com/rumendamyanov/go-geolocation"
)
func apiHandler(w http.ResponseWriter, r *http.Request) {
resp := struct {
*geolocation.Location
*geolocation.ClientInfo
*geolocation.LanguageInfo
}{
geolocation.FromRequest(r),
geolocation.ParseClientInfo(r),
geolocation.ParseLanguageInfo(r),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
Documentation
For comprehensive documentation and additional examples:
- Contributing Guidelines - How to contribute to this project
- Security Policy - Security guidelines and vulnerability reporting
- Code of Conduct - Community guidelines and behavior expectations
- Funding - Support and sponsorship information
- Wiki - Extended examples and tutorials
Contributing
We welcome contributions! Please see our Contributing Guidelines for details on how to submit pull requests, report issues, and contribute to the project.
Support
If you find this project helpful, please consider:
- โญ Starring the repository
- ๐ Reporting issues or suggesting features
- ๐ Supporting via GitHub Sponsors
For detailed support information, see FUNDING.md.