go-geolocation

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"
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:
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:
git clone https://github.com/RumenDamyanov/go-geolocation.git
cd go-geolocation/examples/gin-adapter && go run main.go
Quick Start with Framework Examples
git clone https://github.com/RumenDamyanov/go-geolocation.git
cd go-geolocation
cd examples/nethttp-adapter && go run main.go
cd examples/gin-adapter && go run main.go
cd examples/echo-adapter && go run main.go
cd examples/fiber-adapter && go run main.go
Test the Examples
curl "http://localhost:8080/" | jq
curl "http://localhost:8081/simulate/DE" | jq
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
req := geolocation.Simulate("DE", nil)
info := geolocation.GetGeoInfo(req)
fmt.Printf("Country: %s, IP: %s\n", info.CountryCode, info.IP)
Advanced Simulation
req := geolocation.Simulate("JP", &geolocation.SimulationOptions{
UserAgent: "Custom User Agent",
Languages: []string{"ja", "en"},
})
Auto-Detection of Local Environment
req := getRequest()
if geolocation.IsLocalDevelopment(req) {
fmt.Println("Running in local development mode")
}
Available Countries for Simulation
countries := geolocation.GetAvailableCountries()
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)
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"},
"CH": {"de", "fr", "it"},
},
}
availableSiteLanguages := []string{"en", "fr", "de", "es"}
bestLang := geolocation.GetLanguageForCountry(req, cfg, "CH", availableSiteLanguages)
if geolocation.ShouldSetLanguage(req, "lang") {
geolocation.SetCookie(w, "lang", bestLang, &http.Cookie{MaxAge: 86400 * 30})
}
Screen Resolution Detection
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 LoadConfig
may 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
MIT
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
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.