Writing your own Absinthe DSL with macros and middleware
Tags: phoenix, absinthe, elixir, graphql
Published: 2019-01-03 18:08:54

---
date: "2019-01-03T19:08:54+01:00"
cover_image: "https://maartenvanvliet.nl/macro.jpg"
tags: ["phoenix", "absinthe", "elixir", "graphql"]
title: "Writing your own Absinthe DSL with macros and middleware"
canonical_url: "https://maartenvanvliet.nl/2018/12/29/absinthe_macro_dsl/"
published: true
---
Absinthe is a great library to do graphql in Elixir. However, when writing your resolvers you may find that you are writing some boilerplate multiple times. So, in the spirit of keeping your code DRY, in this post I'll show how we can leverage middleware and a macro to write your own DSL for your Graphql api.

I assume some basic knowledge on how Absinthe works, most of the information can be found on [in the guides](https://hexdocs.pm/absinthe/overview.html)

A common problem in writing api's is making sure that the queries are only accessible for logged in users. To do so in Absinthe we need to set a `current_user` on the context of a graphql query, see the [guides](https://hexdocs.pm/absinthe/context-and-authentication.html#basic-usage) for an example.

To make sure that our resolvers are only accessible when the current_user is set we can pattern match on its existence.

```elixir
query do
    field :profile_picture, :string do
      resolve fn
        _, %{context: %{current_user: current_user}} ->
        {:ok, current_user.profile_picture_url}
        _, _ ->
        {:error, "Not logged in"}
      end
    end
end
```
We match on the presence of `current_user` and if it is not there we return an error. This can get quite repetitive however if you have a lot of authenticated fields. With some middleware we can clean this up.

```elixir
# authenticated.ex
defmodule Example.Authenticated do
  @behaviour Absinthe.Middleware

  def call(resolution, _config) do
    case resolution.context do
      %{current_user: %{}} ->
        resolution

      _ ->
        Absinthe.Resolution.put_result(resolution, {:error, "Not logged in"})
    end
  end
end
```

The middleware implements the `Absinthe.Middleware` behaviour. This requires a `call/2` function that gets passed the current resolution struct. In this struct we can get the context of the current query and find out if the `current_user` is present. Now we need to call the middleware for our field

```elixir
query do
    field :profile_picture, :string do
      middleware Example.Authenticated
      resolve fn
        _, %{context: %{current_user: current_user}} ->
        {:ok, current_user.profile_picture_url}
      end
    end
end
```
We placed the middleware before the resolver, any non-authenticated call will be met with the error and when the `current_user` is present it is simply passed along to the resolver.

This is already a much cleaner option and we can leave this as is. Nonetheless, to showcase how you can use a macro to make a cleaner DSL for your app I'll give a little example.

Create a new file with your macro definition:
```elixir
defmodule Example.IsAuthenticated do
  defmacro is_authenticated() do
    quote do
      middleware(Example.Authenticated, %{})
    end
  end
end
```
This macro does very little, I declare it and the only thing it does is call the `middleware/2` we have seen earlier with the Authenticated middleware. When the macro is invoked it will be rewritten into everything in the `quote` block.

We can rewrite our resolver like this
```elixir
import Example.IsAuthenticated 
query do
    field :profile_picture, :string do
        is_authenticated()
        resolve fn _, %{context: %{current_user: current_user}} ->
            {:ok, current_user.profile_picture_url}
        end
    end
end
```
Note that we need to import to macro for it to work. In our field definition the macro `is_authenticated` is called and rewritten into the call for the middleware. As you can imagine the `middleware/2` is [also a macro](https://hexdocs.pm/absinthe/Absinthe.Schema.Notation.html#middleware/2). 

This is a very simple example of how you can use macros to do code generation for you. For graphql there are many options regarding e.g. authorization or pagination where macros can keep your code more DRY.

Republished from (https://maartenvanvliet.nl/2018/12/29/absinthe_macro_dsl/)

Photo by gil on Unsplash