1 | # Relay Library for GraphQL.js
|
2 |
|
3 | This is a library to allow the easy creation of Relay-compliant servers using the [GraphQL.js](https://github.com/graphql/graphql-js) reference implementation of a GraphQL server.
|
4 |
|
5 | [![Build Status](https://github.com/graphql/graphql-relay-js/workflows/CI/badge.svg?branch=main)](https://github.com/graphql/graphql-relay-js/actions?query=branch%3Amain)
|
6 | [![Coverage Status](https://codecov.io/gh/graphql/graphql-relay-js/branch/master/graph/badge.svg)](https://codecov.io/gh/graphql/graphql-relay-js)
|
7 |
|
8 | ## Getting Started
|
9 |
|
10 | A basic understanding of GraphQL and of the GraphQL.js implementation is needed to provide context for this library.
|
11 |
|
12 | An overview of GraphQL in general is available in the [README](https://github.com/graphql/graphql-spec/blob/master/README.md) for the [Specification for GraphQL](https://github.com/graphql/graphql-spec).
|
13 |
|
14 | This library is designed to work with the [GraphQL.js](https://github.com/graphql/graphql-js) reference implementation of a GraphQL server.
|
15 |
|
16 | An overview of the functionality that a Relay-compliant GraphQL server should provide is in the [GraphQL Relay Specification](https://relay.dev/docs/guides/graphql-server-specification/) on the [Relay website](https://relay.dev/). That overview describes a simple set of examples that exist as [tests](src/__tests__) in this repository. A good way to get started with this repository is to walk through that documentation and the corresponding tests in this library together.
|
17 |
|
18 | ## Using Relay Library for GraphQL.js
|
19 |
|
20 | Install Relay Library for GraphQL.js
|
21 |
|
22 | ```sh
|
23 | npm install graphql graphql-relay
|
24 | ```
|
25 |
|
26 | When building a schema for [GraphQL.js](https://github.com/graphql/graphql-js), the provided library functions can be used to simplify the creation of Relay patterns.
|
27 |
|
28 | ### Connections
|
29 |
|
30 | Helper functions are provided for both building the GraphQL types for connections and for implementing the `resolve` method for fields returning those types.
|
31 |
|
32 | - `connectionArgs` returns the arguments that fields should provide when they return a connection type that supports bidirectional pagination.
|
33 | - `forwardConnectionArgs` returns the arguments that fields should provide when they return a connection type that only supports forward pagination.
|
34 | - `backwardConnectionArgs` returns the arguments that fields should provide when they return a connection type that only supports backward pagination.
|
35 | - `connectionDefinitions` returns a `connectionType` and its associated `edgeType`, given a node type.
|
36 | - `connectionFromArray` is a helper method that takes an array and the arguments from `connectionArgs`, does pagination and filtering, and returns an object in the shape expected by a `connectionType`'s `resolve` function.
|
37 | - `connectionFromPromisedArray` is similar to `connectionFromArray`, but it takes a promise that resolves to an array, and returns a promise that resolves to the expected shape by `connectionType`.
|
38 | - `cursorForObjectInConnection` is a helper method that takes an array and a member object, and returns a cursor for use in the mutation payload.
|
39 | - `offsetToCursor` takes the index of a member object in an array and returns an opaque cursor for use in the mutation payload.
|
40 | - `cursorToOffset` takes an opaque cursor (created with `offsetToCursor`) and returns the corresponding array index.
|
41 |
|
42 | An example usage of these methods from the [test schema](src/__tests__/starWarsSchema.ts):
|
43 |
|
44 | ```js
|
45 | var { connectionType: ShipConnection } = connectionDefinitions({
|
46 | nodeType: shipType,
|
47 | });
|
48 | var factionType = new GraphQLObjectType({
|
49 | name: 'Faction',
|
50 | fields: () => ({
|
51 | ships: {
|
52 | type: ShipConnection,
|
53 | args: connectionArgs,
|
54 | resolve: (faction, args) =>
|
55 | connectionFromArray(
|
56 | faction.ships.map((id) => data.Ship[id]),
|
57 | args,
|
58 | ),
|
59 | },
|
60 | }),
|
61 | });
|
62 | ```
|
63 |
|
64 | This shows adding a `ships` field to the `Faction` object that is a connection. It uses `connectionDefinitions({nodeType: shipType})` to create the connection type, adds `connectionArgs` as arguments on this function, and then implements the resolve function by passing the array of ships and the arguments to `connectionFromArray`.
|
65 |
|
66 | ### Object Identification
|
67 |
|
68 | Helper functions are provided for both building the GraphQL types for nodes and for implementing global IDs around local IDs.
|
69 |
|
70 | - `nodeDefinitions` returns the `Node` interface that objects can implement, and returns the `node` root field to include on the query type. To implement this, it takes a function to resolve an ID to an object, and to determine the type of a given object.
|
71 | - `toGlobalId` takes a type name and an ID specific to that type name, and returns a "global ID" that is unique among all types.
|
72 | - `fromGlobalId` takes the "global ID" created by `toGlobalID`, and returns the type name and ID used to create it.
|
73 | - `globalIdField` creates the configuration for an `id` field on a node.
|
74 | - `pluralIdentifyingRootField` creates a field that accepts a list of non-ID identifiers (like a username) and maps them to their corresponding objects.
|
75 |
|
76 | An example usage of these methods from the [test schema](src/__tests__/starWarsSchema.ts):
|
77 |
|
78 | ```js
|
79 | var { nodeInterface, nodeField } = nodeDefinitions(
|
80 | (globalId) => {
|
81 | var { type, id } = fromGlobalId(globalId);
|
82 | return data[type][id];
|
83 | },
|
84 | (obj) => {
|
85 | return obj.ships ? factionType : shipType;
|
86 | },
|
87 | );
|
88 |
|
89 | var factionType = new GraphQLObjectType({
|
90 | name: 'Faction',
|
91 | fields: () => ({
|
92 | id: globalIdField(),
|
93 | }),
|
94 | interfaces: [nodeInterface],
|
95 | });
|
96 |
|
97 | var queryType = new GraphQLObjectType({
|
98 | name: 'Query',
|
99 | fields: () => ({
|
100 | node: nodeField,
|
101 | }),
|
102 | });
|
103 | ```
|
104 |
|
105 | This uses `nodeDefinitions` to construct the `Node` interface and the `node` field; it uses `fromGlobalId` to resolve the IDs passed in the implementation of the function mapping ID to object. It then uses the `globalIdField` method to create the `id` field on `Faction`, which also ensures implements the `nodeInterface`. Finally, it adds the `node` field to the query type, using the `nodeField` returned by `nodeDefinitions`.
|
106 |
|
107 | ### Mutations
|
108 |
|
109 | A helper function is provided for building mutations with single inputs and client mutation IDs.
|
110 |
|
111 | - `mutationWithClientMutationId` takes a name, input fields, output fields, and a mutation method to map from the input fields to the output fields, performing the mutation along the way. It then creates and returns a field configuration that can be used as a top-level field on the mutation type.
|
112 |
|
113 | An example usage of these methods from the [test schema](src/__tests__/starWarsSchema.ts):
|
114 |
|
115 | ```js
|
116 | var shipMutation = mutationWithClientMutationId({
|
117 | name: 'IntroduceShip',
|
118 | inputFields: {
|
119 | shipName: {
|
120 | type: new GraphQLNonNull(GraphQLString),
|
121 | },
|
122 | factionId: {
|
123 | type: new GraphQLNonNull(GraphQLID),
|
124 | },
|
125 | },
|
126 | outputFields: {
|
127 | ship: {
|
128 | type: shipType,
|
129 | resolve: (payload) => data['Ship'][payload.shipId],
|
130 | },
|
131 | faction: {
|
132 | type: factionType,
|
133 | resolve: (payload) => data['Faction'][payload.factionId],
|
134 | },
|
135 | },
|
136 | mutateAndGetPayload: ({ shipName, factionId }) => {
|
137 | var newShip = {
|
138 | id: getNewShipId(),
|
139 | name: shipName,
|
140 | };
|
141 | data.Ship[newShip.id] = newShip;
|
142 | data.Faction[factionId].ships.push(newShip.id);
|
143 | return {
|
144 | shipId: newShip.id,
|
145 | factionId: factionId,
|
146 | };
|
147 | },
|
148 | });
|
149 |
|
150 | var mutationType = new GraphQLObjectType({
|
151 | name: 'Mutation',
|
152 | fields: () => ({
|
153 | introduceShip: shipMutation,
|
154 | }),
|
155 | });
|
156 | ```
|
157 |
|
158 | This code creates a mutation named `IntroduceShip`, which takes a faction ID and a ship name as input. It outputs the `Faction` and the `Ship` in question. `mutateAndGetPayload` then gets an object with a property for each input field, performs the mutation by constructing the new ship, then returns an object that will be resolved by the output fields.
|
159 |
|
160 | Our mutation type then creates the `introduceShip` field using the return value of `mutationWithClientMutationId`.
|
161 |
|
162 | ## Contributing
|
163 |
|
164 | After cloning this repo, ensure dependencies are installed by running:
|
165 |
|
166 | ```sh
|
167 | npm install
|
168 | ```
|
169 |
|
170 | This library is written in ES6 and uses [Babel](https://babeljs.io/) for ES5 transpilation and [TypeScript](https://www.typescriptlang.org/) for type safety. Widely consumable JavaScript can be produced by running:
|
171 |
|
172 | ```sh
|
173 | npm run build
|
174 | ```
|
175 |
|
176 | Once `npm run build` has run, you may `import` or `require()` directly from node.
|
177 |
|
178 | After developing, the full test suite can be evaluated by running:
|
179 |
|
180 | ```sh
|
181 | npm test
|
182 | ```
|
183 |
|
184 | ## Opening a PR
|
185 |
|
186 | We actively welcome pull requests. Learn how to [contribute](./.github/CONTRIBUTING.md).
|
187 |
|
188 | This repository is managed by EasyCLA. Project participants must sign the free ([GraphQL Specification Membership agreement](https://preview-spec-membership.graphql.org) before making a contribution. You only need to do this one time, and it can be signed by [individual contributors](https://individual-spec-membership.graphql.org/) or their [employers](https://corporate-spec-membership.graphql.org/).
|
189 |
|
190 | To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.
|
191 |
|
192 | You can find [detailed information here](https://github.com/graphql/graphql-wg/tree/main/membership). If you have issues, please email [operations@graphql.org](mailto:operations@graphql.org).
|
193 |
|
194 | If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the [GraphQL Foundation](https://foundation.graphql.org/join).
|
195 |
|
196 | ## Changelog
|
197 |
|
198 | Changes are tracked as [GitHub releases](https://github.com/graphql/graphql-js/releases).
|
199 |
|
200 | ## License
|
201 |
|
202 | graphql-relay-js is [MIT licensed](./LICENSE).
|