This is the second post in a series on using Phoenix and GraphQL to create clean and powerful API’s. In this post I will cover mutations and how they can be used to add CRUD functionality to an app. For those who are just joining us, I recommend going through the last post before starting this one. However, you can start where we left off last time by cloning the repo and checking out the checkpoint-1 branch. If you go that route you’ll want to mix run priv/repo/seeds.exs in order to populate your database with some dummy data after you clone the repo and create your database (postgres).

Preparing the Posts Model

We will be adding CRUD functionality to our post model and will need to make some changes to ensure that everything works properly. Currently, the post changeset doesn’t allow any other fields besides :title and :body . Let’s add :user_id to the cast and validate_required functions so we can properly support associations between users and posts. Our new changeset should look like this:

web/models/post.ex def changeset(struct, params \\ %{}) do struct |> cast(params, [:title, :body, :user_id]) |> validate_required([:title, :body, :user_id]) end 1 2 3 4 5 def changeset ( struct , params \ \ % { } ) do struct | > cast ( params , [ : title , : body , : user_id ] ) | > validate_required ( [ : title , : body , : user_id ] ) end

Now we should be able to accept a :user_id .

Mutations

In GraphQL, there are only two types of operations: Queries and Mutations. A mutation is an operation that “mutates” the underlying data system. Mutations are how you create, read, update, or delete (CRUD) data. You can also use a mutation for something like a login operation in which you send an email and password to the server in order to generate a JWT to send back to the user (I’ll cover that in a future post). For now, we will go through each of the parts in CRUD to demonstrate how this is done in GraphQL.

Create

Our post changeset requires that we pass it 3 arguments: :title , :body , and :user_id . As a result, we will need to create a mutation that requires these 3 arguments. Open up your web/schema.ex file and add the following code:

web/schema.ex query do field :posts, list_of(:post) do resolve &MyApp.PostResolver.all/2 end field :users, list_of(:user) do resolve &MyApp.UserResolver.all/2 end end mutation do field :create_post, type: :post do arg :title, non_null(:string) arg :body, non_null(:string) arg :user_id, non_null(:integer) resolve &MyApp.PostResolver.create/2 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 query do field : posts , list_of ( : post ) do resolve & MyApp . PostResolver . all / 2 end field : users , list_of ( : user ) do resolve & MyApp . UserResolver . all / 2 end end mutation do field : create_post , type : : post do arg : title , non_null ( : string ) arg : body , non_null ( : string ) arg : user_id , non_null ( : integer ) resolve & MyApp . PostResolver . create / 2 end end

As you can see, we’ve added a new mutation block to the bottom of the module. Inside this block we define a :create_post mutation. It accepts each of the arguments we need and makes them required by using the non_null helper. This mutation block is where all of your mutations will go. Now, you may be thinking, “won’t this get super gnarly if we have a lot of mutations?” The answer is yes. Fortunately, Absinthe provides us with some helper functions to clean up our code. I’ll be covering them in more detail in a future post but for the curious you can check them out here. Also, for some examples of how import_types and import_fields work you can see them in action in this test file.

Let’s now define MyApp.PostResolver.create/2 to get this mutation working:

web/resolvers/post_resolver.ex defmodule MyApp.PostResolver do alias MyApp.Repo alias MyApp.Post def all(_args, _info) do {:ok, Repo.all(Post)} end def create(args, _info) do %Post{} |> Post.changeset(args) |> Repo.insert end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 defmodule MyApp . PostResolver do alias MyApp . Repo alias MyApp . Post def all ( _args , _info ) do { : ok , Repo . all ( Post ) } end def create ( args , _info ) do % Post { } | > Post . changeset ( args ) | > Repo . insert end end

With that change we should now be able to create new posts. Open up GraphiQL at http://localhost:4000/graphiql and lets try adding a post. Run the following operation:

mutation CreatePost { create_post(title: "My Post Title", body: "Awesome Post", user_id: 1) { id } } 1 2 3 4 5 mutation CreatePost { create_post ( title : "My Post Title" , body : "Awesome Post" , user_id : 1 ) { id } }

It works! Congratulations, you’ve added your first mutation. One thing to note here is that we can ask the mutation to query data about the new post after it’s been created. In this example we are asking the mutation to give us the new :id .

We can skip the R in CRUD since we’ve already covered querying for data so let’s move on to adding update functionality.

Update

