Categories
Software

Improve your local web app development with Docker

Storage

At the beginning I said that we would use Redis in this app. Redis is great as a simple key-value store which we can use for demonstrating how to communicate with a database within our Docker container setup.

We will add the Redis configuration to our docker-compose file:

docker-compose.yaml

version: '3'

services:
  app:
    working_dir: /app
    stdin_open: true
    image: golang:1.14-alpine
    volumes:
      - ./:/app
    env_file:
      - .env
    ports:
      - ${HTTP_PORT}:${HTTP_PORT}

  redis:
    command: redis-server --port ${REDIS_PORT}
    image: redis:6.0-alpine
    env_file:
      - .env
    ports:
      - ${REDIS_PORT}:${REDIS_PORT}

The container is named redis, and it will simply start redis-server on the port we define in the .env file. To the .env file, add the following variables:

.env

REDIS_PORT=8002
REDIS_HOST=redis

REDIS_HOST here is set to the same name as the container. This is because within the containerized network infrastructure, Docker aliases the Redis container to have that hostname, so we can use this instead of trying to figure out where on the host machine this container was set up on.

Now we want to do something with the storage within the go application. To do this, we will need to use a Redis client: go-redis is a perfect one for our needs. We will also need to set up the module system to install these packages (within the container).

Run the following:

$ docker-compose up -d
$ docker-compose exec app go mod init <name-of-your-module>
$ docker-compose exec app go get github.com/go-redis/redis

This will set up your go.mod module file and install go-redis.

As for what to do with the actual implementation: how about we just increment a counter, and display this on the original endpoint?

main.go

package main

import (
    "context"
    "fmt"
    "github.com/go-redis/redis/v8"
    "log"
    "net/http"
    "os"
)

func main() {
    rdb := redis.NewClient(&redis.Options{
        Addr: os.Getenv("REDIS_HOST") + ":" + os.Getenv("REDIS_PORT"),
    })

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        val, err := rdb.Incr(context.Background(), "counter").Result()

        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }

        w.Write([]byte(fmt.Sprintf("Hello, world! You are visitor #%d", val)))
    })

    port := os.Getenv("HTTP_PORT")

    log.Printf("listening on port %s\n", port)

    panic(http.ListenAndServe(":"+port, nil))
}

Run the app and hit the endpoint. You should see the counter increment in the response!

$ curl http://localhost:8000
Hello, world! You are visitor #1
$ curl http://localhost:8000
Hello, world! You are visitor #2

If a developer is using their host Redis instance, they can configure REDIS_HOST and REDIS_PORT to point to said instance instead of using the Docker instance as well, meaning that we have the full flexibility that was originally desired.