Skip to content

Commit dd2ea4d

Browse files
authored
Add cloud code resolver example (#771)
* Add cloud code resolver example * Move cloud resolver code to customisation section * Add custom schema setup guide * Modify code example to use graphql-relay instead of Buffer * Improve custom schema and cloud code resolver docs
1 parent 0d494e4 commit dd2ea4d

File tree

3 files changed

+211
-1
lines changed

3 files changed

+211
-1
lines changed

_includes/graphql/customisation.md

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,148 @@ You can optionally override the default generated mutation names with aliases:
287287
]
288288
}
289289
```
290+
291+
## Cloud Code Resolvers
292+
293+
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.
294+
295+
Cloud Code functions can then be used as custom resolvers for your user-defined schema.
296+
297+
### Query Resolvers
298+
299+
Here's an example of a custom query and its related cloud code function resolver in action:
300+
301+
```graphql
302+
# schema.graphql
303+
extend type Query {
304+
hello: String! @resolve
305+
}
306+
```
307+
308+
```js
309+
// main.js
310+
Parse.Cloud.define("hello", () => "Hello, world!");
311+
```
312+
313+
```js
314+
// Header
315+
{
316+
"X-Parse-Application-Id": "APPLICATION_ID",
317+
"X-Parse-Master-Key": "MASTER_KEY" // (optional)
318+
}
319+
```
320+
321+
```graphql
322+
query hello {
323+
hello
324+
}
325+
```
326+
327+
The code above should resolve to this:
328+
329+
```js
330+
// Response
331+
{
332+
"data": {
333+
"hello": "Hello, world!"
334+
}
335+
}
336+
```
337+
338+
### Mutation Resolvers
339+
340+
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.
341+
342+
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.
343+
344+
```graphql
345+
# schema.graphql
346+
extend type Mutation {
347+
addToCart(id: ID!): CartItem! @resolve
348+
}
349+
```
350+
351+
**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.
352+
353+
Decoding and encoding `Relay Node Ids` in Cloud Code is needed in order to smoothly interface with your client-side GraphQL queries and mutations.
354+
355+
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:
356+
357+
```sh
358+
$ npm install graphql-relay --save
359+
```
360+
361+
Then, create your `main.js` cloud code file, import `graphql-relay`, and build your `addToCart` function:
362+
363+
```js
364+
// main.js
365+
const { fromGlobalId, toGlobalId } = require('graphql-relay');
366+
367+
Parse.Cloud.define("addToCart", async (req) => {
368+
const { user, params: { id } } = req;
369+
370+
// Decode the incoming Relay Node Id to a
371+
// Parse objectId for Cloud Code use.
372+
const { id: itemObjectId } = fromGlobalId(id);
373+
374+
// Query the user's current cart.
375+
const itemQuery = new Parse.Query("Item");
376+
const item = await itemQuery.get(itemObjectId);
377+
const cartItemQuery = new Parse.Query("CartItem");
378+
cartItemQuery.equalTo("item", item);
379+
cartItemQuery.equalTo("user", user);
380+
const [existingCartItem] = await cartItemQuery.find();
381+
let savedCartItem;
382+
383+
if (existingCartItem) {
384+
// The item is found in the user's cart; increment its quantity.
385+
const quantity = await existingCartItem.get("quantity");
386+
existingCartItem.set("quantity", quantity + 1);
387+
savedCartItem = await existingCartItem.save();
388+
} else {
389+
// The item has not yet been added; create a new cartItem object.
390+
const CartItem = Parse.Object.extend("CartItem");
391+
const cartItem = new CartItem();
392+
savedCartItem = await cartItem.save({ quantity: 1, item, user });
393+
}
394+
395+
// Encode the Parse objectId to a Relay Node Id
396+
// for Parse GraphQL use.
397+
const cartItemId = toGlobalId('CartItem', savedCartItem.id);
398+
399+
// Convert to a JSON object to handle adding the
400+
// Relay Node Id property.
401+
return { ...savedCartItem.toJSON(), id: cartItemId };
402+
});
403+
```
404+
405+
```js
406+
// Header
407+
{
408+
"X-Parse-Application-Id": "APPLICATION_ID",
409+
"X-Parse-Session-Token": "r:b0dfad1eeafa4425d9508f1c0a15c3fa"
410+
}
411+
```
412+
413+
```graphql
414+
mutation addItemToCart {
415+
addToCart(id: "SXRlbTpEbDVjZmFWclRI") {
416+
id
417+
quantity
418+
}
419+
}
420+
```
421+
422+
The code above should resolve to something similar to this:
423+
424+
```js
425+
// Response
426+
{
427+
"data": {
428+
"addToCart": {
429+
"id": "Q2FydEl0ZW06akVVTHlGZnVpQw==",
430+
"quantity": 1
431+
}
432+
}
433+
}
434+
```

_includes/graphql/getting-started.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,71 @@ After starting the app, you can visit [http://localhost:1337/playground](http://
9393

9494
⚠️ 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).
9595

96+
## Adding Custom Schema
97+
98+
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.
99+
100+
First, add a utility for parsing GraphQL queries as a required dependency:
101+
102+
```sh
103+
$ npm install graphql-tag --save
104+
```
105+
106+
Then, modify your `index.js` file to include your custom schema, along with the path to your cloud code file:
107+
108+
```js
109+
const gql = require('graphql-tag');
110+
111+
const parseServer = new ParseServer({
112+
appId: 'APPLICATION_ID',
113+
cloud: './cloud/main.js',
114+
});
115+
116+
const parseGraphQLServer = new ParseGraphQLServer(
117+
parseServer,
118+
{
119+
graphQLPath: '/graphql',
120+
playgroundPath: '/playground',
121+
graphQLCustomTypeDefs: gql`
122+
extend type Query {
123+
hello: String! @resolve
124+
hello2: String! @resolve(to: "hello")
125+
}
126+
`,
127+
}
128+
);
129+
```
130+
131+
Alternatively, you can create your custom schema in a dedicated `schema.graphql` file and reference the file in your `index.js`:
132+
133+
```js
134+
const gql = require('graphql-tag');
135+
const fs = require('fs');
136+
const customSchema = fs.readFileSync('./cloud/schema.graphql');
137+
138+
const parseServer = new ParseServer({
139+
appId: 'APPLICATION_ID',
140+
cloud: './cloud/main.js',
141+
});
142+
143+
const parseGraphQLServer = new ParseGraphQLServer(
144+
parseServer,
145+
{
146+
graphQLPath: '/graphql',
147+
playgroundPath: '/playground',
148+
graphQLCustomTypeDefs: gql`${customSchema}`,
149+
}
150+
);
151+
```
152+
153+
```graphql
154+
# schema.graphql
155+
extend type Query {
156+
hello: String! @resolve
157+
hello2: String! @resolve(to: "hello")
158+
}
159+
```
160+
96161
## Running Parse Dashboard
97162

98163
[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.

_includes/graphql/objects.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ mutation createAGameScore {
7070
}
7171
```
7272

73-
**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`.
73+
**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`.
7474

7575
## Update
7676

0 commit comments

Comments
 (0)