Before we can implement update functionality I need to introduce a new concept. GraphQL mutation arguments can be one of a few types of data (the full list). One of those types is the input_object . It is similar to the other GraphQL Objects that we’ve made previously but it represents an object of data that is passed to a mutation as an argument. Let’s create one to represent the post_params for updating a post and then we will define the :update_post mutation:

web/schema.ex input_object :update_post_params do field :title, non_null(:string) field :body, non_null(:string) field :user_id, non_null(:integer) end mutation do field :create_post, type: :post do arg :title, non_null(:string) arg :body, non_null(:string) arg :user_id, non_null(:integer) resolve &MyApp.PostResolver.create/2 end field :update_post, type: :post do arg :id, non_null(:integer) arg :post, :update_post_params resolve &MyApp.PostResolver.update/2 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 input_object : update_post_params do field : title , non_null ( : string ) field : body , non_null ( : string ) field : user_id , non_null ( : integer ) end mutation do field : create_post , type : : post do arg : title , non_null ( : string ) arg : body , non_null ( : string ) arg : user_id , non_null ( : integer ) resolve & MyApp . PostResolver . create / 2 end field : update_post , type : : post do arg : id , non_null ( : integer ) arg : post , : update_post_params resolve & MyApp . PostResolver . update / 2 end end

Our :update_post mutation requires two arguments: a post :id and data to update a post in the form of the :post argument. Let’s now add the MyApp.PostResolver.update/2 function:

web/resolvers/post_resolver.ex def update(%{id: id, post: post_params}, _info) do Repo.get!(Post, id) |> Post.changeset(post_params) |> Repo.update end 1 2 3 4 5 def update ( % { id : id , post : post_params } , _info ) do Repo . get ! ( Post , id ) | > Post . changeset ( post_params ) | > Repo . update end

If we jump back into GraphiQL we can run the following operation to see this in action:

mutation UpdatePost { update_post(id: 11, post: {title: "My Updated Post Title", body: "Awesome Postsss", user_id: 1}) { id, title, body } } 1 2 3 4 5 6 7 mutation UpdatePost { update_post ( id : 11 , post : { title : "My Updated Post Title" , body : "Awesome Postsss" , user_id : 1 } ) { id , title , body } }

With that we now have CRU! Let’s finish it off by adding delete functionality.

Delete

Our last task is to add the ability to delete posts. We can do this with a delete mutation. Let’s add the following code to web/schema.ex :

web/schema.ex mutation do field :create_post, type: :post do arg :title, non_null(:string) arg :body, non_null(:string) arg :user_id, non_null(:integer) resolve &MyApp.PostResolver.create/2 end field :update_post, type: :post do arg :id, non_null(:integer) arg :post, :update_post_params resolve &MyApp.PostResolver.update/2 end field :delete_post, type: :post do arg :id, non_null(:integer) resolve &MyApp.PostResolver.delete/2 end end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 mutation do field : create_post , type : : post do arg : title , non_null ( : string ) arg : body , non_null ( : string ) arg : user_id , non_null ( : integer ) resolve & MyApp . PostResolver . create / 2 end field : update_post , type : : post do arg : id , non_null ( : integer ) arg : post , : update_post_params resolve & MyApp . PostResolver . update / 2 end field : delete_post , type : : post do arg : id , non_null ( : integer ) resolve & MyApp . PostResolver . delete / 2 end end

Our :delete_post mutation will accept one argument: a post :id. Let’s now add the MyApp.PostResolver.delete/2 function:

web/resolvers/post_resolver.ex def delete(%{id: id}, _info) do post = Repo.get!(Post, id) Repo.delete(post) end 1 2 3 4 def delete ( % { id : id } , _info ) do post = Repo . get ! ( Post , id ) Repo . delete ( post ) end

Let’s test it out. Go back into GraphiQL and run the following operation:

mutation DeletePost { delete_post(id: 11) { id } } 1 2 3 4 5 mutation DeletePost { delete_post ( id : 11 ) { id } }

It works! Now we can delete posts.

Conclusion

In this post we built off of the last post and added CRUD functionality to our API. As you can see, it’s relatively painless to start adding mutations to a GraphQL API in Phoenix with the excellent Absinthe library.

I’ve added the code from this tutorial to the repo and it will be under the branch checkpoint-2. As always, if you have any questions feel free to reach out. I’m happy to help in any way that I can.

You can find the next post in the series here.