Browse Source

docs: More docs on graphql

Michael Bromley 2 years ago
parent
commit
dd5c5a289e

+ 293 - 5
docs/docs/guides/getting-started/graphql-intro/index.mdx

@@ -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.

+ 13 - 4
docs/docs/guides/getting-started/try-the-api/index.mdx

@@ -165,13 +165,18 @@ For more information about `ErrorResult` and the handling of errors in Vendure,
 The Admin API exposes all the functionality required to manage the store. It is used by the Admin UI, but can also be used
 by integrations and custom scripts.
 
+:::note
+The examples in this section are not interactive, due to security settings on our demo server,
+but you can paste them into your local GraphQL playground.
+:::
+
 Open the GraphQL Playground at [http://localhost:3000/admin-api](http://localhost:3000/admin-api).
 
 ### Logging in
 
 Most Admin API operations are restricted to authenticated users. So first of all we'll need to log in.
 
-<Playground api="admin" server="demo" document={`
+```graphql title="Admin API"
 mutation {
   login(username: "superadmin", password: "superadmin") {
     ...on CurrentUser {
@@ -184,34 +189,38 @@ mutation {
     }
   }
 }
-`} />
+```
 
 ### Fetch a product
 
 The Admin API exposes a lot more information about products than you can get from the Shop API:
 
-<Playground api="admin" server="demo" document={`
+```graphql title="Admin API"
 query {
   product(id: 42) {
+    // highlight-next-line
     enabled
     name
     variants {
       id
       name
+      // highlight-next-line
       enabled
       prices {
         currencyCode
         price
       }
+      // highlight-start
       stockLevels {
         stockLocationId
         stockOnHand
         stockAllocated
       }
+      // highlight-end
     }
   }
 }
-`} />
+```
 
 :::info
 GraphQL is statically typed and uses a **schema** containing information about all the available queries, mutations and types. In the

+ 1 - 0
docs/src/components/Playground/index.tsx

@@ -8,6 +8,7 @@ export default function Playground(props: {
     const urlEncoded = encodeURIComponent(props.document.trim());
     return (
         <iframe
+            loading="lazy"
             style={{
                 width: '100%',
                 minHeight: '500px',