Redis Sets and Go - Counting unique IP Addresses on page visits
Read more articles on Redis, Go
Redis Sets and Go - Counting unique IP Addresses on page visits

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
    TheDeveloperCafe © 2022-2024