├── .babelrc ├── .gitignore ├── README.md ├── babelRelayPlugin.js ├── data └── quotes ├── index.js ├── js ├── app.js ├── quote.js ├── search-form.js └── thumbs-up-mutation.js ├── package.json ├── public └── index.html ├── schema └── main.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "passPerPreset": true, 3 | "presets": [ 4 | {"plugins": ["./babelRelayPlugin"]}, 5 | "react", 6 | "es2015", 7 | "stage-0" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/bundle.js 3 | public/bundle.js.map 4 | cache/schema.json 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning GraphQL and Relay 2 | 3 | A Packt Book: https://www.packtpub.com/web-development/learning-graphql-and-relay 4 | 5 | For questions: [slack.jscomplete.com](http://slack.jscomplete.com/) 6 | -------------------------------------------------------------------------------- /babelRelayPlugin.js: -------------------------------------------------------------------------------- 1 | const babelRelayPlugin = require('babel-relay-plugin'); 2 | const schema = require('./cache/schema.json'); 3 | 4 | module.exports = babelRelayPlugin(schema.data); 5 | -------------------------------------------------------------------------------- /data/quotes: -------------------------------------------------------------------------------- 1 | The best preparation for tomorrow is doing your best today. 2 | Life is 10 percent what happens to you and 90 percent how you react to it. 3 | If opportunity doesn't knock, build a door. 4 | Try to be a rainbow in someone's cloud. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const { MongoClient } = require('mongodb'); 4 | const assert = require('assert'); 5 | const { graphql } = require('graphql'); 6 | const { introspectionQuery } = require('graphql/utilities'); 7 | const graphqlHTTP = require('express-graphql'); 8 | const express = require('express'); 9 | 10 | const app = express(); 11 | app.use(express.static('public')); 12 | 13 | const mySchema = require('./schema/main'); 14 | const MONGO_URL = 'mongodb://localhost:27017/test'; 15 | 16 | MongoClient.connect(MONGO_URL, (err, db) => { 17 | assert.equal(null, err); 18 | console.log('Connected to MongoDB server'); 19 | 20 | app.use('/graphql', graphqlHTTP({ 21 | schema: mySchema, 22 | context: { db }, 23 | graphiql: true 24 | })); 25 | 26 | graphql(mySchema, introspectionQuery) 27 | .then(result => { 28 | fs.writeFileSync( 29 | path.join(__dirname, 'cache/schema.json'), 30 | JSON.stringify(result, null, 2) 31 | ); 32 | console.log('Generated cached schema.json file'); 33 | }) 34 | .catch(console.error); 35 | 36 | app.listen(3000, () => 37 | console.log('Running Express.js on port 3000') 38 | ); 39 | }); 40 | -------------------------------------------------------------------------------- /js/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Relay from 'react-relay'; 4 | import { debounce } from 'lodash'; 5 | 6 | import SearchForm from './search-form'; 7 | import Quote from './quote'; 8 | 9 | class QuotesLibrary extends React.Component { 10 | constructor(props) { 11 | super(props); 12 | this.search = debounce(this.search.bind(this), 300); 13 | } 14 | search(searchTerm) { 15 | this.props.relay.setVariables({searchTerm}); 16 | } 17 | render() { 18 | return ( 19 |
24 | )} 25 |
35 |39 | ); 40 | } 41 | } 42 | 43 | Quote = Relay.createContainer(Quote, { 44 | initialVariables: { 45 | showLikes: false 46 | }, 47 | fragments: { 48 | quote: () => Relay.QL ` 49 | fragment OneQuote on Quote { 50 | ${ThumbsUpMutation.getFragment('quote')} 51 | text 52 | author 53 | likesCount @include(if: $showLikes) 54 | } 55 | ` 56 | } 57 | }); 58 | 59 | export default Quote; 60 | -------------------------------------------------------------------------------- /js/search-form.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class SearchForm extends React.Component { 4 | static propTypes = { 5 | searchAction: React.PropTypes.func.isRequired 6 | }; 7 | 8 | handleChange = event => { 9 | this.props.searchAction(event.target.value); 10 | }; 11 | 12 | render() { 13 | return ( 14 | 19 | ) 20 | } 21 | } 22 | 23 | export default SearchForm; 24 | -------------------------------------------------------------------------------- /js/thumbs-up-mutation.js: -------------------------------------------------------------------------------- 1 | import Relay from 'react-relay'; 2 | 3 | class ThumbsUpMutation extends Relay.Mutation { 4 | 5 | static fragments = { 6 | quote: () => Relay.QL ` 7 | fragment on Quote { 8 | id 9 | likesCount 10 | } 11 | ` 12 | }; 13 | 14 | getMutation() { 15 | return Relay.QL ` 16 | mutation { 17 | thumbsUp 18 | } 19 | `; 20 | } 21 | 22 | getVariables() { 23 | return { 24 | quoteId: this.props.quote.id 25 | }; 26 | } 27 | 28 | getFatQuery() { 29 | return Relay.QL ` 30 | fragment on ThumbsUpMutationPayload { 31 | quote { 32 | likesCount 33 | } 34 | } 35 | `; 36 | } 37 | 38 | getConfigs() { 39 | return [ 40 | { 41 | type: 'FIELDS_CHANGE', 42 | fieldIDs: { 43 | quote: this.props.quote.id 44 | } 45 | } 46 | ]; 47 | } 48 | 49 | getOptimisticResponse() { 50 | return { 51 | quote: { 52 | id: this.props.quote.id, 53 | likesCount: this.props.quote.likesCount + 1 54 | } 55 | }; 56 | } 57 | 58 | } 59 | 60 | export default ThumbsUpMutation; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-project", 3 | "version": "1.0.0", 4 | "description": "Learning GraphQL and Relay", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/edgecoders/learning-graphql-and-relay.git" 12 | }, 13 | "author": "Samer Buna", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/edgecoders/learning-graphql-and-relay/issues" 17 | }, 18 | "homepage": "https://github.com/edgecoders/learning-graphql-and-relay#readme", 19 | "dependencies": { 20 | "babel-loader": "^6.2.4", 21 | "babel-preset-es2015": "^6.13.2", 22 | "babel-preset-react": "^6.11.1", 23 | "babel-preset-stage-0": "^6.5.0", 24 | "babel-relay-plugin": "^0.9.2", 25 | "express": "^4.14.0", 26 | "express-graphql": "^0.5.3", 27 | "graphql": "^0.6.2", 28 | "graphql-relay": "^0.4.2", 29 | "lodash": "^4.14.2", 30 | "mongodb": "^2.2.5", 31 | "react": "^15.3.0", 32 | "react-dom": "^15.3.0", 33 | "react-relay": "^0.9.2", 34 | "webpack": "^1.13.1" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |{this.props.quote.text}
36 | 37 | {this.displayLikes()} 38 |