DEV Community

wolfiton
wolfiton

Posted on • Edited on

Absinthe Journey with Wolfiton or How to Bring the Absinthe Tutorial UP to Date- Part 4 The Door

Hi everyone,

Series navigation
PART 1 Introduction
PART 2 Foundation
PART 3 The Walls
PART 4 The Door
PART 5 Testing
PART 6 Can you fly?
PART 7 The User

Welcome to part 4 of the series but before we jump in let's recap what we did in the third part:

  • We configured our project

  • Added git version control

  • created our first migration, schema, and context

  • updated our post-migration using alter table

We are ready to start working with Graphql

Section 1

Schema

Let's create a new feature using hub:

  • navigate to the project root in the terminal
cd ~/Codes/wolf_blog
  • create a new feature(functionality)
hub checkout -b 01-The_Door 

The output should look like this

hub checkout -b 01-The_Door                                                                                                     1 ✘  at 07:11:00 
Switched to a new branch '01-The_Door'

The schema file will be placed in /lib/wolf_blog_web

Create a new file and call it schema.ex and type in

defmodule WolfBlogWeb.Schema do

end

We use modules in Elixir to keep functions in different namespaces, also there are not classes or inheritance, this is Functional Programming.

Bringing Absinthe(graphql library)

If you remember Dear Reader in part 3 of this series we installed some packages and one of them was Absinthe.

Now we can call it and put it to work in our schema.ex type:

defmodule WolfBlogWeb.Schema do
  use Absinthe.Schema # add this to power up our schema
end

But wait! this gives us an error that says that we don't have a type, an object, and a query.

Let's fix that by creating /lib/wolf_blog_web/types

Now create a new file in the types folder, called post_types.ex and type in:

defmodule WolfBlogWeb.Types.PostTypes do
  use Absinthe.Schema.Notation

  @desc "Post fields that can be interrogated(get)"
  object :post do
    @desc "The post id"
    field :id, :id
    @desc "The title of the post"
    field :title, :string
    @desc "The post body"
    field :body, :string
  end

end


We use desc to describe and document what each field is and also here we describe what the post type is representing. The @desc documentation is recommended and it should be used for others to be able to understand your code easier.

Also, this should look familiar to you because they represent the migration and the schema we used last time.

Let's add this to the schema to shutdown one error regarding the types.

If you don't see an error Dear reader, it is because you haven't installed the Elixir_lsp extension yet(recommended in part 3 of this series).

Back to the schema.ex we go, let's add the types:

defmodule WolfBlogWeb.Schema do
  use Absinthe.Schema
  alias WolfBlogWeb.Resolvers.PostResolver

  import_types WolfBlogWeb.Types.PostTypes

  query  do
    @desc "list_all_posts"
    field :posts, list_of(:post) do
      resolve &PostResolver.list_all_posts/3
    end
  end

end

Section 2

Resolvers

We made our error go away from our schema.ex file but we still got an undefined function list_all_post().

So to fix this, we need to create a folder resolver and a file that will connect our context with the Absinthe Schema. This is done using a resolver.

Let's create /lib/wolf_blog_web/resolvers and inside the folder a file called post_resolver.ex

In post_resolver.ex type:

defmodule WolfBlogWeb.Resolvers.PostResolver do
  alias WolfBlog.Blog

  def list_all_posts(_, _, _) do
    {:ok, Blog.list_posts()} #here we use an ok tuple 
  end
end

The tuple above expects to receive an output(a value) from Blog.list_posts(). if it doesn't receive one it will throw an error.

Section 3

Routes

To be able to interact with our Graphql API, we need to define some routes.

Let's open up /lib/wolf_blog_web/router.ex

It should look like this:

defmodule WolfBlogWeb.Router do
  use WolfBlogWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/api", WolfBlogWeb do
    pipe_through :api
  end
end

We are interested in this part:

scope "/api", WolfBlogWeb do
    pipe_through :api
end

So scope is a block of code that can group many routes. You can think of scope like a box that can have many things from the same type(only router related). For example, it can have 3 balls.

So let's add our routes for the Graphql Schema:


