内容简介:One characteristic of a complete API is its ability to allow clients to modify the server-side data. This can be in the form of creating new entries, updating or deleting existing ones, etc. Building a GraphQL server couldn’t be any different.To describe t
One characteristic of a complete API is its ability to allow clients to modify the server-side data. This can be in the form of creating new entries, updating or deleting existing ones, etc. Building a GraphQL server couldn’t be any different.
To describe this kind of features, GraphQL uses the term Mutations
. Mutations are just like normal queries. The only difference is that they make it explicit that they will result in some kind of side-effect on the server-side data.
And that’s where this post will focus on. I am going to describe how to add the mutations to create, edit, and delete an entry on a GraphQL server built with Vapor.
Prerequisites
The code in this post is a continuation of the code from my two latest posts. In the first one, I described how to setup a GraphQL server with Vapor and in the second I focused on how to use custom types on this GraphQL server .
If you want to go through the code as you read the post, the code from the previous posts as well as the code from this post is available on GitHub .
Now, let’s get started!
Implementation
Prepare the models
First, and before we jump into the mutations, we need to adjust our existing models to support the mutations.
For example, when it comes to the edit mutation, we will have to update some properties of the Post
model from constant to variable properties.
struct Post: Codable { let id: CustomUUID - let title: String + var title: String let publishedAt: Date - let tags: [Tag] + var tags: [Tag] let author: Author }
Similarly, in order to be able to filter the posts by the id
property, we will have to make CustomUUID
conform to the Equatable
protocol.
-struct CustomUUID: Codable { +struct CustomUUID: Codable, Equatable {
With our models ready, we can now move on and start adding the logic for the mutations!
Delete mutation
Let’s start with the logic for the deletePost
mutation. For this mutation, we will expect the id
of the Post
to delete as an argument. Then, using this id
, we will search for the Post
in the in-memory list. If we manage to find the post, we are going to remove it and return true
to the client. Otherwise, we will return false
to let the client know that we didn’t manage to find the Post
.
For the sake of this post, I am using a Bool
as a return type. Ideally, we should return an error Type, but to keep this post focused on the mutations, I decided to go with the Bool
.
So, let’s add an extension to the PostController
with this logic.
extension PostController { struct DeletePostArguments: Codable { let id: CustomUUID } func deletePost(request: Request, arguments: DeletePostArguments) -> Bool { let postIndex = posts.firstIndex{ $0.id == arguments.id } guard let index = postIndex?.indexValue else { return false } posts.remove(at: index) return true } }
Similarly, let’s add the logic for the editPost
mutation!
Edit mutation
The edit mutation will allow the user to change the value of the title and the tags for a given post. As a result, this time, we are going to require three arguments; an id
, which we will use to find the Post
to edit, as well as a title
and a tags
argument, which will contain the updated values for the title
and tags
properties respectively.
To keep the logic visually separated, let’s add a new extension to PostController
for the logic related to the edit mutation. This extension will contain a new structure named EditPostArguments
and a function editPost
which will be responsible for editing a post.
The implementation will look like the following snippet:
extension PostController { struct EditPostArguments: Codable { let id: CustomUUID let title: String let tags: [Tag] } func editPost(request: Request, arguments: EditPostArguments) -> Post? { let postIndex = posts.firstIndex { $0.id == arguments.id } guard let index = postIndex?.indexValue else { return nil } posts[index].title = arguments.title posts[index].tags = arguments.tags return posts[index] } }
The EditPostArguments
structure has the three properties that we mentioned before. Then, the editPost
function will accept those arguments as a parameter and will use the id
to search for the Post
. If we can’t find it, we will return nil
to the client. If we manage to find it, we will update the properties of the Post
with the values on the arguments and return the updated post to the client.
Last but not least, let’s see how we can add the functionality to create a new post entity!
Create mutation
To create a new Post
we would need a title, a set of tags, and the id of the author.
It’s time to introduce a new GraphQL concept, the inputs. GraphQL distinguishes the types that can be used as input from those that can be used as outputs to queries. For example, ina previous post, I have used the type Author
to return the author’s data. This is a typical example of an Output type. This kind of types, though, cannot be used when we want to pass arguments, be it in a query or a mutation. In order to define complex types for arguments, there is the concept of Input
. Input
is just like a type, with the only difference being the purpose of use and that it can only include scalar, enums, strings, int, float, bool, and other input types. We can not use an Output
type as a field on an Input
type.
So, let’s create a PostInput
type and add the properties that we need to create a new Post
.
struct PostInput: Codable { let title: String let tags: [Tag] let authorId: CustomUUID }
Now, we can define the function to create a new Post
on an extension of the PostController
.
extension PostController { struct CreatePostArguments: Codable { let input: PostInput } func createPost(request: Request, arguments: CreatePostArguments) -> Post? { guard author.id == arguments.input.authorId else { return nil } let post = Post( id: CustomUUID(value: UUID()), title: arguments.input.title, publishedAt: Date(), tags: arguments.input.tags, author: author ) posts.append(post) return post } }
In the same way as for the other functions, we define a structure for the type of the arguments. This structure contains a sole field of the type PostInput
that we defined earlier. Then the function createPost
takes an instance of this CreatePostArguments
structure and uses it to create a new Post
entity, which we later append to the list of existing posts.
And that’s about it for the logic part of our mutations. Now, it’s time to integrate them into the GraphQL server.
GraphQL
To make those functions and arguments available on the GraphQL schema, we will have to update the FieldKeyProvider
extension of the PostController
and add the keys for them. We will use those keys to map the function and the arguments of PostController
to the mutations and the arguments of the GraphQL schema.
enum FieldKeys: String { + case id + case title + case tags + case input + case posts + case deletePost + case editPost + case createPost }
We will also provide conformance to the FieldKeyProvider
protocol and define keys for the PostInput
structure.
extension PostInput: FieldKeyProvider { typealias FieldKey = FieldKeys enum FieldKeys : String { case title case tags case authorId } }
Lastly, we will update the GraphQL schema definition on Schema.swift
by adding the definition for the PostInput
input type and the definitions for the mutations using the keys we defined on the FieldKeyProvider
extensions.
Query([ Field(.posts, at: PostController.fetchPosts), ]), + + Input(PostInput.self, [ + InputField(.title, at: \.title), + InputField(.tags, at: \.tags), + InputField(.authorId, at: \.authorId) + ]), + + Mutation([ + Field(.deletePost, at: PostController.deletePost) + .argument(.id, at: \.id), + + Field(.editPost, at: PostController.editPost) + .argument(.id, at: \.id) + .argument(.title, at: \.title) + .argument(.tags, at: \.tags), + + Field(.createPost, at: PostController.createPost) + .argument(.input, at: \.input) + ]) + ])
And that’s all folks! We can now build and run the vapor server!
How to test?
To verify what we have done so far, we are going to use a tool named GraphQL Playground .
The installation process is quite simple, you just have to run brew cask install graphql-playground
. Then, you can use the Spotlight Search ( ⌘
+ Space bar
) and open the application GraphQL Playground
.
Once GraphQL Playground is running, we could run the following query to fetch the available posts:
query AllPosts { posts { id author { id } } }
From the response, we are going to keep the id of the author and we will use it to create a new post with the following query (replace 00000000-0000-0000-0000-000000000000
with the UUID from the response):
mutation CreatePost { createPost(input: { authorId: { value: "00000000-0000-0000-0000-000000000000" }, tags: [Swift, Vapor] title: "A new post" }) { id title publishedAt tags author { id } } }
This time, keep the id of the post from the response, and use it on the next query to edit the title and the tags of the post:
mutation EditPost { editPost( id: { value: "00000000-0000-0000-0000-000000000000" }, tags: [Swift, Vapor, GraphQL], title: "A new post with an updated title") { id title publishedAt tags } }
Finally, we can delete the post that we have created using the id of the post from the previous query on the following query:
mutation DeletePost { deletePost(id: { value: "00000000-0000-0000-0000-000000000000" }) }
Conclusion
And that’s about it! In this post, we have seen how to add mutations to create, edit and delete a Post
on a GraphQL server built with Vapor. We have also seen how to take advantage of GraphQL’s Inputs for arguments with complex types and how to use GraphQL Playground
to run our GraphQL queries.
In the next post, I am going to continue this GraphQL & Swift journey and I am going to investigate how to use the mutations that we defined in this post from an iOS app. So stay tuned and follow me on Twitter should you want to get notified once the next post is published or you have a question or comment about this post.
Thanks for reading this post, and see you next time!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Android编程权威指南
[美] Bill Phillips、[美] Brian Hardy / 王明发 / 人民邮电出版社 / 2014-4 / CNY 99.00元
权威、全面、实用、易懂,是本书最大的特色。本书根据美国大名鼎鼎的Big Nerd Ranch训练营的Android培训讲义编写而成,已经为微软、谷歌、Facebook等行业巨头培养了众多专业人才。作者巧妙地把Android开发所需的庞杂知识、行业实践、编程规范等融入一本书中,通过精心编排的应用示例、循序渐进的内容组织,以及循循善诱的语言,深入地讲解了Android开发的方方面面。如果学完一章之后仍......一起来看看 《Android编程权威指南》 这本书的介绍吧!
HTML 编码/解码
HTML 编码/解码
RGB CMYK 转换工具
RGB CMYK 互转工具