|
|
@@ -2,6 +2,8 @@
|
|
|
title: Introducing GraphQL
|
|
|
---
|
|
|
import Playground from '@site/src/components/Playground';
|
|
|
+import Tabs from '@theme/Tabs';
|
|
|
+import TabItem from '@theme/TabItem';
|
|
|
|
|
|
:::info
|
|
|
This is an introduction to GraphQL for those who are new to it. If you are already familiar with GraphQL, you may choose
|
|
|
@@ -190,6 +192,289 @@ automatic code generation.
|
|
|
It also ensures that only valid queries can be made against the API.
|
|
|
:::
|
|
|
|
|
|
+### Operations
|
|
|
+
|
|
|
+An **operation** is the general name for a GraphQL query or mutation. When you are building your client application, you will
|
|
|
+be defining operations which you can then send to the server.
|
|
|
+
|
|
|
+Here's an example of a query operation based on the schema above:
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+query {
|
|
|
+ customers {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Response" label="Response" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "customers": [
|
|
|
+ {
|
|
|
+ "id": "1",
|
|
|
+ "name": "John Smith",
|
|
|
+ "email": "j.smith@email.com"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": "2",
|
|
|
+ "name": "Jane Doe",
|
|
|
+ "email": "j.doe@email.com"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+Here's an example mutation operation to update the first customer's email:
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Query" label="Query" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation {
|
|
|
+ updateCustomerName(input: {
|
|
|
+ customerId: "1",
|
|
|
+ email: "john.smith@email.com"
|
|
|
+ }) {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Response" label="Response" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "updateCustomerName": {
|
|
|
+ "id": "1",
|
|
|
+ "name": "John Smith",
|
|
|
+ // highlight-next-line
|
|
|
+ "email": "john.smith@email.com"
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+Operations can also have a **name**, which, while not required, is recommended for real applications as it makes debugging easier
|
|
|
+(similar to having named vs anonymous functions in JavaScript), and also allows you to take advantage of code generation tools.
|
|
|
+
|
|
|
+Here's the above query with a name:
|
|
|
+
|
|
|
+```graphql
|
|
|
+// highlight-next-line
|
|
|
+query GetCustomers {
|
|
|
+ customers {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### Variables
|
|
|
+
|
|
|
+Operations can also have **variables**. Variables are used to pass input values into the operation. In the example `updateCustomerName` mutation
|
|
|
+operation above, we are passing an input object specifying the `customerId` and `email`. However, in that example they are hard-coded into the
|
|
|
+operation. In a real application, you would want to pass those values in dynamically.
|
|
|
+
|
|
|
+Here's how we can re-write the above mutation operation to use variables:
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Mutation" label="Mutation" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+// highlight-next-line
|
|
|
+mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
|
|
|
+ // highlight-next-line
|
|
|
+ updateCustomerName(input: $input) {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Variables" label="Variables" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "input": {
|
|
|
+ "customerId": "1",
|
|
|
+ "email": "john.smith@email.com"
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Response" label="Response" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "updateCustomerName": {
|
|
|
+ "id": "1",
|
|
|
+ "name": "John Smith",
|
|
|
+ // highlight-next-line
|
|
|
+ "email": "john.smith@email.com"
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+### Fragments
|
|
|
+
|
|
|
+A **fragment** is a reusable set of fields on an object type. Let's define a fragment for the `Customer` type that
|
|
|
+we can re-use in both the query and the mutation:
|
|
|
+
|
|
|
+```graphql
|
|
|
+fragment CustomerFields on Customer {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+Now we can re-write the query and mutation operations to use the fragment:
|
|
|
+
|
|
|
+```graphql
|
|
|
+query GetCustomers{
|
|
|
+ customers {
|
|
|
+ ...CustomerFields
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
|
|
|
+ updateCustomerName(input: $input) {
|
|
|
+ ...CustomerFields
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+You can think of the syntax as similar to the JavaScript object spread operator (`...`).
|
|
|
+
|
|
|
+### Union types
|
|
|
+
|
|
|
+A **union type** is a special type which can be one of a number of other types. Let's say for example that when attempting to update a customer's
|
|
|
+email address, we want to return an error type if the email address is already in use. We can update our schema to model this as a union type:
|
|
|
+
|
|
|
+```graphql
|
|
|
+type Mutation {
|
|
|
+ updateCustomerName(input: UpdateCustomerNameInput!): UpdateCustomerNameResult!
|
|
|
+}
|
|
|
+
|
|
|
+// highlight-next-line
|
|
|
+union UpdateCustomerNameResult = Customer | EmailAddressInUseError
|
|
|
+
|
|
|
+type EmailAddressInUseError {
|
|
|
+ errorCode: String!
|
|
|
+ message: String!
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+:::info
|
|
|
+In Vendure, we use this pattern for almost all mutations. You can read more about it in the [Error Handling guide](/guides/advanced-topics/error-handling).
|
|
|
+:::
|
|
|
+
|
|
|
+Now, when we perform this mutation, we need alter the way we select the fields in the response, since the response could be one of two types:
|
|
|
+
|
|
|
+<Tabs>
|
|
|
+<TabItem value="Mutation" label="Mutation" default>
|
|
|
+
|
|
|
+```graphql
|
|
|
+mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
|
|
|
+ updateCustomerName(input: $input) {
|
|
|
+ __typename
|
|
|
+ ... on Customer {
|
|
|
+ id
|
|
|
+ name
|
|
|
+ email
|
|
|
+ }
|
|
|
+ ... on EmailAddressInUseError {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Success case" label="Success case" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "updateCustomerName": {
|
|
|
+ "__typename": "Customer",
|
|
|
+ "id": "1",
|
|
|
+ "name": "John Smith",
|
|
|
+ "email": "john.smith@email.com"
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+<TabItem value="Error case" label="Error case" >
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "data": {
|
|
|
+ "updateCustomerName": {
|
|
|
+ "__typename": "EmailAddressInUseError",
|
|
|
+ "errorCode": "EMAIL_ADDRESS_IN_USE",
|
|
|
+ "message": "The email address is already in use"
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+</TabItem>
|
|
|
+</Tabs>
|
|
|
+
|
|
|
+The `__typename` field is a special field available on all types which returns the name of the type. This is useful for
|
|
|
+determining which type was returned in the response in your client application.
|
|
|
+
|
|
|
+:::tip
|
|
|
+The above operation could also be written to use the `CustomerFields` fragment we defined earlier:
|
|
|
+```graphql
|
|
|
+mutation UpdateCustomerName($input: UpdateCustomerNameInput!) {
|
|
|
+ updateCustomerName(input: $input) {
|
|
|
+ // highlight-next-line
|
|
|
+ ...CustomerFields
|
|
|
+ ... on EmailAddressInUseError {
|
|
|
+ errorCode
|
|
|
+ message
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+:::
|
|
|
+
|
|
|
### Resolvers
|
|
|
|
|
|
The schema defines the _shape_ of the data, but it does not define _how_ the data is fetched. This is the job of the resolvers.
|
|
|
@@ -200,11 +485,6 @@ would be resolved by a function which fetches the list of customers from the dat
|
|
|
To get started with Vendure's APIs, you don't need to know much about resolvers beyond this basic understanding. However,
|
|
|
later on you may want to write your own custom resolvers to extend the API. This is covered in the [Extending the GraphQL API guide](/guides/how-to/extend-graphql-api/).
|
|
|
|
|
|
-:::info
|
|
|
-This is just a very brief overview. For a complete introduction to all parts of the GraphQL type system, see [Schemas & Types](https://graphql.org/learn/schema/)
|
|
|
-section of the official documentation.
|
|
|
-:::
|
|
|
-
|
|
|
## Querying data
|
|
|
|
|
|
Now that we have a basic understanding of the GraphQL type system, let's look at how we can use it to query data from the Vendure API.
|
|
|
@@ -247,5 +527,13 @@ which fields of that object type we want to fetch. For example:
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+## Further reading
|
|
|
+
|
|
|
+This is just a very brief overview, intended to introduce you to the main concepts you'll need to build with Vendure.
|
|
|
+There are many more language features and best practices to learn about which we did not cover here.
|
|
|
|
|
|
+Here are some resources you can use to gain a deeper understanding of GraphQL:
|
|
|
|
|
|
+- The official [Introduction to GraphQL on graphql.org](https://graphql.org/learn/) is comprehensive and easy to follow.
|
|
|
+- For a really fundamental understanding, see the [GraphQL language spec](https://spec.graphql.org/).
|
|
|
+- If you like to learn from videos, the [graphql.wtf](https://graphql.wtf/) series is a great resource.
|