Redis cache store backend

For read-only systems, like Heroku, you won't be able to use the built-in Mnesia backend cache for distribution and to persist cache data between restarts. Instead let's use Redix to store our cache data in Redis.

First add Redix to your list of dependencies in mix.exs:

def deps do
    # ...
    {:redix, "~> 0.9.2"}

Now set up your WEB_PATH/pow_redis_cache.ex like so:

defmodule MyAppWeb.PowRedisCache do
  @behaviour Pow.Store.Base

  alias Pow.Config

  @redix_instance_name :redix

  def put(config, key, value) do
    key     = redis_key(config, key)
    ttl     = Config.get(config, :ttl)
    value   = :erlang.term_to_binary(value)
    command = put_command(key, value, ttl)

    Redix.noreply_command(@redix_instance_name, command)

  defp put_command(key, value, ttl) when is_integer(ttl) and ttl > 0, do: ["SET", key, value, "PX", ttl]
  defp put_command(key, value, _ttl), do: ["SET", key, value]

  def delete(config, key) do
    key = redis_key(config, key)

    Redix.noreply_command(@redix_instance_name, ["DEL", key])

  def get(config, key) do
    key = redis_key(config, key)

    case Redix.command(@redix_instance_name, ["GET", key]) do
      {:ok, nil}   -> :not_found
      {:ok, value} -> :erlang.binary_to_term(value)

  def keys(config) do
    namespace = redis_key(config, "")
    length    = String.length(namespace)

    {:ok, values} = Redix.command(@redix_instance_name, ["KEYS", "#{namespace}*"]), &String.slice(&1, length..-1))

  defp redis_key(config, key) do
    namespace = Config.get(config, :namespace, "cache")


We'll need to start the Redix application on our app startup, so in application.ex add {Redix, name: :redix} to your supervision tree:

  def start(_type, _args) do
    import Supervisor.Spec

    children = [
      # ...
      {Redix, name: :redix}

    opts = [strategy: :one_for_one, name: MyApp.Supervisor]
    Supervisor.start_link(children, opts)

By default localhost Redis is used, but you can update this by using a Redis URI: {Redix, {"redis://", [name: :redix]}}

Finally update the config with your new Redis cache backend:

config :my_app, :pow,
  user: MyApp.Users.User,
  repo: MyApp.Repo,
  cache_store_backend: MyAppWeb.PowRedisCache

And now you've a running Redis cache store backend!

Test module

defmodule MyAppWeb.PowRedisCacheTest do
  use ExUnit.Case
  doctest MyAppWeb.PowRedisCache

  alias MyAppWeb.PowRedisCache

  @default_config [namespace: "test", ttl: :timer.hours(1)]

  test "can put, get and delete records" do
    assert PowRedisCache.get(@default_config, "key") == :not_found

    PowRedisCache.put(@default_config, "key", "value")
    assert PowRedisCache.get(@default_config, "key") == "value"

    PowRedisCache.delete(@default_config, "key")
    assert PowRedisCache.get(@default_config, "key") == :not_found

  test "fetch keys" do
    PowRedisCache.put(@default_config, "key1", "value")
    PowRedisCache.put(@default_config, "key2", "value")

    assert Enum.sort(PowRedisCache.keys(@default_config)) == ["key1", "key2"]

  test "records auto purge" do
    config = Keyword.put(@default_config, :ttl, 100)

    PowRedisCache.put(config, "key", "value")
    assert PowRedisCache.get(config, "key") == "value"
    assert PowRedisCache.get(config, "key") == :not_found