Skip to content

Add cloud code resolver example #771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 12, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 93 additions & 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 Expand Up @@ -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
}
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to show how to pass the schema into the ParseGraphQL Server, here developers can think that a simple schema.graphql at the root of the folder can do the job

Copy link
Contributor Author

@185driver 185driver Oct 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I could use the example found in this PR. It's the code I used in my own project.

I'm hesitant to include the custom schema setup code in this section, as it seems to only be a related topic, and not quite this same one. Would it be okay for me to include the custom schema setup in the Getting Started section instead? If so, it could be inserted in one of a couple places:

  1. In the Using Express.Js sub-section (mixed in with the other ParseServer setup code), or
  2. Given its own sub-section named something like, Adding Custom Schema (I think I would prefer this method)

```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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What you think about ES6 shortcuts

Suggested change
const cartItemJson = savedCartItem.toJSON();
Object.assign(cartItemJson, { id: cartItemId });
return cartItemJson;
// Convert to a JSON object to handle adding the base64 id property.
return {...savedCartItem.toJSON(), id: cartItemId }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice. Thank you.

});
```

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

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid some code like this

  const decoded = Buffer.from(id, "base64").toString();
  const itemObjectId = decoded.split(":")[1];

We need to inform developers that they can use graphql-relay lib.

May be add some reference about the graphql lib graphql-relay and the const { type, id } = fromGlobalId(globalId); method

Link: https://github.com/graphql/graphql-relay-js#object-identification
Lib: https://www.npmjs.com/package/graphql-relay

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I use my Buffer based code in the example, but add a text explanation and reference to the graphql lib as an optional solution, or should I dump the Buffer based code, use the graphql lib for the code example, and then reference the lib in the text?

With that lib available (thanks for mentioning it!) I personally would use it in my own projects, but there's something to be said for using code examples that are a bit more vanilla JS in the documentation too. Your preference?

Decoding and encoding these Relay Global Object Identifications in Cloud Code is needed in order to seamlessly interface with your client-side GraphQL mutations.