├── .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 | --------------------------------------------------------------------------------