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 inschema.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
If you click on the schema in the right-hand corner like in the image below.
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.
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
Navigate to http://localhost:4000/graphiql
We are in the playground again
Now type in our first query in the left-hand side like in the image below:
{
posts{
id
title
body
}
}
Now hit the play button and you should get the data, we seeded earlier.
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)