You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
While it makes an excellent longer-term goal, GraphQL doesn’t need to stand above the entire data tree in an organization in order to provide value and expending large amounts of engineering effort to switch an entire stack should generally be avoided.
11
11
12
12
Unlike some technologies, which are difficult to embrace without getting buy-in from all the teams in an organization, GraphQL can be utilized by smaller units (e.g. component by component, team by team) and later integrated into the bigger picture.
13
13
14
14
Thanks to GraphQL’s ability to fetch data from any data source, it makes sense to keep existing APIs as they are and use GraphQL to “wrap” the data. This can be appreciated by organizations with existing collections of REST (or similar) APIs which power their user experiences since they can lean heavily on their existing investments. This allows small teams to immediately realize many benefits of GraphQL without waiting for deeper adoption and allows existing versions of the application, which have already been deployed and rely on the existing API, to keep functioning uninterrupted.
15
15
16
-
## Schema scope and ownership
16
+
<h2id="schema">Schema scope and ownership</h2>
17
17
18
18
APIs change as the products which use them evolve and their ideal structure is usually defined by the front-end applications which consume them. While it’s certainly possible, and sometimes desirable, to have a single GraphQL schema which blankets an entire organization’s operational concerns, trying to manage a large schema presents challenges which are avoided by separating the schema into more manageable pieces that align with individual products.
19
19
@@ -23,7 +23,7 @@ Having product teams own the GraphQL schema for their products allows the schema
23
23
24
24
Organizations looking to offer a single API endpoint can assemble the individual product schemas, managed by individual product teams into a monolithic API by “stitching” the various schemas together.
An API implemented using GraphQL allows developers to query the exact information they desire from an API and nothing more. With proper visibility into how the API performs, developers can understand the implications of adding or removing fields, especially those which might be slow (notoriously or otherwise!).
29
29
@@ -35,7 +35,7 @@ In addition to empowering front-end developers to make quick, educated decisions
35
35
36
36
This clarity allows pushing schema changes to production with the confidence of knowing that the API is performing as well as it was before, if not better!
37
37
38
-
## Write the server in JavaScript
38
+
<h2id="javascript">Write the server in JavaScript</h2>
39
39
40
40
Facebook's reference implementation has been written in JavaScript since its original release and fresh developments in the GraphQL ecosystem have frequently appeared first in JavaScript or languages which transpile to JavaScript, like TypeScript.
description: Our recommendations for architecting your dream GraphQL API
4
4
---
5
5
6
-
GraphQL schemas are at their best when they are designed around the needs of client applications. When a team is building their first GraphQL schema, they might be tempted to create literal mappings on top of existing database collections or tables using CRUD-like root fields but it’s important to consider how this could be disadvantageous.
6
+
Proper schema design is important during GraphQL adoption. While GraphQL makes it easier to evolve an API, large applications will find it more difficult to allocate time for refactoring. Therefore, schema design decisions should be made carefully to avoid accumulating technical debt.
7
7
8
-
While this literal database-to-schema mapping may be a fast way to get up and running, we strongly suggest avoiding it and instead building the schema around based on how the GraphQL API will be used by the front-end.
8
+
This article details some practices around schema design which should help avoid expensive refactoring in the future.
9
9
10
10
<h2id="style">Style conventions</h2>
11
11
@@ -17,17 +17,38 @@ The GraphQL specification is flexible in the style that it dictates and doesn't
17
17
18
18
<h2id="gql">Wrapping documents with gql</h2>
19
19
20
-
There are two common ways to write GraphQL schemas and queries. The first is to write queries into a `.graphql` file and import them into your other files for usage. The second is to write them wrapped them with the `gql` tag provided by the `graphql-tag` library.
20
+
There are two common ways to write GraphQL schemas and queries. The first is to write queries into a `.graphql` file and import them into your other files for usage. The second is to write them wrapped them with the `gql` tag provided by the [graphql-tag library](https://github.com/apollographql/graphql-tag#graphql-tag).
21
21
22
22
We recommend doing the latter for a couple reasons. Most notably, it can save a build step. Using `.graphql` files requires a loader to parse the file and make it useful. This may not seem like a concern on the client, but it may be especially useful on the server, where there’s often not a build step.
23
23
24
24
Additionally, using the `gql` tag unlocks full syntax highlighting in most editors and auto-formatting support with [prettier](https://prettier.io/).
25
25
26
-
Here’s an example of how to use the `gql` tag to wrap your schemas
26
+
<h2id="structure">Structure</h2>
27
+
28
+
GraphQL schemas are at their best when they are designed around the needs of client applications. When a team is building their first GraphQL schema, they might be tempted to create literal mappings on top of existing database collections or tables using CRUD-like root fields but it’s important to consider how this could be disadvantageous.
29
+
30
+
While this literal database-to-schema mapping may be a fast way to get up and running, we strongly suggest avoiding it and instead building the schema around based on how the GraphQL API will be used by the front-end.
31
+
32
+
If a database has has fields or relationships that the client won’t need, don’t include them in the schema. Adding fields later is cheap, so additions to a schema should be made when the need arises. Likewise, if a connection between two types of data doesn’t currently exist in a database, that doesn’t mean it can’t be added later.
27
33
28
-
<!-- TODO: can insert the glitch apollo-launchpad example here? -->
34
+
For example, if you have a REST endpoint exposing a list of events and their locations, but not weather information for the day of the event, that doesn’t mean you can’t design a schema like the following:
35
+
36
+
```graphql
37
+
typeEvent {
38
+
name: String
39
+
date: String
40
+
weather: WeatherInfo
41
+
}
42
+
43
+
typeWeatherInfo {
44
+
temperature: Float
45
+
description: String
46
+
}
47
+
```
29
48
30
-
<h2id="interfaces">Using interfaces</h2>
49
+
Inscenarioslikethis, youwouldjustneedtofetchtheweatherinformationfromanotherendpoint (or a 3rd party endpoint) inyourresolvers.
50
+
51
+
<h2id="interfaces">Utilizinginterfaces</h2>
31
52
32
53
InterfacesareapowerfulwaytobuildanduseGraphQLschemasthroughtheuseof_abstracttypes_. Abstracttypescan'tbeuseddirectlyinschema, but can be used as building blocks for creating explicit types.
33
54
@@ -79,29 +100,27 @@ Furthermore, if we need to return fields which are only provided by either `Text
79
100
80
101
```graphql
81
102
queryGetBooks {
82
-
schoolBooks {
83
-
title
84
-
...onTextBook {
85
-
classes {
86
-
name
103
+
schoolBooks {
104
+
title
105
+
...onTextBook {
106
+
classes {
107
+
name
108
+
}
87
109
}
88
-
}
89
-
...onColoringBook {
90
-
colors {
91
-
name
110
+
...onColoringBook {
111
+
colors {
112
+
name
113
+
}
92
114
}
93
115
}
94
116
}
95
-
}
96
117
```
97
118
98
-
To see an interface in practice, check out this [example]()
99
-
100
-
<h2id="mutation-design">Designing mutations</h2>
119
+
<h2id="mutations">Designing mutations</h2>
101
120
102
121
Mutations are one of the core types in GraphQL. Just like you can make a query to fetch information, you can make a mutation to update or change information. Unlike REST, GraphQL mutations actually are executed in two parts: the mutation itself, and a subsequent query, which can return any kind of data that you normally could query for. A mutation definition for updating the age of a `User` could look like this:
@@ -139,35 +158,61 @@ With a response looking something like:
139
158
}
140
159
```
141
160
142
-
The first thing to note is that it’s most common to return the thing you’re updating from a mutation. In our example, we were updating a `User` record, so we returned that updated user. There’s nothing _enforcing_ this practice, but it’s highly recommended, because it’s often needed to have an updated user to let the clients update any local cache with a new instance of that `User`. For this same reason, it’s useful design mutations to update only one entity. If you wanted to update two unrelated entities, it’s recommended to in separate mutations.
161
+
The first thing to note is that it’s most common to return the thing you’re updating from a mutation. In our example, we were updating a `User` record, so we returned that updated user. There’s nothing _enforcing_ this practice, but it’s highly recommended, because it’s often needed to have an updated user to let the clients update any local cache with a new instance of that `User`. For this same reason, it’s useful design mutations to update only one entity. If you wanted to update two unrelated entities, it’s recommended to in separate mutations. For more details on how to handle errors and warnings in mutations, see the [mutation responses](#mutation-responses) section below.
143
162
144
-
But what if we wanted to update more than just a single or couple attributes on a user? Passing each thing we need as a single argument would get tedious. For this, we can use Input types.
163
+
But what if we wanted to update more than just a single or couple attributes on a user? Passing each thing we need as a single argument would get tedious. Especially if multiple mutations used similar fields. For this, we can use input types, which are explained in the next section.
145
164
146
-
<h3id="input">Input types</h3>
165
+
<h3id="mutation-input-types">Input types</h3>
147
166
148
-
Input types are a special type in GraphQL reserved for using as arguments to queries and (more commonly) mutations. You can think of them as being similar to object types for your arguments, on top of the other Scalars. Input types can
167
+
Input types are a special type in GraphQL which are defined as arguments to queries and, more commonly, mutations. They can be thought of as object types for arguments, in addition to the other scalar types. Input types are especially useful when multiple mutations require similar information; for example, when creating a user and updating a user require the same fields, like `age` and `name`.
149
168
169
+
Input types are used like any other type and defining them is similar to a typical object type definitions, but with the `input` keyword rather than `type`.
150
170
151
-
1) unlike in REST, mutations can return values
171
+
Here is an example of two mutations that operate on a `User`, _without_ using input types:
152
172
153
-
2) we recommend always returning the item you mutate in order to automatically update the Apollo Client cache (show what this would look like on the front end by requesting an id and the property you mutated)
173
+
```
174
+
type Mutation {
175
+
createUser(name: String, age: Int, address: String, phone: String): User
To avoid the repetition of argument fields, this can be refactored to use an input type, as follows:
181
+
182
+
```
183
+
type Mutation {
184
+
createUser(user: UserInput): User
185
+
updateUser(id: ID!, user: UserInput): User
186
+
}
187
+
188
+
input UserInput {
189
+
name: String
190
+
age: Int
191
+
address: String
192
+
phone: String
193
+
}
194
+
```
154
195
155
-
<h3id="responses">Mutation responses</h3>
196
+
<h3id="mutation-responses">Responses</h3>
156
197
157
-
When making a mutation, many things could go wrong. A common way for handling errors when making a mutation is to simply `throw` an error. While that's fine sometimes, other times it may be useful to get a partial response from a mutation. For example, if you're trying to update a user's `name` and `age` and you made the following mutation:
198
+
Mutations have a higher chance of causing errors than queries since they are modifying data. A common way to handle errors during a mutation is to simply `throw` an error. While that's fine, throwing an error in the resolver will return an error to the caller and prevent a partial response, which could be useful in the event of a partial update. Consider the following mutation example, which tries to update a user's `name` and `age`:
This would likely cause an error, since the age was a negative value. Here, you could throw an error in the resolver, but what if you want to still update the name and return the `user`. If you simply threw an error in the resolver, you couldn't get that partial response back, and there wouldn't be an easy way to tell the client an error occurred with part of the mutation.
209
+
With validation in place, this mutation might cause an error since the `age` is a negative value. While it’s possible that the entire operation should be stopped, there’s an opportunity to partially update the user’s record with the new `name` and return the updated record with the `age` left untouched.
210
+
211
+
Luckily, the powerful structure of GraphQL mutations accommodates this use case and can return transactional information about the update alongside the records which have been changed which enables client-side updates to occur automatically.
169
212
170
-
Mutations are an incredibly powerful part of GraphQL as they can easily return both information about the data updating transaction, as well as the actual data that has changed very easily. One pattern that we recommend to make this consistent is to have a `MutationResponse` interface that can be easily implemented for any `Mutation` fields. The `MutationResponse` is designed to allow transactional information alongside returning valuable data to make client side updates automatic! The interface looks like this:
213
+
In order to provide consistency across a schema, we suggest introducing a `MutationResponse` interface which can be implemented on every mutation response in a schema and enables transactional information to be returned in addition to the normal mutation response object.
214
+
215
+
A `MutationResponse` interface would look like this:
*`code` is a string representing a transactional value explaining details about the status of the data change. Think of this like an HTTP status code.
258
+
*`success` is a boolean indicating whether the update was successful or not. This allows a coarse check by the client to know if there were failures.
259
+
*`message` is a string that is meant to be a human-readable description of the status of the transaction. It is intended to be used in the UI of the product.
260
+
*`post` is added by the implementing type `AddPostMutationResponse` to return back the newly created post for the client to use!
Following this pattern for mutations provides detailed information about the data that has changed and feedback on whether the operation was successful or not. Armed with this information, developers can easily react to failures in the client and fetch the information they need to update their local cache.
0 commit comments