Caching in Golang using Memcached
Tags: go, tutorial, beginners, webdev
Published: 2021-08-27 17:37:47

In the past I had already taught how to cache data from our [in-memory](https://dev.to/franciscomendes10866/easy-and-simple-in-memory-cache-in-golang-1lpb) applications, as well as how to cache using an external resource such as [Redis](https://dev.to/franciscomendes10866/caching-in-golang-using-redis-418n).

The idea of today's application is to make a request to the [JSONPlaceholder](https://jsonplaceholder.typicode.com/) API and we will get a photo according to the id that is provided in the parameters.

The framework I'm going to use is [Fiber](https://gofiber.io/), if you've used Express.js in the past or a similar framework, you'll feel comfortable creating our Api.

The library I will be using in this article as a Memcached client is [gomemcache](https://github.com/bradfitz/gomemcache) as it is quite simple to configure and use.

# Let's code

First let's install the following packages:

```sh
go get github.com/gofiber/fiber/v2
go get github.com/bradfitz/gomemcache/memcache
```

Then let's create a simple API:

```go
package main

import "github.com/gofiber/fiber/v2"

func main() {
    app := fiber.New()

    app.Get("/", func(c *fiber.Ctx) error {
        return c.SendString("I hope it runs 😅")
    })

    app.Listen(":3000")
}
```

To run the API use the following command:

```sh
go run .
```

If you are testing our Api, you will receive the `I hope it runs 😅` message in the body of the response.

Now if we are going to get a single photo, we will have the following json in the body of the response:

```js
{
  "albumId": 1,
  "id": 1,
  "title": "accusamus beatae ad facilis cum similique qui sunt",
  "url": "https://via.placeholder.com/600/92c952",
  "thumbnailUrl": "https://via.placeholder.com/150/92c952"
}
```

Now we have to create our struct which we'll call **Photo**, but first let's create a file called `utils.go`. The struct will contain the following fields:

```go
// @utils.go

package main

type Photo struct {
    AlbumID      int    `json:"albumId"`
    ID           int    `json:"id"`
    Title        string `json:"title"`
    URL          string `json:"url"`
    ThumbnailURL string `json:"thumbnailUrl"`
}
```

This way we can make some changes to our endpoint, first we will add the `id` parameter. Then we will get the value of it using the `c.Params()` function.

```go
app.Get("/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    // ...
})
```

Now we will make the http request to fetch a single photo according to the `id` that is passed in the parameters. And we will return that same photo.

```go
app.Get("/:id", func(c *fiber.Ctx) error {
    id := c.Params("id")
    res, err := http.Get("https://jsonplaceholder.typicode.com/photos/" + id)
    if err != nil {
        return err
    }

    defer res.Body.Close()
    body, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return err
    }

    photo := Photo{}
    parseErr := json.Unmarshal(body, &photo)
    if parseErr != nil {
        return parseErr
    }

    return c.JSON(fiber.Map{"Data": photo})
})
```

Now if you test our Api, not forgetting to indicate the id parameter, we will get the data from the respective photo. However we are constantly receiving data directly from the JSONPlaceholder API.

What we want is to keep the photo data for a certain time in the cache, and during that time, the data we should receive must come from the cache.

But first of all, let's go back to the `utils.go` file and create a function called `toJson()`, which will take a buffer as an argument and will return the photo's json.

```go
// @utils.go

package main

import "encoding/json"

type Photo struct {
    AlbumID      int    `json:"albumId"`
    ID           int    `json:"id"`
    Title        string `json:"title"`
    URL          string `json:"url"`
    ThumbnailURL string `json:"thumbnailUrl"`
}

func toJson(val []byte) Photo {
    photo := Photo{}
    err := json.Unmarshal(val, &photo)
    if err != nil {
        panic(err)
    }
    return photo
}
```

Now we can import the gomemcache library and let's create our Memcached client. In this example we only need to indicate the host and its port.

```go
package main

import (
    "encoding/json"
    "io/ioutil"
    "net/http"

    "github.com/bradfitz/gomemcache/memcache"
    "github.com/gofiber/fiber/v2"
)

var cache = memcache.New("localhost:11211")

// ...
```

Now, on our endpoint, we have to cache our app's data before sending the data in the response. For that, we'll use the `cache.Set()` function to which we'll pass a single argument that contains three properties. 

The first is the **key**, which in this case is the *id*, the second is the **value**, which in this case is the *body* (response body buffer) and finally the **expiration** time in seconds, into which I set the value *10*.

```go
app.Get("/:id", func(c *fiber.Ctx) error {
    // ...

    cacheErr := cache.Set(&memcache.Item{Key: id, Value: body, Expiration: 10})
    if cacheErr != nil {
        panic(cacheErr)
    }

    return c.JSON(fiber.Map{"Data": photo})
})
```

Still on the endpoint, we still need to remove the logic we have with `json.Unmarshal()` and replace it with the `toJson()` function we created.

For that we will pass the body of the answer as the only argument of the function and we will return the function data in the answer of our Api.

```go
app.Get("/:id", func(c *fiber.Ctx) error {
    // ...

    cacheErr := cache.Set(&memcache.Item{Key: id, Value: body, Expiration: 10})
    if cacheErr != nil {
        panic(cacheErr)
    }

    data := toJson(body)
    return c.JSON(fiber.Map{"Data": data})
})
```

Now we will have the data cached, but we haven't finished this yet because we still need to create a middleware.

What this middleware will do is check if the key already exists in the cache, if it does we will return the data we have stored in the cache.

But if the key doesn't exist in the cache, we will execute the next method of our route, which in this case will be the execution of the http request.

```go
func verifyCache(c *fiber.Ctx) error {
    // ...
}
```

First we have to get the value of the `id` parameter. Then we will use the `cache.Get()` which only needs one argument, which is the **key**, which we're going to pass the *id* to.

```go
func verifyCache(c *fiber.Ctx) error {
    id := c.Params("id")
    val, err := cache.Get(id)
    // ...
}
```

If the key does not exist we will proceed to the next method to perform the http request, using the `c.Next()` function.

```go
func verifyCache(c *fiber.Ctx) error {
    id := c.Params("id")
    val, err := cache.Get(id)
    if err != nil {
        return c.Next()
    }

    // ...
}
```

Otherwise we will use the `toJson()` function again to convert the buffer to json and we will return it.

```go
func verifyCache(c *fiber.Ctx) error {
    id := c.Params("id")
    val, err := cache.Get(id)
    if err != nil {
        return c.Next()
    }

    data := toJson(val.Value)
    return c.JSON(fiber.Map{"Cached": data})
}
```

Last but not least we need to add middleware to our route, like this:

```go
app.Get("/:id", verifyCache, func(c *fiber.Ctx) error {
    // ...
})
```

The final code should look like the following:

```go
package main

import (
	"io/ioutil"
	"net/http"

	"github.com/bradfitz/gomemcache/memcache"
	"github.com/gofiber/fiber/v2"
)

var cache = memcache.New("localhost:11211")

func verifyCache(c *fiber.Ctx) error {
	id := c.Params("id")
	val, err := cache.Get(id)
	if err != nil {
		return c.Next()
	}

	data := toJson(val.Value)
	return c.JSON(fiber.Map{"Cached": data})
}

func main() {
	app := fiber.New()

	app.Get("/:id", verifyCache, func(c *fiber.Ctx) error {
		id := c.Params("id")
		res, err := http.Get("https://jsonplaceholder.typicode.com/photos/" + id)
		if err != nil {
			panic(err)
		}

		defer res.Body.Close()
		body, err := ioutil.ReadAll(res.Body)
		if err != nil {
			panic(err)
		}

		cacheErr := cache.Set(&memcache.Item{Key: id, Value: body, Expiration: 10})
		if cacheErr != nil {
			panic(cacheErr)
		}

		data := toJson(body)
		return c.JSON(fiber.Map{"Data": data})
	})

	app.Listen(":3000")
}
```

Now, if we are going to test the performance of our app, we might notice a big difference in its performance. Without the use of the cache each request took an average of `327ms`, after the implementation of the cache each request took an average of `6ms`.

## Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 😁

Hope you have a great day! 🤙