Skip to content

Commit

Permalink
Add cloud code resolver example (#771)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
185driver committed Oct 12, 2020
1 parent 0d494e4 commit dd2ea4d
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 1 deletion.
145 changes: 145 additions & 0 deletions _includes/graphql/customisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,148 @@ You can optionally override the default generated mutation names with aliases:
]
}
```

## Cloud Code 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.

Cloud Code functions can then be used as custom resolvers for your user-defined schema.

### Query Resolvers

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 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 smoothly interface with your client-side GraphQL queries and mutations.

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');

Parse.Cloud.define("addToCart", async (req) => {
const { user, params: { id } } = req;

// 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");
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 Node Id
// for Parse GraphQL use.
const cartItemId = toGlobalId('CartItem', savedCartItem.id);

// Convert to a JSON object to handle adding the
// Relay Node Id property.
return { ...savedCartItem.toJSON(), id: cartItemId };
});
```

```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
}
}
}
```
65 changes: 65 additions & 0 deletions _includes/graphql/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. Your custom schema is resolved via [Cloud Code](#cloud-code-resolvers) functions.

First, 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 your `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.
Expand Down
2 changes: 1 addition & 1 deletion _includes/graphql/objects.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit dd2ea4d

Please sign in to comment.