From 1075d4e192c1837b3cf66bf16ee1eaa81bc61b38 Mon Sep 17 00:00:00 2001 From: Doug Allrich Date: Tue, 29 Sep 2020 09:41:19 -0500 Subject: [PATCH 1/5] Add cloud code resolver example --- _includes/graphql/objects.md | 94 +++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/_includes/graphql/objects.md b/_includes/graphql/objects.md index 407ac2814..8791f77c8 100644 --- a/_includes/graphql/objects.md +++ b/_includes/graphql/objects.md @@ -70,7 +70,7 @@ mutation createAGameScore { } ``` -**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm), it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. +**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. ## Update @@ -238,3 +238,95 @@ mutation aNestedMutation { } } ``` + +## Cloud Code Resolvers + +If you need more control over how your mutations modify data than what Parse's auto-generated mutations can provide, Cloud Code functions can be used as custom resolvers. + +For example, if you have classes named `Item` and `CartItem` in the schema, you can create an `addToCart` custom mutation that tests whether a specific item is already in the user's cart. If found, the cart item's quantity is incremented by one. If not, a new `CartItem` object is created. + +The ability to branch your resolver logic in this way enables you to replicate functionality found in Parse's auto-generated `createCartItem` and `updateCartItem` mutations and combine those behaviors into a single custom resolver. + +```sh +# schema.graphql +extend type Mutation { + addToCart(id: ID!): CartItem! @resolve +} +``` + +```js +// main.js +Parse.Cloud.define("addToCart", async (req) => { + const { user, params: { id } } = req; + + // Decode the incoming base64 id to an objectId for Cloud Code use. + const decoded = Buffer.from(id, "base64").toString(); + const itemObjectId = decoded.split(":")[1]; + + // Query the user's current cart. + const itemQuery = new Parse.Query("Item"); + const item = await itemQuery.get(itemObjectId); + const cartItemQuery = new Parse.Query("CartItem"); + cartItemQuery.equalTo("item", item); + cartItemQuery.equalTo("user", user); + const [existingCartItem] = await cartItemQuery.find(); + let savedCartItem; + + if (existingCartItem) { + // The item is found in the user's cart; increment its quantity. + const quantity = await existingCartItem.get("quantity"); + existingCartItem.set("quantity", quantity + 1); + savedCartItem = await existingCartItem.save(); + } else { + // The item has not yet been added; create a new cartItem object. + const CartItem = Parse.Object.extend("CartItem"); + const cartItem = new CartItem(); + savedCartItem = await cartItem.save({ quantity: 1, item, user }); + } + + // Encode the Parse objectId to a Relay Global Object Identification + // (a special use-case base64 id string) for Parse GraphQL use. + const concatObjectId = `CartItem:${savedCartItem.id}`; + const cartItemId = Buffer.from(concatObjectId).toString("base64"); + + // Convert to a JSON object to handle adding the base64 id property. + const cartItemJson = savedCartItem.toJSON(); + Object.assign(cartItemJson, { id: cartItemId }); + return cartItemJson; +}); +``` + +```js +// Header +{ + "X-Parse-Application-Id": "APPLICATION_ID", + "X-Parse-Session-Token": "r:b0dfad1eeafa4425d9508f1c0a15c3fa" +} +``` + +```graphql +mutation addItemToCart { + addToCart(id: "SXRlbTpEbDVjZmFWclRI") { + id + quantity + } +} +``` + +The code above should resolve to something similar to this: + +```js +// Response +{ + "data": { + "addToCart": { + "id": "Q2FydEl0ZW06akVVTHlGZnVpQw==", + "quantity": 1 + } + } +} +``` + +**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize this type of `id`, so it must be converted to a Parse `objectId` for use in Cloud Code function queries. + +Decoding and encoding these Relay Global Object Identifications in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL mutations. From a25a6e65e380733b0de6d938dfa248faf8b459a6 Mon Sep 17 00:00:00 2001 From: Doug Allrich Date: Fri, 9 Oct 2020 09:45:48 -0500 Subject: [PATCH 2/5] Move cloud resolver code to customisation section --- _includes/graphql/customisation.md | 110 ++++++++++++++++++++++++++--- _includes/graphql/objects.md | 92 ------------------------ 2 files changed, 101 insertions(+), 101 deletions(-) diff --git a/_includes/graphql/customisation.md b/_includes/graphql/customisation.md index 72825c196..5788430e1 100644 --- a/_includes/graphql/customisation.md +++ b/_includes/graphql/customisation.md @@ -71,7 +71,7 @@ interface ParseGraphQLConfiguration { // By default, all write mutation types are // exposed for all included classes. Use this to disable - // the available mutation types for this class and optionally + // the available mutation types for this class and optionally // override the default generated name with aliases. mutation?: { create?: boolean; @@ -99,7 +99,7 @@ We have provided a public API in `ParseGraphQLServer` which accepts the above JS // ... ParseGraphQLConfiguration }; - await parseGraphQLServer.setGraphQLConfig(config); + await parseGraphQLServer.setGraphQLConfig(config); ``` ### Include or Exclude Classes @@ -207,14 +207,14 @@ By default, the schema exposes a `get` and `find` operation for each class, for "query": { "get": true, "find": false - } + } }, { "className": "Review", "query": { "get": false, "find": true - } + } } ] } @@ -229,13 +229,13 @@ By default, generated query names use pluralized version of `className`. You can "className": "Likes", "query": { "getAlias": "like" - } + } }, { "className": "Data", "query": { "findAlias": "findData" - } + } } ] } @@ -255,7 +255,7 @@ By default, the schema exposes a `create`, `update` and `delete` operation for e "create": true, "update": true, "destroy": true - } + } }, { "className": "Review", @@ -263,7 +263,7 @@ By default, the schema exposes a `create`, `update` and `delete` operation for e "create": true, "update": false, "destroy": true - } + } } ] } @@ -282,8 +282,100 @@ You can optionally override the default generated mutation names with aliases: "createAlias": "newRecord", "updateAlias": "changeRecord", "destroyAlias": "eraseRecord" - } + } } ] } ``` + +## Cloud Code Resolvers + +If you need more control over how your mutations modify data than what Parse's auto-generated mutations can provide, Cloud Code functions can be used as custom resolvers. + +For example, if you have classes named `Item` and `CartItem` in the schema, you can create an `addToCart` custom mutation that tests whether a specific item is already in the user's cart. If found, the cart item's quantity is incremented by one. If not, a new `CartItem` object is created. + +The ability to branch your resolver logic in this way enables you to replicate functionality found in Parse's auto-generated `createCartItem` and `updateCartItem` mutations and combine those behaviors into a single custom resolver. + +```sh +# schema.graphql +extend type Mutation { + addToCart(id: ID!): CartItem! @resolve +} +``` + +```js +// main.js +Parse.Cloud.define("addToCart", async (req) => { + const { user, params: { id } } = req; + + // Decode the incoming base64 id to an objectId for Cloud Code use. + const decoded = Buffer.from(id, "base64").toString(); + const itemObjectId = decoded.split(":")[1]; + + // Query the user's current cart. + const itemQuery = new Parse.Query("Item"); + const item = await itemQuery.get(itemObjectId); + const cartItemQuery = new Parse.Query("CartItem"); + cartItemQuery.equalTo("item", item); + cartItemQuery.equalTo("user", user); + const [existingCartItem] = await cartItemQuery.find(); + let savedCartItem; + + if (existingCartItem) { + // The item is found in the user's cart; increment its quantity. + const quantity = await existingCartItem.get("quantity"); + existingCartItem.set("quantity", quantity + 1); + savedCartItem = await existingCartItem.save(); + } else { + // The item has not yet been added; create a new cartItem object. + const CartItem = Parse.Object.extend("CartItem"); + const cartItem = new CartItem(); + savedCartItem = await cartItem.save({ quantity: 1, item, user }); + } + + // Encode the Parse objectId to a Relay Global Object Identification + // (a special use-case base64 id string) for Parse GraphQL use. + const concatObjectId = `CartItem:${savedCartItem.id}`; + const cartItemId = Buffer.from(concatObjectId).toString("base64"); + + // Convert to a JSON object to handle adding the base64 id property. + const cartItemJson = savedCartItem.toJSON(); + Object.assign(cartItemJson, { id: cartItemId }); + return cartItemJson; +}); +``` + +```js +// Header +{ + "X-Parse-Application-Id": "APPLICATION_ID", + "X-Parse-Session-Token": "r:b0dfad1eeafa4425d9508f1c0a15c3fa" +} +``` + +```graphql +mutation addItemToCart { + addToCart(id: "SXRlbTpEbDVjZmFWclRI") { + id + quantity + } +} +``` + +The code above should resolve to something similar to this: + +```js +// Response +{ + "data": { + "addToCart": { + "id": "Q2FydEl0ZW06akVVTHlGZnVpQw==", + "quantity": 1 + } + } +} +``` + +**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize this type of `id`, so it must be converted to a Parse `objectId` for use in Cloud Code function queries. + +Decoding and encoding these Relay Global Object Identifications in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL mutations. diff --git a/_includes/graphql/objects.md b/_includes/graphql/objects.md index 8791f77c8..d3a38be68 100644 --- a/_includes/graphql/objects.md +++ b/_includes/graphql/objects.md @@ -238,95 +238,3 @@ mutation aNestedMutation { } } ``` - -## Cloud Code Resolvers - -If you need more control over how your mutations modify data than what Parse's auto-generated mutations can provide, Cloud Code functions can be used as custom resolvers. - -For example, if you have classes named `Item` and `CartItem` in the schema, you can create an `addToCart` custom mutation that tests whether a specific item is already in the user's cart. If found, the cart item's quantity is incremented by one. If not, a new `CartItem` object is created. - -The ability to branch your resolver logic in this way enables you to replicate functionality found in Parse's auto-generated `createCartItem` and `updateCartItem` mutations and combine those behaviors into a single custom resolver. - -```sh -# schema.graphql -extend type Mutation { - addToCart(id: ID!): CartItem! @resolve -} -``` - -```js -// main.js -Parse.Cloud.define("addToCart", async (req) => { - const { user, params: { id } } = req; - - // Decode the incoming base64 id to an objectId for Cloud Code use. - const decoded = Buffer.from(id, "base64").toString(); - const itemObjectId = decoded.split(":")[1]; - - // Query the user's current cart. - const itemQuery = new Parse.Query("Item"); - const item = await itemQuery.get(itemObjectId); - const cartItemQuery = new Parse.Query("CartItem"); - cartItemQuery.equalTo("item", item); - cartItemQuery.equalTo("user", user); - const [existingCartItem] = await cartItemQuery.find(); - let savedCartItem; - - if (existingCartItem) { - // The item is found in the user's cart; increment its quantity. - const quantity = await existingCartItem.get("quantity"); - existingCartItem.set("quantity", quantity + 1); - savedCartItem = await existingCartItem.save(); - } else { - // The item has not yet been added; create a new cartItem object. - const CartItem = Parse.Object.extend("CartItem"); - const cartItem = new CartItem(); - savedCartItem = await cartItem.save({ quantity: 1, item, user }); - } - - // Encode the Parse objectId to a Relay Global Object Identification - // (a special use-case base64 id string) for Parse GraphQL use. - const concatObjectId = `CartItem:${savedCartItem.id}`; - const cartItemId = Buffer.from(concatObjectId).toString("base64"); - - // Convert to a JSON object to handle adding the base64 id property. - const cartItemJson = savedCartItem.toJSON(); - Object.assign(cartItemJson, { id: cartItemId }); - return cartItemJson; -}); -``` - -```js -// Header -{ - "X-Parse-Application-Id": "APPLICATION_ID", - "X-Parse-Session-Token": "r:b0dfad1eeafa4425d9508f1c0a15c3fa" -} -``` - -```graphql -mutation addItemToCart { - addToCart(id: "SXRlbTpEbDVjZmFWclRI") { - id - quantity - } -} -``` - -The code above should resolve to something similar to this: - -```js -// Response -{ - "data": { - "addToCart": { - "id": "Q2FydEl0ZW06akVVTHlGZnVpQw==", - "quantity": 1 - } - } -} -``` - -**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize this type of `id`, so it must be converted to a Parse `objectId` for use in Cloud Code function queries. - -Decoding and encoding these Relay Global Object Identifications in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL mutations. From 354c81b1c2c07a2843f9c560341226115b4b50f5 Mon Sep 17 00:00:00 2001 From: Doug Allrich Date: Fri, 9 Oct 2020 20:18:34 -0500 Subject: [PATCH 3/5] Add custom schema setup guide --- _includes/graphql/getting-started.md | 65 ++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/_includes/graphql/getting-started.md b/_includes/graphql/getting-started.md index 405f04f68..a51bbdb2c 100644 --- a/_includes/graphql/getting-started.md +++ b/_includes/graphql/getting-started.md @@ -93,6 +93,71 @@ After starting the app, you can visit [http://localhost:1337/playground](http:// ⚠️ Please do not mount the GraphQL Playground in production as anyone could access your API Playground and read or change your application's data. [Parse Dashboard](#running-parse-dashboard) has a built-in GraphQL Playground and it is the recommended option for production apps. If you want to secure your API in production take a look at [Class Level Permissions](/js/guide/#class-level-permissions). +## Adding Custom Schema + +The Parse GraphQL API supports the use of custom user-defined schema. You can write your own types, queries, and mutations, which will be merged with the ones that are automatically generated. The new types are resolved via [Cloud Code](#cloud-code-resolvers) functions. + +Add a utility for parsing GraphQL queries as a required dependency: + +```sh +$ npm install graphql-tag --save +``` + +Then, modify your `index.js` file to include your custom schema, along with the path to your cloud code file: + +```js +const gql = require('graphql-tag'); + +const parseServer = new ParseServer({ + appId: 'APPLICATION_ID', + cloud: './cloud/main.js', +}); + +const parseGraphQLServer = new ParseGraphQLServer( + parseServer, + { + graphQLPath: '/graphql', + playgroundPath: '/playground', + graphQLCustomTypeDefs: gql` + extend type Query { + hello: String! @resolve + hello2: String! @resolve(to: "hello") + } + `, + } +); +``` + +Alternatively, you can create your custom schema in a dedicated `schema.graphql` file and reference the file in `index.js`: + +```js +const gql = require('graphql-tag'); +const fs = require('fs'); +const customSchema = fs.readFileSync('./cloud/schema.graphql'); + +const parseServer = new ParseServer({ + appId: 'APPLICATION_ID', + cloud: './cloud/main.js', +}); + +const parseGraphQLServer = new ParseGraphQLServer( + parseServer, + { + graphQLPath: '/graphql', + playgroundPath: '/playground', + graphQLCustomTypeDefs: gql`${customSchema}`, + } +); +``` + +```graphql +# schema.graphql +extend type Query { + hello: String! @resolve + hello2: String! @resolve(to: "hello") +} +``` + ## Running Parse Dashboard [Parse Dashboard](https://github.com/parse-community/parse-dashboard) is a standalone dashboard for managing your Parse Server apps, including your objects' schema and data, logs, jobs, CLPs, and push notifications. Parse Dashboard also has a built-in GraphQL Playground that you can use to play around with your auto-generated Parse GraphQL API. It is the recommended option for **production** applications. From 9d9a3cab5085e52a0421fe08f0daaf3141bad1f7 Mon Sep 17 00:00:00 2001 From: Doug Allrich Date: Fri, 9 Oct 2020 21:08:13 -0500 Subject: [PATCH 4/5] Modify code example to use graphql-relay instead of Buffer --- _includes/graphql/customisation.md | 36 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/_includes/graphql/customisation.md b/_includes/graphql/customisation.md index 5788430e1..9d44bc0b0 100644 --- a/_includes/graphql/customisation.md +++ b/_includes/graphql/customisation.md @@ -303,14 +303,26 @@ extend type Mutation { } ``` +**Note**: The `id` passed in to your Cloud Code function from a GraphQL query is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize `Relay Node Ids`, so they must be converted to a Parse `objectId` for use there. + +Decoding and encoding `Relay Node Ids` in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL queries and mutations. + +Install the following utility as a required dependency to enable decoding and encoding `Relay Node Ids`: + +```sh +$ npm install graphql-relay --save +``` + ```js // main.js +const { fromGlobalId, toGlobalId } = require('graphql-relay'); + Parse.Cloud.define("addToCart", async (req) => { const { user, params: { id } } = req; - // Decode the incoming base64 id to an objectId for Cloud Code use. - const decoded = Buffer.from(id, "base64").toString(); - const itemObjectId = decoded.split(":")[1]; + // Decode the incoming Relay Node Id to a + // Parse objectId for Cloud Code use. + const { id: itemObjectId } = fromGlobalId(id); // Query the user's current cart. const itemQuery = new Parse.Query("Item"); @@ -333,15 +345,13 @@ Parse.Cloud.define("addToCart", async (req) => { savedCartItem = await cartItem.save({ quantity: 1, item, user }); } - // Encode the Parse objectId to a Relay Global Object Identification - // (a special use-case base64 id string) for Parse GraphQL use. - const concatObjectId = `CartItem:${savedCartItem.id}`; - const cartItemId = Buffer.from(concatObjectId).toString("base64"); + // Encode the Parse objectId to a Relay Node Id + // for Parse GraphQL use. + const cartItemId = toGlobalId('CartItem', savedCartItem.id); - // Convert to a JSON object to handle adding the base64 id property. - const cartItemJson = savedCartItem.toJSON(); - Object.assign(cartItemJson, { id: cartItemId }); - return cartItemJson; + // Convert to a JSON object to handle adding the + // Relay Node Id property. + return { ...savedCartItem.toJSON(), id: cartItemId }; }); ``` @@ -375,7 +385,3 @@ The code above should resolve to something similar to this: } } ``` - -**Note:** The `id` is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize this type of `id`, so it must be converted to a Parse `objectId` for use in Cloud Code function queries. - -Decoding and encoding these Relay Global Object Identifications in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL mutations. From 8d6a2d89e222f18afcdd03885f957602d9ebadd7 Mon Sep 17 00:00:00 2001 From: Doug Allrich Date: Sat, 10 Oct 2020 21:30:28 -0500 Subject: [PATCH 5/5] Improve custom schema and cloud code resolver docs --- _includes/graphql/customisation.md | 61 ++++++++++++++++++++++++---- _includes/graphql/getting-started.md | 6 +-- 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/_includes/graphql/customisation.md b/_includes/graphql/customisation.md index 9d44bc0b0..0bd756be9 100644 --- a/_includes/graphql/customisation.md +++ b/_includes/graphql/customisation.md @@ -290,29 +290,76 @@ You can optionally override the default generated mutation names with aliases: ## Cloud Code Resolvers -If you need more control over how your mutations modify data than what Parse's auto-generated mutations can provide, Cloud Code functions can be used as custom resolvers. +The Parse GraphQL API supports the use of custom user-defined schema. The [Adding Custom Schema](#adding-custom-schema) section explains how to get started using this feature. -For example, if you have classes named `Item` and `CartItem` in the schema, you can create an `addToCart` custom mutation that tests whether a specific item is already in the user's cart. If found, the cart item's quantity is incremented by one. If not, a new `CartItem` object is created. +Cloud Code functions can then be used as custom resolvers for your user-defined schema. -The ability to branch your resolver logic in this way enables you to replicate functionality found in Parse's auto-generated `createCartItem` and `updateCartItem` mutations and combine those behaviors into a single custom resolver. +### Query Resolvers -```sh +Here's an example of a custom query and its related cloud code function resolver in action: + +```graphql +# schema.graphql +extend type Query { + hello: String! @resolve +} +``` + +```js +// main.js +Parse.Cloud.define("hello", () => "Hello, world!"); +``` + +```js +// Header +{ + "X-Parse-Application-Id": "APPLICATION_ID", + "X-Parse-Master-Key": "MASTER_KEY" // (optional) +} +``` + +```graphql +query hello { + hello +} +``` + +The code above should resolve to this: + +```js +// Response +{ + "data": { + "hello": "Hello, world!" + } +} +``` + +### Mutation Resolvers + +At times, you may need more control over how your mutations modify data than what Parse's auto-generated mutations can provide. For example, if you have classes named `Item` and `CartItem` in the schema, you can create an `addToCart` custom mutation that tests whether a specific item is already in the user's cart. If found, the cart item's quantity is incremented by one. If not, a new `CartItem` object is created. + +The ability to branch your resolver logic enables you to replicate functionality found in Parse's auto-generated `createCartItem` and `updateCartItem` mutations and combine those behaviors into a single custom resolver. + +```graphql # schema.graphql extend type Mutation { addToCart(id: ID!): CartItem! @resolve } ``` -**Note**: The `id` passed in to your Cloud Code function from a GraphQL query is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it's **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize `Relay Node Ids`, so they must be converted to a Parse `objectId` for use there. +**Note**: The `id` passed in to your Cloud Code function from a GraphQL query is a [Relay Global Object Identification](https://facebook.github.io/relay/graphql/objectidentification.htm); it is **not** a Parse `objectId`. Most of the time the `Relay Node Id` is a `Base64` of the `ParseClass` and the `objectId`. Cloud code does not recognize a `Relay Node Id`, so converting it to a Parse `objectId` is required. -Decoding and encoding `Relay Node Ids` in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL queries and mutations. +Decoding and encoding `Relay Node Ids` in Cloud Code is needed in order to smoothly interface with your client-side GraphQL queries and mutations. -Install the following utility as a required dependency to enable decoding and encoding `Relay Node Ids`: +First, install the [Relay Library for GraphQL.js](https://www.npmjs.com/package/graphql-relay) as a required dependency to enable decoding and encoding `Relay Node Ids` in your cloud code functions: ```sh $ npm install graphql-relay --save ``` +Then, create your `main.js` cloud code file, import `graphql-relay`, and build your `addToCart` function: + ```js // main.js const { fromGlobalId, toGlobalId } = require('graphql-relay'); diff --git a/_includes/graphql/getting-started.md b/_includes/graphql/getting-started.md index a51bbdb2c..cce74cc79 100644 --- a/_includes/graphql/getting-started.md +++ b/_includes/graphql/getting-started.md @@ -95,9 +95,9 @@ After starting the app, you can visit [http://localhost:1337/playground](http:// ## Adding Custom Schema -The Parse GraphQL API supports the use of custom user-defined schema. You can write your own types, queries, and mutations, which will be merged with the ones that are automatically generated. The new types are resolved via [Cloud Code](#cloud-code-resolvers) functions. +The Parse GraphQL API supports the use of custom user-defined schema. You can write your own types, queries, and mutations, which will be merged with the ones that are automatically generated. Your custom schema is resolved via [Cloud Code](#cloud-code-resolvers) functions. -Add a utility for parsing GraphQL queries as a required dependency: +First, add a utility for parsing GraphQL queries as a required dependency: ```sh $ npm install graphql-tag --save @@ -128,7 +128,7 @@ const parseGraphQLServer = new ParseGraphQLServer( ); ``` -Alternatively, you can create your custom schema in a dedicated `schema.graphql` file and reference the file in `index.js`: +Alternatively, you can create your custom schema in a dedicated `schema.graphql` file and reference the file in your `index.js`: ```js const gql = require('graphql-tag');