Skip to content

Commit

Permalink
feat: update to more fit the format of a guide
Browse files Browse the repository at this point in the history
Signed-off-by: Brent Hoover <[email protected]>
  • Loading branch information
brent-hoover committed Feb 21, 2022
1 parent 1d0e2b2 commit 4701f77
Showing 1 changed file with 52 additions and 48 deletions.
100 changes: 52 additions & 48 deletions guides/create-graphql-query.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
## At a glance

In this guide, we’ll guide you through the steps you need to create a successful query in GraphQL
You're working on a new implementation for Leader of the Pack, an outdoor equipment retailer. Let's say that you don't ship Kayaks to every location because of the logistical challenges of shipping something that large. So you need to add
a query that will show you if a particular product can be shipped to a particular zip code. Once you have that data in the system you need to have a way to make it available to the client. This is usually done by creating a GraphQL query. In this guide, we’ll walk you through the steps you need to create a query in GraphQL and have it recognized by the system.

## Identify which plugin owns the query
## What you need

The complete Reaction Commerce GraphQL API is created by stitching together domain-specific APIs from all of the API plugins. So when adding a new query, the first step is to decide which plugin should own it. This is usually obvious, but not always. You should think about whether any other plugins or services will need to call your query. If the query is fundamental to the system, then it may need to go in the "core" plugin, if no better alternative exists.
You should already have an understanding of what fields/data you wish to share with the client and what GraphQL type each field is. Consult the [GraphQL Reference](https://graphql.org/learn/schema/) for more info on GraphQL types.

## Understand the difference between a plugin query function and a GraphQL query resolver
## Identify which plugin owns the query

See [Resolver mutations and queries vs. plugin mutations and queries in the GraphQL concepts docs](../docs/graphql-concepts.md)
The complete Open Commerce GraphQL API is created by stitching together domain-specific APIs from all the API plugins. So when adding a new query, the first step is to decide which plugin should own it. This is usually obvious, but not always. You should think about whether any other plugins or services will need to call your query.
Typically, if you are adding data your new query will exist in your custom plugin.

## Name the query

Expand All @@ -23,53 +25,53 @@ When choosing a name for the query, there are a few rules to follow:
- If it doesn't already exist, create `schema.graphql` in `schemas` in the plugin.
- Import the GraphQL file into `index.js` and default export it in an array:

```js
import importAsString from "@reactioncommerce/api-utils/importAsString.js";
```js
import importAsString from "@reactioncommerce/api-utils/importAsString.js";

const schema = importAsString("./schema.graphql");
const schema = importAsString("./schema.graphql");

export default [schema];
```
export default [schema];
```

> NOTE: For large plugins, you can split to multiple `.graphql` files and export a multi-item array.
> NOTE: For large plugins, you can split to multiple `.graphql` files and export a multi-item array.
- In the `.graphql` file, add your query within `extend type Query { }`. Add an `extend type Query` section near the top if the file doesn't have it yet.
- If your query returns multiple documents, it should return a Relay-compatible Connection and accept standard connection arguments. This is true of any `fields` on any types you create as well.

Example: `groups(after: ConnectionCursor, before: ConnectionCursor, first: ConnectionLimitInt, last: ConnectionLimitInt, sortOrder: SortOrder = asc, sortBy: GroupSortByField = createdAt): GroupConnection`

- Document your query, the new types, and all fields in those types using string literals. See [Documenting a GraphQL Schema](../guides/developers-guide/core/developing-graphql.md#documenting-a-graphql-schema).
- Document your query, the new types, and all fields in those types using string literals. <!-- TODO: See [Documenting a GraphQL Schema](../guides/developers-guide/core/developing-graphql.md#documenting-a-graphql-schema) -->.
- If not already done, register your schemas in the plugin's `index.js` file:

```js
import schemas from "./schemas";
```js
import schemas from "./schemas";

export default async function register(app) {
await app.registerPlugin({
graphQL: {
schemas
},
// other props
});
}
```
export default async function register(app) {
await app.registerPlugin({
graphQL: {
schemas
},
// other props
});
}
```

## Create the plugin query file

- If it doesn't already exist, create `queries` folder in the plugin, and add an `index.js` file there.
- In `queries`, create a file for the query, e.g. `widgets.js` for the `widgets` query. The file should look something like this:
- In `queries`, create a file for the query, e.g. `isAvailableToShip.js` for the `isAvailableToShip` query. The file should look something like this:

```js
import Logger from "@reactioncommerce/logger";

/**
* @method widgets
* @method isAvailableToShip
* @summary TODO
* @param {Object} context - an object containing the per-request state
* @return {Promise<Object>} TODO
*/
export default async function widgets(context) {
Logger.info("widgets query is not yet implemented");
export default async function isAvailableToShip(context) {
Logger.info("isAvailableToShip query is not yet implemented");
return null;
}
```
Expand All @@ -79,10 +81,10 @@ export default async function widgets(context) {
In `queries/index.js` in the plugin, import your query and add it to the default export object. Example:

```js
import widgets from "./widgets"
import isAvailableToShip from "./isAvailableToShip.js"

export default {
widgets
isAvailableToShip
};
```

Expand All @@ -99,20 +101,20 @@ export default async function register(app) {
}
```

Your plugin query function is now available in the GraphQL context as `context.queries.widgets`.
Your plugin query function is now available in the GraphQL context as `context.queries.isAvailableToShip`.

> NOTE: The queries objects from all plugins are merged, so be sure that another plugin does not have a query with the same name. The last one registered with that name will win, and plugins are generally registered in alphabetical order by plugin name. Tip: You can use this to your advantage if you want to override the query function of a core plugin without modifying core code.
## Add a test file for your query

If your query is in a file named `widgets.js`, your Jest tests should be in a file named `widgets.test.js` in the same folder. Initially you can copy and paste the following test:
If your query is in a file named `isAvailableToShip.js`, your Jest tests should be in a file named `isAvailableToShip.test.js` in the same folder. Initially you can copy and paste the following test:

```js
import mockContext from "/imports/test-utils/helpers/mockContext";
import widgets from "./widgets";
import isAvailableToShip from "./isAvailableToShip.js";

test("expect to return a Promise that resolves to null", async () => {
const result = await widgets(mockContext);
const result = await isAvailableToShip(mockContext);
expect(result).toEqual(null);
});
```
Expand All @@ -121,24 +123,26 @@ This of course should be updated with tests that are appropriate for whatever yo

## Create the GraphQL query resolver file

> Note: All of these elements are required for your query to work and if missing often will not throw an error. If your query is not showing or not returning any data up you probably need to recheck that all of these pieces are here and have the correct name and format.
- If it doesn't already exist, create `resolvers` folder in the plugin, and add an `index.js` file there.
- If it doesn't already exist, create `resolvers/Query` folder in the plugin, and add an `index.js` file there. "Query" must be capitalized.
- In `resolvers/Query`, create a file for the query resolver, e.g. `widgets.js` for the `widgets` query. The file should look something like this initially:
- In `resolvers/Query`, create a file for the query resolver, e.g. `isAvailableToShip.js` for the `isAvailableToShip` query. The file should look something like this initially:

```js
/**
* @name "Query.widgets"
* @name "Query.isAvailableToShip"
* @method
* @memberof MyPlugin/GraphQL
* @summary resolver for the widgets GraphQL query
* @summary resolver for the isAvailableToShip GraphQL query
* @param {Object} parentResult - unused
* @param {Object} args - an object of all arguments that were sent by the client
* @param {Object} context - an object containing the per-request state
* @return {Promise<Object>} TODO
*/
export default async function widgets(parentResult, args, context) {
export default async function isAvailableToShip(parentResult, args, context) {
// TODO: decode incoming IDs here
return context.queries.widgets(context);
return context.queries.isAvailableToShip(context);
}
```

Expand All @@ -152,10 +156,10 @@ Make adjustments to the resolver function so that it reads and passes along the
In `resolvers/Query/index.js` in the plugin, import your query resolver and add it to the default export object. Example:

```js
import widgets from "./widgets"
import isAvailableToShip from "./isAvailableToShip.js"

export default {
widgets
isAvailableToShip
};
```

Expand Down Expand Up @@ -202,17 +206,17 @@ Calling your query with GraphQL should now work.

## Add a test file for your query resolver

If your query resolver is in a file named `widgets.js`, your Jest tests should be in a file named `widgets.test.js` in the same folder. Initially you can copy and paste the following test:
If your query resolver is in a file named `isAvailableToShip.js`, your Jest tests should be in a file named `isAvailableToShip.test.js` in the same folder. Initially you can copy and paste the following test:

```js
import widgets from "./widgets";
import isAvailableToShip from "./isAvailableToShip.js";

test("calls queries.widgets and returns the result", async () => {
test("calls queries.isAvailableToShip and returns the result", async () => {
const mockResponse = "MOCK_RESPONSE";
const mockQuery = jest.fn().mockName("queries.widgets").mockReturnValueOnce(Promise.resolve(mockResponse));
const mockQuery = jest.fn().mockName("queries.isAvailableToShip").mockReturnValueOnce(Promise.resolve(mockResponse));

const result = await widgets(null, { /* TODO */ }, {
queries: { widgets: mockQuery },
const result = await isAvailableToShip(null, { /* TODO */ }, {
queries: { isAvailableToShip: mockQuery },
userId: "123"
});

Expand All @@ -229,8 +233,8 @@ Adjust the query function and the query resolver function until they work as exp

## Update the JSDoc comments

Write/update jsdoc comments for the plugin query function, the query resolver, and any util functions. The resolver function must have `@memberof <PluginName>/GraphQL` in the jsdoc, and the `@name` must be the full GraphQL schema path in quotation marks, e.g., "Query.widgets". (The quotation marks are necessary for the output API documentation to be correct due to the periods.)
Write/update jsdoc comments for the plugin query function, the query resolver, and any util functions. The resolver function must have `@memberof <PluginName>/GraphQL` in the jsdoc, and the `@name` must be the full GraphQL schema path in quotation marks, e.g., "Query.isAvailableToShip". (The quotation marks are necessary for the output API documentation to be correct due to the periods.)

## More resources

[Build an API plugin guide](https://mailchimp.com/developer/open-commerce/guides/build-api-plugin/)
[Build an API plugin guide](https://mailchimp.com/developer/open-commerce/guides/build-api-plugin/)

0 comments on commit 4701f77

Please sign in to comment.