In this article you are going to learn how to count unique IP addresses on page visits using Redis Sets and Go.
You are going to implement 2 simple REST endpoints to complete the exercise.
1. Running Redis with Docker #
Run Redis using Docker with this simple command:
docker run --rm --name redis 6379:6379 -d redis:latest
2. Initiating the Go Project #
Create a new Go project and install essential Redis
and Chi
packages. If you prefer a different HTTP router, feel free to use it.
# Create a new Go project
mkdir go-redis-sets
# Install Chi for routing
go get -u github.com/go-chi/chi/v5
# Install Redis package
go get github.com/redis/go-redis/v9
2.1 Connecting to Redis #
Connect your Go project to Redis.
package main import ( "context" "fmt" "github.com/redis/go-redis/v9" ) func main() { ctx := context.Background() // Connect to Redis rdb := redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", DB: 0, }) // Ping Redis to ensure the connection _, err := rdb.Ping(ctx).Result() if err != nil { panic(err) } }
main.go
2.2 Setting up Chi Router #
Configure a Chi router for handling HTTP requests and run the server on port 8080
.
package main import ( "context" "fmt" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/redis/go-redis/v9" ) func main() { // ... (Existing redis code) // Create a new Chi router mux := chi.NewRouter() // Start the HTTP server http.ListenAndServe(":8080", mux) }
main.go
3. Rest Endpoints #
Now let's implement REST 2 endpoints.
- One which will act as our main endpoint to collect
IP
addresses on. - And another to list out all the
IP
addresses for a given endpoint.
3.1 GET article endpoint #
Use the Redis command SADD to add an item to a set.
Each time a request is received add the associated IP
address to the associated "set" of the article.
package main import ( "context" "fmt" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/redis/go-redis/v9" ) func main() { // ... (Existing code) mux.Get("/articles/{articleId}", func(w http.ResponseWriter, r *http.Request) { // Extract the 'articleId' parameter from the request URL articleId := chi.URLParam(r, "articleId") // Create a Redis key using the articleId for storage key := fmt.Sprintf("articles:%s", articleId) // Get client IP address ip := r.RemoteAddr // Add the IP address to a Redis Set associated with the article _, err := rdb.SAdd(r.Context(), key, ip).Result() if err != nil { fmt.Println(err) // Respond with an internal server error status and message w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("internal server error")) return } // Respond with a success status and message w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) }
main.go
3.2 Get article metrics endpoint #
Write an endpoint to fetch the unique IP Addresses related to a given article.
Use the Redis command SMEMBERS to list out all the items inside a set.
package main import ( "context" "fmt" "net/http" "strings" "github.com/go-chi/chi/v5" "github.com/redis/go-redis/v9" ) func main() { // ... (Existing code) mux.Get("/articles/{articleId}/metrics", func(w http.ResponseWriter, r *http.Request) { // Extract the 'articleId' parameter from the request URL articleId := chi.URLParam(r, "articleId") // Create a Redis key using the articleId for fetching metrics key := fmt.Sprintf("articles:%s", articleId) // Retrieve the members (IP addresses) from the Redis Set associated with the article members, err := rdb.SMembers(ctx, key).Result() if err != nil { fmt.Println(err) // Respond with an internal server error status and message w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("internal server error")) return } // Respond with a success status and the joined members (IP addresses) w.WriteHeader(http.StatusOK) w.Write([]byte(strings.Join(members, "\n"))) }) }
main.go