├── .eslintignore
├── utils
├── babelRelayPlugin.js
└── updateSchema.js
├── .gitignore
├── public
└── index.html
├── server.js
├── browser.js
├── LICENSE
├── schema
├── database.js
└── schema.js
├── .eslintrc
├── package.json
├── App.js
└── README.md
/.eslintignore:
--------------------------------------------------------------------------------
1 | public/bundle.js
2 |
--------------------------------------------------------------------------------
/utils/babelRelayPlugin.js:
--------------------------------------------------------------------------------
1 | module.exports = require('babel-relay-plugin')(require('../schema/schema.json').data)
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | .tern-port
4 | v8.log
5 |
6 | schema/schema.json
7 | public/bundle.js
8 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/utils/updateSchema.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 | var path = require('path')
3 | var graphql = require('graphql').graphql
4 | var introspectionQuery = require('graphql/utilities').introspectionQuery
5 | var schema = require('../schema/schema')
6 |
7 | graphql(schema, introspectionQuery).then(function(result) {
8 | if (result.errors) return console.error(result.errors) // eslint-disable-line no-console
9 | fs.writeFileSync(path.join(__dirname, '../schema/schema.json'), JSON.stringify(result, null, 2))
10 | })
11 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express')
2 | var graphqlHttp = require('express-graphql')
3 | var schema = require('./schema/schema')
4 |
5 | // The server is just a simple Express app
6 | var app = express()
7 |
8 | // We respond to all GraphQL requests from `/graphql` using the
9 | // `express-graphql` middleware, which we pass our schema to.
10 | app.use('/graphql', graphqlHttp({schema: schema}))
11 |
12 | // The rest of the routes are just for serving static files
13 | app.use('/relay', express.static('./node_modules/react-relay/dist'))
14 | app.use('/', express.static('./public'))
15 |
16 | app.listen(3000, function() { console.log('Listening on 3000...') }) // eslint-disable-line no-console
17 |
--------------------------------------------------------------------------------
/browser.js:
--------------------------------------------------------------------------------
1 | var React = require('react') // eslint-disable-line no-unused-vars
2 | var ReactDOM = require('react-dom')
3 | var Relay = require('react-relay') // eslint-disable-line no-unused-vars
4 | var App = require('./App')
5 |
6 | // This file is the entry point on the browser – browserify will compile it, as
7 | // well as App.js and any other client-side dependencies and create
8 | // public/bundle.js which will be requested by public/index.html
9 |
10 | ReactDOM.render(
11 | // At the top of a Relay tree is the root container, which we pass our
12 | // wrapped App component to, as well as the query configuration ("route"). If
13 | // we need to render a different component, say as a result of a navigation
14 | // event, then we would update it here.
15 | // We also illustrate the use of the onReadyStateChange handler in case
16 | // there's a network error, etc
17 | { if (error) console.error(error) }} />, // eslint-disable-line no-console
19 |
20 | document.getElementById('content')
21 | )
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2015 Michael Hart (michael.hart.au@gmail.com)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/schema/database.js:
--------------------------------------------------------------------------------
1 | // We use these types to hold data and resolve from GraphQL types in our schema
2 |
3 | function User(id, name) {
4 | this.id = id.toString()
5 | this.name = name
6 | }
7 |
8 | function Widget(id, userId, name) {
9 | this.id = id.toString()
10 | this.userId = userId.toString()
11 | this.name = name
12 | }
13 |
14 | // In a realistic system, the get functions below would return objects from a
15 | // datastore like a DB or a REST API instead of an in-memory store like this.
16 | // You can also return promises for async fetching
17 |
18 | var users = [new User(1, 'Anonymous')]
19 |
20 | var widgets = [
21 | new Widget(1, 1, 'What\'s-it'),
22 | new Widget(2, 1, 'Who\'s-it'),
23 | new Widget(3, 1, 'How\'s-it'),
24 | ]
25 |
26 | module.exports = {
27 | User: User,
28 | Widget: Widget,
29 | getUser: function(id) { return users.filter(function(u) { return u.id == id })[0] },
30 | getAnonymousUser: function() { return users[0] },
31 | getWidget: function(id) { return widgets.filter(function(w) { return w.id == id })[0] },
32 | getWidgetsByUser: function(userId) { return widgets.filter(function(w) { return w.userId == userId }) },
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint:recommended",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "es6": true,
7 | "mocha": true
8 | },
9 | "parserOptions": {
10 | "ecmaFeatures": {
11 | "jsx": true
12 | }
13 | },
14 | "rules": {
15 |
16 | // relaxed restrictions
17 | "no-mixed-requires": 0,
18 | "no-underscore-dangle": 0,
19 | "no-shadow": 0,
20 | "no-use-before-define": [2, "nofunc"],
21 | "camelcase": [2, {"properties": "never"}],
22 | "curly": 0,
23 | "eqeqeq": 0,
24 | "new-parens": 0,
25 | "quotes": [2, "single", "avoid-escape"],
26 | "semi": [2, "never"],
27 | "strict": 0,
28 |
29 | // extra restrictions
30 | "no-empty-character-class": 2,
31 | "no-extra-parens": [2, "functions"],
32 | "no-floating-decimal": 2,
33 | "no-lonely-if": 2,
34 | "no-self-compare": 2,
35 | "no-throw-literal": 2,
36 | "no-unused-vars": 2,
37 |
38 | // style
39 | "array-bracket-spacing": [2, "never"],
40 | "brace-style": [2, "1tbs", {"allowSingleLine": true}],
41 | "comma-dangle": [2, "always-multiline"],
42 | "comma-style": [2, "last"],
43 | "consistent-this": [2, "self"],
44 | "object-curly-spacing": [2, "never"],
45 | "operator-assignment": [2, "always"],
46 | "operator-linebreak": [2, "after"],
47 | "keyword-spacing": 2,
48 | "space-before-blocks": [2, "always"],
49 | "space-before-function-paren": [2, "never"],
50 | "space-in-parens": [2, "never"],
51 | "spaced-comment": [2, "always"]
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "simple-relay-starter",
3 | "version": "1.3.12",
4 | "description": "A very simple example of React Relay using Browserify",
5 | "main": "server.js",
6 | "repository": "mhart/simple-relay-starter",
7 | "keywords": [
8 | "react",
9 | "reactjs",
10 | "relay",
11 | "browserify",
12 | "graphql"
13 | ],
14 | "author": "Michael Hart ",
15 | "license": "MIT",
16 | "dependencies": {
17 | "express": "^4.14.0",
18 | "express-graphql": "^0.6.2",
19 | "graphql": "^0.9.1",
20 | "graphql-relay": "^0.5.1",
21 | "react": "^15.4.2",
22 | "react-dom": "^15.4.2",
23 | "react-relay": "^0.10.0"
24 | },
25 | "devDependencies": {
26 | "babel-preset-es2015": "^6.22.0",
27 | "babel-preset-react": "^6.22.0",
28 | "babel-relay-plugin": "^0.10.0",
29 | "babelify": "^7.3.0",
30 | "browserify": "^14.0.0",
31 | "browserify-shim": "^3.8.13",
32 | "nodemon": "^1.11.0",
33 | "onchange": "^3.2.1",
34 | "parallelshell": "^2.0.0"
35 | },
36 | "browserify-shim": {
37 | "react": "global:React",
38 | "react-dom": "global:ReactDOM",
39 | "react-relay": "global:Relay"
40 | },
41 | "browserify": {
42 | "transform": [
43 | [
44 | "babelify",
45 | {
46 | "presets": [
47 | "es2015",
48 | "react"
49 | ],
50 | "plugins": [
51 | "./utils/babelRelayPlugin"
52 | ]
53 | }
54 | ],
55 | "browserify-shim"
56 | ]
57 | },
58 | "scripts": {
59 | "start": "node server.js",
60 | "dev": "npm run build && npm run watch",
61 | "build": "npm run build:schema && npm run build:browser",
62 | "build:schema": "node ./utils/updateSchema.js",
63 | "build:browser": "browserify browser.js -o public/bundle.js",
64 | "watch": "parallelshell 'npm run watch:schema' 'npm run watch:browser' 'npm run watch:server'",
65 | "watch:schema": "onchange schema/schema.js -- npm run build:schema",
66 | "watch:browser": "onchange browser.js App.js schema/schema.json -- npm run build:browser",
67 | "watch:server": "nodemon --watch server.js --watch 'schema/*.js' server.js"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | var React = require('react')
2 | var Relay = require('react-relay')
3 |
4 | // A simple top-level component that illustrates how to render Relay-fetched
5 | // data using props. In this case Relay will populate a `user` property that
6 | // has a collection of `widgets` based on the queries and fragments we give it
7 | // further below.
8 | class App extends React.Component {
9 | render() {
10 | return (
11 |
12 |
User: {this.props.user.name}
13 |
Widgets:
14 |
15 | {/* In schema/schema.js we define a Connection between users and widgets */}
16 | {/* Connections use `edges` and `node` to hold paging info and child items */}
17 | {this.props.user.widgets.edges.map(edge =>
18 | - {edge.node.name} (Global ID: {edge.node.id})
19 | )}
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | // The component we need to export is a Relay wrapper around our App component
27 | // from above. It declares the GraphQL fragments where we list the properties
28 | // we want to be fetched – eg, user.name, user.widgets.edges, etc
29 | exports.Container = Relay.createContainer(App, {
30 | fragments: {
31 | // The property name here reflects what is added to `this.props` above.
32 | // This template string will be parsed by babel-relay-plugin when we browserify.
33 | user: () => Relay.QL`
34 | fragment on User {
35 | name,
36 | widgets(first: 10) {
37 | edges {
38 | node {
39 | id,
40 | name,
41 | },
42 | },
43 | },
44 | }
45 | `,
46 | },
47 | })
48 |
49 | // The Relay root container needs to know what queries will occur at the top
50 | // level – these configurations are currently called Routes in Relay, but this
51 | // name is misleading and under review so we don't use it here.
52 | exports.queries = {
53 | name: 'AppQueries', // can be anything, just used as an identifier
54 | params: {},
55 | queries: {
56 | // We can use this shorthand so long as the component we pair this with has
57 | // a fragment named "user", as we do above.
58 | user: () => Relay.QL`query { user }`,
59 | },
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/schema/schema.js:
--------------------------------------------------------------------------------
1 | var GraphQL = require('graphql')
2 | var GraphQLRelay = require('graphql-relay')
3 | var db = require('./database')
4 |
5 | // This module exports a GraphQL Schema, which is a declaration of all the
6 | // types, queries and mutations we'll use in our system.
7 |
8 | // Relay adds some specific types that it needs to function, including Node, Edge, Connection
9 |
10 | // Firstly we need to create the Node interface in our system. This has nothing
11 | // to do with Node.js! In Relay, Node refers to an entity – that is, an object
12 | // with an ID.
13 |
14 | // To create this interface, we need to pass in a resolving function as the
15 | // first arg to nodeDefinitions that can fetch an entity given a global Relay
16 | // ID. The second arg can be used to resolve an entity into a GraphQL type –
17 | // but it's actually optional, so we'll leave it out and use isTypeOf on the
18 | // GraphQL types further below.
19 |
20 | var nodeDefinitions = GraphQLRelay.nodeDefinitions(function(globalId) {
21 | var idInfo = GraphQLRelay.fromGlobalId(globalId)
22 | if (idInfo.type == 'User') {
23 | return db.getUser(idInfo.id)
24 | } else if (idInfo.type == 'Widget') {
25 | return db.getWidget(idInfo.id)
26 | }
27 | return null
28 | })
29 |
30 | // We can now use the Node interface in the GraphQL types of our schema
31 |
32 | var widgetType = new GraphQL.GraphQLObjectType({
33 | name: 'Widget',
34 | description: 'A shiny widget',
35 |
36 | // Relay will use this function to determine if an object in your system is
37 | // of a particular GraphQL type
38 | isTypeOf: function(obj) { return obj instanceof db.Widget },
39 |
40 | // We can either declare our fields as an object of name-to-definition
41 | // mappings or a closure that returns said object (see userType below)
42 | fields: {
43 | id: GraphQLRelay.globalIdField('Widget'),
44 | name: {
45 | type: GraphQL.GraphQLString,
46 | description: 'The name of the widget',
47 | },
48 | },
49 | // This declares this GraphQL type as a Node
50 | interfaces: [nodeDefinitions.nodeInterface],
51 | })
52 |
53 | var userType = new GraphQL.GraphQLObjectType({
54 | name: 'User',
55 | description: 'A person who uses our app',
56 | isTypeOf: function(obj) { return obj instanceof db.User },
57 |
58 | // We use a closure here because we need to refer to widgetType from above
59 | fields: function() {
60 | return {
61 | id: GraphQLRelay.globalIdField('User'),
62 | name: {
63 | type: GraphQL.GraphQLString,
64 | description: 'The name of the user',
65 | },
66 | // Here we set up a paged one-to-many relationship ("Connection")
67 | widgets: {
68 | description: 'A user\'s collection of widgets',
69 |
70 | // Relay gives us helper functions to define the Connection and its args
71 | type: GraphQLRelay.connectionDefinitions({name: 'Widget', nodeType: widgetType}).connectionType,
72 | args: GraphQLRelay.connectionArgs,
73 |
74 | // You can define a resolving function for any field.
75 | // It can also return a promise if you need async data fetching
76 | resolve: function(user, args) {
77 | // This wraps a Connection object around your data array
78 | // Use connectionFromPromisedArray if you return a promise instead
79 | return GraphQLRelay.connectionFromArray(db.getWidgetsByUser(user.id), args)
80 | },
81 | },
82 | }
83 | },
84 | interfaces: [nodeDefinitions.nodeInterface],
85 | })
86 |
87 | // Now we can bundle our types up and export a schema
88 | // GraphQL expects a set of top-level queries and optional mutations (we have
89 | // none in this simple example so we leave the mutation field out)
90 | module.exports = new GraphQL.GraphQLSchema({
91 | query: new GraphQL.GraphQLObjectType({
92 | name: 'Query',
93 | fields: {
94 | // Relay needs this to query Nodes using global IDs
95 | node: nodeDefinitions.nodeField,
96 | // Our own root query field(s) go here
97 | user: {
98 | type: userType,
99 | resolve: function() { return db.getAnonymousUser() },
100 | },
101 | },
102 | }),
103 | })
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | simple-relay-starter
2 | --------------------
3 |
4 | A simple example of how to get started with
5 | [Relay](https://facebook.github.io/relay/) using some slightly different
6 | approaches to [relay-starter-kit](https://github.com/relayjs/relay-starter-kit)
7 | that may make it easier to navigate for first-time users, especially Node.js
8 | users.
9 |
10 | Unlike [relay-starter-kit](https://github.com/relayjs/relay-starter-kit), this
11 | project uses [Browserify](http://browserify.org/) instead of
12 | [Webpack](https://webpack.github.io/), does not use a proxy for the GraphQL
13 | endpoint and does not require ES6 features for any server-side code, so it can
14 | be run directly with `node` – resulting in less boilerplate and making it
15 | easier to understand the code.
16 |
17 | Example
18 | -------
19 |
20 | ```console
21 | $ npm install
22 | $ npm run build
23 | $ npm start
24 | ```
25 |
26 | Then navigate to [http://localhost:3000](http://localhost:3000) and
27 | observe the network request to `/graphql` that Relay makes to retrieve the data.
28 |
29 | For development, you can use:
30 |
31 | ```console
32 | $ npm run dev
33 | ```
34 |
35 | Which will build the schema and then watch for any file changes, rebuilding the
36 | schema and/or restarting the server as necessary.
37 |
38 | Here are the files involved:
39 |
40 | `App.js`:
41 | ```js
42 | var React = require('react')
43 | var Relay = require('react-relay')
44 |
45 | // A simple top-level component that illustrates how to render Relay-fetched
46 | // data using props. In this case Relay will populate a `user` property that
47 | // has a collection of `widgets` based on the queries and fragments we give it
48 | // further below.
49 | class App extends React.Component {
50 | render() {
51 | return (
52 |
53 |
User: {this.props.user.name}
54 |
Widgets:
55 |
56 | {/* In schema/schema.js we define a Connection between users and widgets */}
57 | {/* Connections use `edges` and `node` to hold paging info and child items */}
58 | {this.props.user.widgets.edges.map(edge =>
59 | - {edge.node.name} (Global ID: {edge.node.id})
60 | )}
61 |
62 |
63 | )
64 | }
65 | }
66 |
67 | // The component we need to export is a Relay wrapper around our App component
68 | // from above. It declares the GraphQL fragments where we list the properties
69 | // we want to be fetched – eg, user.name, user.widgets.edges, etc
70 | exports.Container = Relay.createContainer(App, {
71 | fragments: {
72 | // The property name here reflects what is added to `this.props` above.
73 | // This template string will be parsed by babel-relay-plugin when we browserify.
74 | user: () => Relay.QL`
75 | fragment on User {
76 | name,
77 | widgets(first: 10) {
78 | edges {
79 | node {
80 | id,
81 | name,
82 | },
83 | },
84 | },
85 | }
86 | `,
87 | },
88 | })
89 |
90 | // The Relay root container needs to know what queries will occur at the top
91 | // level – these configurations are currently called Routes in Relay, but this
92 | // name is misleading and under review so we don't use it here.
93 | exports.queries = {
94 | name: 'AppQueries', // can be anything, just used as an identifier
95 | params: {},
96 | queries: {
97 | // We can use this shorthand so long as the component we pair this with has
98 | // a fragment named "user", as we do above.
99 | user: () => Relay.QL`query { user }`,
100 | },
101 | }
102 | ```
103 |
104 | `browser.js`:
105 | ```js
106 | var React = require('react')
107 | var ReactDOM = require('react-dom')
108 | var Relay = require('react-relay')
109 | var App = require('./App')
110 |
111 | // This file is the entry point on the browser – browserify will compile it, as
112 | // well as App.js and any other client-side dependencies and create
113 | // public/bundle.js which will be requested by public/index.html
114 |
115 | ReactDOM.render(
116 | // At the top of a Relay tree is the root container, which we pass our
117 | // wrapped App component to, as well as the query configuration ("route"). If
118 | // we need to render a different component, say as a result of a navigation
119 | // event, then we would update it here.
120 | // We also illustrate the use of the onReadyStateChange handler in case
121 | // there's a network error, etc
122 | { if (error) console.error(error) }} />,
124 |
125 | document.getElementById('content')
126 | )
127 | ```
128 |
129 | `public/index.html`:
130 | ```html
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | ```
139 |
140 | `server.js`:
141 | ```js
142 | var express = require('express')
143 | var graphqlHttp = require('express-graphql')
144 | var schema = require('./schema/schema')
145 |
146 | // The server is just a simple Express app
147 | var app = express()
148 |
149 | // We respond to all GraphQL requests from `/graphql` using the
150 | // `express-graphql` middleware, which we pass our schema to.
151 | app.use('/graphql', graphqlHttp({schema: schema}))
152 |
153 | // The rest of the routes are just for serving static files
154 | app.use('/relay', express.static('./node_modules/react-relay/dist'))
155 | app.use('/', express.static('./public'))
156 |
157 | app.listen(3000, function() { console.log('Listening on 3000...') })
158 | ```
159 |
160 | `schema/database.js`:
161 | ```js
162 | // We use these types to hold data and resolve from GraphQL types in our schema
163 |
164 | function User(id, name) {
165 | this.id = id.toString()
166 | this.name = name
167 | }
168 |
169 | function Widget(id, userId, name) {
170 | this.id = id.toString()
171 | this.userId = userId.toString()
172 | this.name = name
173 | }
174 |
175 | // In a realistic system, the get functions below would return objects from a
176 | // datastore like a DB or a REST API instead of an in-memory store like this.
177 | // You can also return promises for async fetching
178 |
179 | var users = [new User(1, 'Anonymous')]
180 |
181 | var widgets = [
182 | new Widget(1, 1, 'What\'s-it'),
183 | new Widget(2, 1, 'Who\'s-it'),
184 | new Widget(3, 1, 'How\'s-it'),
185 | ]
186 |
187 | module.exports = {
188 | User: User,
189 | Widget: Widget,
190 | getUser: function(id) { return users.filter(function(u) { return u.id == id })[0] },
191 | getAnonymousUser: function() { return users[0] },
192 | getWidget: function(id) { return widgets.filter(function(w) { return w.id == id })[0] },
193 | getWidgetsByUser: function(userId) { return widgets.filter(function(w) { return w.userId == userId }) },
194 | }
195 | ```
196 |
197 | `schema/schema.js`:
198 | ```js
199 | var GraphQL = require('graphql')
200 | var GraphQLRelay = require('graphql-relay')
201 | var db = require('./database')
202 |
203 | // This module exports a GraphQL Schema, which is a declaration of all the
204 | // types, queries and mutations we'll use in our system.
205 |
206 | // Relay adds some specific types that it needs to function, including Node, Edge, Connection
207 |
208 | // Firstly we need to create the Node interface in our system. This has nothing
209 | // to do with Node.js! In Relay, Node refers to an entity – that is, an object
210 | // with an ID.
211 |
212 | // To create this interface, we need to pass in a resolving function as the
213 | // first arg to nodeDefinitions that can fetch an entity given a global Relay
214 | // ID. The second arg can be used to resolve an entity into a GraphQL type –
215 | // but it's actually optional, so we'll leave it out and use isTypeOf on the
216 | // GraphQL types further below.
217 |
218 | var nodeDefinitions = GraphQLRelay.nodeDefinitions(function(globalId) {
219 | var idInfo = GraphQLRelay.fromGlobalId(globalId)
220 | if (idInfo.type == 'User') {
221 | return db.getUser(idInfo.id)
222 | } else if (idInfo.type == 'Widget') {
223 | return db.getWidget(idInfo.id)
224 | }
225 | return null
226 | })
227 |
228 | // We can now use the Node interface in the GraphQL types of our schema
229 |
230 | var widgetType = new GraphQL.GraphQLObjectType({
231 | name: 'Widget',
232 | description: 'A shiny widget',
233 |
234 | // Relay will use this function to determine if an object in your system is
235 | // of a particular GraphQL type
236 | isTypeOf: function(obj) { return obj instanceof db.Widget },
237 |
238 | // We can either declare our fields as an object of name-to-definition
239 | // mappings or a closure that returns said object (see userType below)
240 | fields: {
241 | id: GraphQLRelay.globalIdField('Widget'),
242 | name: {
243 | type: GraphQL.GraphQLString,
244 | description: 'The name of the widget',
245 | },
246 | },
247 | // This declares this GraphQL type as a Node
248 | interfaces: [nodeDefinitions.nodeInterface],
249 | })
250 |
251 | var userType = new GraphQL.GraphQLObjectType({
252 | name: 'User',
253 | description: 'A person who uses our app',
254 | isTypeOf: function(obj) { return obj instanceof db.User },
255 |
256 | // We use a closure here because we need to refer to widgetType from above
257 | fields: function() {
258 | return {
259 | id: GraphQLRelay.globalIdField('User'),
260 | name: {
261 | type: GraphQL.GraphQLString,
262 | description: 'The name of the user',
263 | },
264 | // Here we set up a paged one-to-many relationship ("Connection")
265 | widgets: {
266 | description: 'A user\'s collection of widgets',
267 |
268 | // Relay gives us helper functions to define the Connection and its args
269 | type: GraphQLRelay.connectionDefinitions({name: 'Widget', nodeType: widgetType}).connectionType,
270 | args: GraphQLRelay.connectionArgs,
271 |
272 | // You can define a resolving function for any field.
273 | // It can also return a promise if you need async data fetching
274 | resolve: function(user, args) {
275 | // This wraps a Connection object around your data array
276 | // Use connectionFromPromisedArray if you return a promise instead
277 | return GraphQLRelay.connectionFromArray(db.getWidgetsByUser(user.id), args)
278 | },
279 | },
280 | }
281 | },
282 | interfaces: [nodeDefinitions.nodeInterface],
283 | })
284 |
285 | // Now we can bundle our types up and export a schema
286 | // GraphQL expects a set of top-level queries and optional mutations (we have
287 | // none in this simple example so we leave the mutation field out)
288 | module.exports = new GraphQL.GraphQLSchema({
289 | query: new GraphQL.GraphQLObjectType({
290 | name: 'Query',
291 | fields: {
292 | // Relay needs this to query Nodes using global IDs
293 | node: nodeDefinitions.nodeField,
294 | // Our own root query field(s) go here
295 | user: {
296 | type: userType,
297 | resolve: function() { return db.getAnonymousUser() },
298 | },
299 | },
300 | }),
301 | })
302 | ```
303 |
--------------------------------------------------------------------------------