defmodule WolfBlogWeb.Router do
  use WolfBlogWeb, :router

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/" do
    pipe_through :api

    forward "/graphql", Absinthe.Plug , schema:   WolfBlogWeb.Absinthe.Schema

    forward "/graphiql", Absinthe.Plug.GraphiQL, schema: WolfBlogWeb.Schema, interface: :playground
  end
end

forward will send the request to a specific route and add different route options on that request. In our case, it will add the Absinthe Plug and our Absinthe Schema we defined earlier in schema.ex.

Note: Absinthe Plug offers two Graphql interfaces the playground and a simple interface.

Now we can start our API using the following command:

mix phx.server

You should see Dear reader, the Graphql playground interface image

Graphql playground interface

If you click on the schema in the right-hand corner like in the image below.

Alt Text

Then the following menu should pop up with information regarding our schema and query. Now click the posts: [Post] option in this menu and the following should appear.

Alt Text

As we can see the @desc from our query is present and also all our @desc from our fields are present.

Now you can see that documenting the code really helps and anyone else that will use our API will understand what everything is.

Now close the browser and also stop the mix phx.server, command using Ctrl + c.

To make our lives easier let's add some seeds.

Section 4

Seed data for dev and test

To be able to seed data we will need to use structs.

So let's change our post schema first:

cd ~/Codes/wolf_blog/lib/wolf_blog/blog/post.ex

defmodule WolfBlog.Blog.Post do
  use Ecto.Schema
  import Ecto.Changeset

  schema "posts" do
    field :body, :string
    field :title, :string

    timestamps()
  end

  @doc false
  def changeset(%Post{} = post, attrs) do
    post
    |> cast(attrs, [:title, :body])
    |> validate_required([:title, :body])
    |> unique_constraint(:title)
  end
end

You might get an error that looks like this now

** (CompileError) lib/wolf_blog/blog/post.ex:13: 
Post.__struct__/0 is undefined, cannot expand struct Post
    lib/wolf_blog/blog/post.ex:13: (module)

It says in plain English that it doesn't understand where the Post struct is and can't extend it.

Let's fix it

defmodule WolfBlog.Blog.Post do
  use Ecto.Schema
  import Ecto.Changeset
  alias WolfBlog.Blog.Post # add this to tell the struct what post is

  schema "posts" do
    field :body, :string
    field :title, :string

    timestamps()
  end

  @doc false
  def changeset(%Post{} = post, attrs) do
    post
    |> cast(attrs, [:title, :body])
    |> validate_required([:title, :body])
    |> unique_constraint(:title)
  end
end

The error is now gone so let's write our Seeder:

cd ~/Codes/wolf_blog/lib/wolf_blog/seeder.ex

Type in


defmodule WolfBlog.Seeder do
  def power_up() do

    alias WolfBlog.Blog.Post
    alias WolfBlog.Repo

    absinthe_title =  "Absinthe is great"

    absinthe_body =  "Absinthe can really make working with Phoenix and Graphql much easier.The big advantage is that you can also test the code."

    _article = %Post{title: absinthe_title , body: absinthe_body} |> Repo.insert() #Bring the values from title and body and create
                                                                  # an article that get's added to the DB

    :ok
  end
end

Thanks @nobbz and @cevado for helping me realize that I was using the struct when I needed a string.

Optional, how I did it wrong

absinthe_title = %Post{"Absinthe is great"} #this is a struct but due to a lot of work lately, I got tired and couldn't see it.

So they send me in the right direction by showing me the error I made.

The seed is ready but in order to activate it, we need to tell the seeds.ex file in:

cd ~/Codes/wolf_blog/lib/priv/repo/seeds.exs


WolfBlog.Seeder.power_up() # add this line after all 
                           # the comments in the file

Let's run our seed, using this command:

mix run priv/repo/seeds.exs

Let's try our first query!

Type in the terminal

mix phx.server

Now type in our first query in the left-hand side like in the image below:

{
  posts{
    id
    title
    body
  }
}

Alt Text

Now hit the play button and you should get the data, we seeded earlier.

Alt Text

Good now let's add all to our feature using:

hub add .
hub commit -m "Add Schema resolver types for post and the route for graphql"

hub push --set-upstream origin 01-The_Door

I hope you enjoy this part of the series Dear Reader and if you also find it useful, share it on social.

Credits:

Thanks, @nobbz , and @cevado for helping me realize that I was using the struct when I needed a string.

Top comments (0)