Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mhart/simple-relay-starter

A very simple starter for React Relay using Browserify
https://github.com/mhart/simple-relay-starter

Last synced: about 2 months ago
JSON representation

A very simple starter for React Relay using Browserify

Awesome Lists containing this project

README

        

simple-relay-starter
--------------------

A simple example of how to get started with
[Relay](https://facebook.github.io/relay/) using some slightly different
approaches to [relay-starter-kit](https://github.com/relayjs/relay-starter-kit)
that may make it easier to navigate for first-time users, especially Node.js
users.

Unlike [relay-starter-kit](https://github.com/relayjs/relay-starter-kit), this
project uses [Browserify](http://browserify.org/) instead of
[Webpack](https://webpack.github.io/), does not use a proxy for the GraphQL
endpoint and does not require ES6 features for any server-side code, so it can
be run directly with `node` – resulting in less boilerplate and making it
easier to understand the code.

Example
-------

```console
$ npm install
$ npm run build
$ npm start
```

Then navigate to [http://localhost:3000](http://localhost:3000) and
observe the network request to `/graphql` that Relay makes to retrieve the data.

For development, you can use:

```console
$ npm run dev
```

Which will build the schema and then watch for any file changes, rebuilding the
schema and/or restarting the server as necessary.

Here are the files involved:

`App.js`:
```js
var React = require('react')
var Relay = require('react-relay')

// A simple top-level component that illustrates how to render Relay-fetched
// data using props. In this case Relay will populate a `user` property that
// has a collection of `widgets` based on the queries and fragments we give it
// further below.
class App extends React.Component {
render() {
return (


User: {this.props.user.name}


Widgets:



    {/* In schema/schema.js we define a Connection between users and widgets */}
    {/* Connections use `edges` and `node` to hold paging info and child items */}
    {this.props.user.widgets.edges.map(edge =>
  • {edge.node.name} (Global ID: {edge.node.id})

  • )}


)
}
}

// The component we need to export is a Relay wrapper around our App component
// from above. It declares the GraphQL fragments where we list the properties
// we want to be fetched – eg, user.name, user.widgets.edges, etc
exports.Container = Relay.createContainer(App, {
fragments: {
// The property name here reflects what is added to `this.props` above.
// This template string will be parsed by babel-relay-plugin when we browserify.
user: () => Relay.QL`
fragment on User {
name,
widgets(first: 10) {
edges {
node {
id,
name,
},
},
},
}
`,
},
})

// The Relay root container needs to know what queries will occur at the top
// level – these configurations are currently called Routes in Relay, but this
// name is misleading and under review so we don't use it here.
exports.queries = {
name: 'AppQueries', // can be anything, just used as an identifier
params: {},
queries: {
// We can use this shorthand so long as the component we pair this with has
// a fragment named "user", as we do above.
user: () => Relay.QL`query { user }`,
},
}
```

`browser.js`:
```js
var React = require('react')
var ReactDOM = require('react-dom')
var Relay = require('react-relay')
var App = require('./App')

// This file is the entry point on the browser – browserify will compile it, as
// well as App.js and any other client-side dependencies and create
// public/bundle.js which will be requested by public/index.html

ReactDOM.render(
// At the top of a Relay tree is the root container, which we pass our
// wrapped App component to, as well as the query configuration ("route"). If
// we need to render a different component, say as a result of a navigation
// event, then we would update it here.
// We also illustrate the use of the onReadyStateChange handler in case
// there's a network error, etc
{ if (error) console.error(error) }} />,

document.getElementById('content')
)
```

`public/index.html`:
```html

```

`server.js`:
```js
var express = require('express')
var graphqlHttp = require('express-graphql')
var schema = require('./schema/schema')

// The server is just a simple Express app
var app = express()

// We respond to all GraphQL requests from `/graphql` using the
// `express-graphql` middleware, which we pass our schema to.
app.use('/graphql', graphqlHttp({schema: schema}))

// The rest of the routes are just for serving static files
app.use('/relay', express.static('./node_modules/react-relay/dist'))
app.use('/', express.static('./public'))

app.listen(3000, function() { console.log('Listening on 3000...') })
```

`schema/database.js`:
```js
// We use these types to hold data and resolve from GraphQL types in our schema

function User(id, name) {
this.id = id.toString()
this.name = name
}

function Widget(id, userId, name) {
this.id = id.toString()
this.userId = userId.toString()
this.name = name
}

// In a realistic system, the get functions below would return objects from a
// datastore like a DB or a REST API instead of an in-memory store like this.
// You can also return promises for async fetching

var users = [new User(1, 'Anonymous')]

var widgets = [
new Widget(1, 1, 'What\'s-it'),
new Widget(2, 1, 'Who\'s-it'),
new Widget(3, 1, 'How\'s-it'),
]

module.exports = {
User: User,
Widget: Widget,
getUser: function(id) { return users.filter(function(u) { return u.id == id })[0] },
getAnonymousUser: function() { return users[0] },
getWidget: function(id) { return widgets.filter(function(w) { return w.id == id })[0] },
getWidgetsByUser: function(userId) { return widgets.filter(function(w) { return w.userId == userId }) },
}
```

`schema/schema.js`:
```js
var GraphQL = require('graphql')
var GraphQLRelay = require('graphql-relay')
var db = require('./database')

// This module exports a GraphQL Schema, which is a declaration of all the
// types, queries and mutations we'll use in our system.

// Relay adds some specific types that it needs to function, including Node, Edge, Connection

// Firstly we need to create the Node interface in our system. This has nothing
// to do with Node.js! In Relay, Node refers to an entity – that is, an object
// with an ID.

// To create this interface, we need to pass in a resolving function as the
// first arg to nodeDefinitions that can fetch an entity given a global Relay
// ID. The second arg can be used to resolve an entity into a GraphQL type –
// but it's actually optional, so we'll leave it out and use isTypeOf on the
// GraphQL types further below.

var nodeDefinitions = GraphQLRelay.nodeDefinitions(function(globalId) {
var idInfo = GraphQLRelay.fromGlobalId(globalId)
if (idInfo.type == 'User') {
return db.getUser(idInfo.id)
} else if (idInfo.type == 'Widget') {
return db.getWidget(idInfo.id)
}
return null
})

// We can now use the Node interface in the GraphQL types of our schema

var widgetType = new GraphQL.GraphQLObjectType({
name: 'Widget',
description: 'A shiny widget',

// Relay will use this function to determine if an object in your system is
// of a particular GraphQL type
isTypeOf: function(obj) { return obj instanceof db.Widget },

// We can either declare our fields as an object of name-to-definition
// mappings or a closure that returns said object (see userType below)
fields: {
id: GraphQLRelay.globalIdField('Widget'),
name: {
type: GraphQL.GraphQLString,
description: 'The name of the widget',
},
},
// This declares this GraphQL type as a Node
interfaces: [nodeDefinitions.nodeInterface],
})

var userType = new GraphQL.GraphQLObjectType({
name: 'User',
description: 'A person who uses our app',
isTypeOf: function(obj) { return obj instanceof db.User },

// We use a closure here because we need to refer to widgetType from above
fields: function() {
return {
id: GraphQLRelay.globalIdField('User'),
name: {
type: GraphQL.GraphQLString,
description: 'The name of the user',
},
// Here we set up a paged one-to-many relationship ("Connection")
widgets: {
description: 'A user\'s collection of widgets',

// Relay gives us helper functions to define the Connection and its args
type: GraphQLRelay.connectionDefinitions({name: 'Widget', nodeType: widgetType}).connectionType,
args: GraphQLRelay.connectionArgs,

// You can define a resolving function for any field.
// It can also return a promise if you need async data fetching
resolve: function(user, args) {
// This wraps a Connection object around your data array
// Use connectionFromPromisedArray if you return a promise instead
return GraphQLRelay.connectionFromArray(db.getWidgetsByUser(user.id), args)
},
},
}
},
interfaces: [nodeDefinitions.nodeInterface],
})

// Now we can bundle our types up and export a schema
// GraphQL expects a set of top-level queries and optional mutations (we have
// none in this simple example so we leave the mutation field out)
module.exports = new GraphQL.GraphQLSchema({
query: new GraphQL.GraphQLObjectType({
name: 'Query',
fields: {
// Relay needs this to query Nodes using global IDs
node: nodeDefinitions.nodeField,
// Our own root query field(s) go here
user: {
type: userType,
resolve: function() { return db.getAnonymousUser() },
},
},
}),
})
```