

RequestCache — request\_cache\_plug v1.0.1

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache.ex#L1 "View Source")

RequestCache

(request\_cache\_plug v1.0.1)

===================================================================================================================================================================

[requestcache](#module-requestcache)

 RequestCache

---------------------------------------------------

[![Test](https://github.com/MikaAK/request_cache_plug/actions/workflows/test-actions.yml/badge.svg)](https://github.com/MikaAK/request_cache_plug/actions/workflows/test-actions.yml)

[![codecov](https://codecov.io/gh/MikaAK/request_cache_plug/branch/main/graph/badge.svg?token=RF4ASVG5PV)](https://codecov.io/gh/MikaAK/request_cache_plug)

[![Hex version badge](https://img.shields.io/hexpm/v/request_cache_plug.svg)](https://hex.pm/packages/request_cache_plug)

This plug allows us to cache our graphql queries and phoenix controller requests declaritevly

We call the cache inside either a resolver or a controller action and this will store it preventing further

executions of our query on repeat requests.

The goal of this plug is to short-circuit any processing phoenix would

normally do upon request including json decoding/parsing, the only step that should run is telemetry

### 

[installation](#module-installation)

 Installation

This package can be installed by adding `request_cache_plug` to your list of dependencies in `mix.exs`:

```

def deps do

 [

 {:request\_cache\_plug, "~> 0.2"}

 ]

end

```

Documentation can be found at <https://hexdocs.pm/request_cache_plug>.

### 

[config](#module-config)

 Config

This is the default config, it can all be changed

```

config :request\_cache\_plug,

 enabled?: true,

 verbose?: false,

 graphql\_paths: ["/graphiql", "/graphql"],

 conn\_priv\_key: :\_\_shared\_request\_cache\_\_,

 request\_cache\_module: RequestCache.ConCacheStore,

 default\_ttl: :timer.hours(1),

 default\_concache\_opts: [

 ttl\_check\_interval: :timer.seconds(1),

 acquire\_lock\_timeout: :timer.seconds(1),

 ets\_options: [write\_concurrency: true, read\_concurrency: true]

 ]

```

### 

[usage](#module-usage)

 Usage

This plug is intended to be inserted into the `endpoint.ex` fairly early in the pipeline,

it should go after telemetry but before our parsers

```

plug Plug.Telemetry, event\_prefix: [:phoenix, :endpoint]

plug RequestCache.Plug

plug Plug.Parsers,

 parsers: [:urlencoded, :multipart, :json],

 pass: ["\*/\*"]

```

We also need to setup a before\_send hook to our absinthe\_plug (if not using absinthe you can skip this step)

```

plug Absinthe.Plug, before\_send: {RequestCache, :connect\_absinthe\_context\_to\_conn}

```

What this does is allow us to see the results of items we put onto our request context from within plugs coming after absinthe

After that we can utilize our cache in a few ways

#### Utilization with Phoenix Controllers

```

def index(conn, params) do

 conn

 |> RequestCache.store(:timer.seconds(60))

 |> put\_status(200)

 |> json(%{...})

end

```

#### Utilization with Absinthe Resolvers

```

def all(params, \_resolution) do

 # Instead of returning {:ok, value} we return this

 RequestCache.store(value, :timer.seconds(60))

end

```

#### Utilization with Absinthe Middleware

```

field :user, :user do

 arg :id, non\_null(:id)

 middleware RequestCache.Middleware, ttl: :timer.seconds(60)

 resolve &Resolvers.User.find/2

end

```

### 

[specifying-specific-caching-locations](#module-specifying-specific-caching-locations)

 Specifying Specific Caching Locations

We have a few ways to control the caching location of our RequestCache, by default if you have `con_cache` installed,

we have access to `RequestCache.ConCacheStore` which is the default setting

However we can override this by setting `config :request_cache_plug, :request_cache_module, MyCustomCache`

Caching module will be expected to have the following API:

```

def get(key) do

 ...

end

def put(key, ttl, value) do

 ...

end

```

You are responsible for starting the cache, including ConCacheStore, so if you're planning to use it make sure

you add `RequestCache.ConCacheStore` to the application.ex list of children

***Specifying the module per function is currently not fully implemented, check back soon for updates***

We can also override the module for a particular request by passing the option to our graphql middleware or

our `&RequestCache.store/2` function as `[ttl: 123, cache: MyCacheModule]`

##### With Middleware

```

field :user, :user do

 arg :id, non\_null(:id)

 middleware RequestCache.Middleware, ttl: :timer.seconds(60), cache: MyCacheModule

 resolve &Resolvers.User.find/2

end

```

##### In a Resolver

```

def all(params, resolution) do

 RequestCache.store(value, ttl: :timer.seconds(60), cache: MyCacheModule)

end

```

##### In a Controller

```

def index(conn, params) do

 RequestCache.store(conn, ttl: :timer.seconds(60), cache: MyCacheModule)

end

```

### 

[telemetry](#module-telemetry)

 telemetry

Cache events are emitted via :telemetry. Events are:

* `[:request_cache_plug, :graphql, :cache_hit]`

* `[:request_cache_plug, :graphql, :cache_miss]`

* `[:request_cache_plug, :rest, :cache_hit]`

* `[:request_cache_plug, :rest, :cache_miss]`

* `[:request_cache_plug, :cache_put]`

For GraphQL endpoints it is possible to provide a list of atoms that will be passed through to the event metadata; e.g.:

##### With Middleware

```

field :user, :user do

 arg :id, non\_null(:id)

 middleware RequestCache.Middleware,

 ttl: :timer.seconds(60),

 cache: MyCacheModule,

 labels: [:service, :endpoint],

 whitelisted\_query\_names: ["MyQueryName"] # By default all queries are cached, can also whitelist based off query name from GQL Document

 resolve &Resolvers.User.find/2

end

```

##### In a Resolver

```

def all(params, resolution) do

 RequestCache.store(value, ttl: :timer.seconds(60), cache: MyCacheModule, labels: [:service, :endpoint])

end

```

The events will look like this:

```

{

 [:request\_cache\_plug, :graphql, :cache\_hit],

 %{count: 1},

 %{ttl: 3600000, cache\_key: "/graphql:NNNN", labels: [:service, :endpoint]}

}

```

##### Enable Error Caching

In order to enable error caching we can either setup `cached_errors` in our config

or as an option to `RequestCache.store` or [`RequestCache.Middleware`](RequestCache.Middleware.html).

The value of `cached_errors` can be one of `[]`, `:all` or a list of reason\_atoms as

defined by [`Plug.Conn.Status`](https://hexdocs.pm/plug/1.13.4/Plug.Conn.Status.html) such as `:not_found`, or `:internal_server_error`.

In REST this works off the response codes returned. However, in order to use reason\_atoms in GraphQL

you will need to make sure your errors contain some sort of `%{code: "not_found"}` response in them

Take a look at [error\_message](https://github.com/MikaAK/elixir_error_message) for a compatible error system

### 

[notes-gotchas](#module-notes-gotchas)

 Notes/Gotchas

* In order for this caching to work, we cannot be using POST requests as specced out by GraphQL, not for queries at least, fortunately this doesn't actually matter since we can use any http method we want (there will be a limit to query size), in a production app you may be doing this already due to the caching you gain from CloudFlare

* Caches are stored via a MD5 hashed key that correlates to your query in GraphQL, or in REST your url path + query parameters

* Absinthe and ConCache are optional dependencies, if you don't have them you won't have access to [`RequestCache.Middleware`](RequestCache.Middleware.html) or `RequestCache.ConCacheStore`

* If no ConCache is found, you must set `config :request_cache_module` to something else

### 

[caching-header](#module-caching-header)

 Caching Header

When an item is served from the cache, we return a header `rc-cache-status` which has a value of `HIT`. Using this you can tell if the item was

served out of cache, without it the item was fetched

### 

[example-reduction](#module-example-reduction)

 Example Reduction

In the case of a large (16mb) payload running through absinthe, this plug cuts down response times from 400+ms -> <400μs

![image](https://user-images.githubusercontent.com/4650931/161464277-713e994b-1246-43ac-82a1-fb2442cd7bce.png)

[Link to this section](#summary)

 Summary

==========================================

[Types](#types)

----------------

[opts()](#t:opts/0)

[Functions](#functions)

------------------------

[connect\_absinthe\_context\_to\_conn(conn, blueprint)](#connect_absinthe_context_to_conn/2)

[store(conn, opts\_or\_ttl \\ [])](#store/2)

[Link to this section](#types)

Types

=====================================

[Link to this type](#t:opts/0 "Link to this type")

opts()

======

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache.ex#L6 "View Source")

```

@type opts() :: [ttl: [pos\_integer](https://hexdocs.pm/elixir/typespecs.html#basic-types)(), cache: [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)(), cached_errors: :all | [[atom](https://hexdocs.pm/elixir/typespecs.html#basic-types)()]]

```

[Link to this section](#functions)

Functions

=============================================

[Link to this function](#connect_absinthe_context_to_conn/2 "Link to this function")

connect\_absinthe\_context\_to\_conn(conn, blueprint)

=====================================================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache.ex#L33 "View Source")

[Link to this function](#store/2 "Link to this function")

store(conn, opts\_or\_ttl \\ [])

================================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache.ex#L13 "View Source")

```

@spec store(conn :: [Plug.Conn.t](https://hexdocs.pm/plug/1.13.4/Plug.Conn.html#t:t/0)(), opts_or_ttl :: [opts](#t:opts/0)() | [pos\_integer](https://hexdocs.pm/elixir/typespecs.html#basic-types)()) ::

  [Plug.Conn.t](https://hexdocs.pm/plug/1.13.4/Plug.Conn.html#t:t/0)()

```





RequestCache.Middleware — request\_cache\_plug v1.0.1

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/middleware.ex#L4 "View Source")

RequestCache.Middleware

(request\_cache\_plug v1.0.1)

=========================================================================================================================================================================================

[Link to this section](#summary)

 Summary

==========================================

[Functions](#functions)

------------------------

[store\_result(result, ttl)](#store_result/2)

[Link to this section](#functions)

Functions

=============================================

[Link to this function](#store_result/2 "Link to this function")

store\_result(result, ttl)

==========================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/middleware.ex#L83 "View Source")

```

@spec store_result(

  result :: [any](https://hexdocs.pm/elixir/typespecs.html#basic-types)(),

  opts_or_ttl :: [RequestCache.opts](RequestCache.html#t:opts/0)() | [pos\_integer](https://hexdocs.pm/elixir/typespecs.html#basic-types)()

) :: {:middleware, [module](https://hexdocs.pm/elixir/typespecs.html#built-in-types)(), [RequestCache.opts](RequestCache.html#t:opts/0)()}

```





RequestCache.Plug — request\_cache\_plug v1.0.1

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L1 "View Source")

RequestCache.Plug

(request\_cache\_plug v1.0.1)

=============================================================================================================================================================================

This plug allows you to cache GraphQL requests based off their query name and

variables. This should be placed right after telemetry and before parsers so that it can

stop any processing of the requests and immediately return a response.

Please see [`RequestCache`](RequestCache.html) for more details

[Link to this section](#summary)

 Summary

==========================================

[Functions](#functions)

------------------------

[empty\_errors\_pattern()](#empty_errors_pattern/0)

[error\_codes\_pattern(cached\_errors)](#error_codes_pattern/1)

[error\_pattern()](#error_pattern/0)

[request\_cache\_header()](#request_cache_header/0)

[store\_request(conn, opts)](#store_request/2)

[Link to this section](#functions)

Functions

=============================================

[Link to this function](#empty_errors_pattern/0 "Link to this function")

empty\_errors\_pattern()

========================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L216 "View Source")

[Link to this function](#error_codes_pattern/1 "Link to this function")

error\_codes\_pattern(cached\_errors)

=====================================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L219 "View Source")

[Link to this function](#error_pattern/0 "Link to this function")

error\_pattern()

================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L217 "View Source")

[Link to this function](#request_cache_header/0 "Link to this function")

request\_cache\_header()

========================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L20 "View Source")

[Link to this function](#store_request/2 "Link to this function")

store\_request(conn, opts)

==========================

[View Source](https://github.com/mikaak/request_cache_plug/blob/main/lib/request_cache/plug.ex#L251 "View Source")

