├── .babelrc ├── .eslintrc ├── .gitignore ├── .jscsrc ├── .jshintrc ├── db.js ├── index.js ├── package.json ├── schema-relay.js ├── schema.js └── server.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "node5" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | extends: eslint:recommended 3 | env: 4 | browser: true 5 | node: true 6 | mocha: true 7 | rules: 8 | strict: 0 9 | no-console: 0 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | **/.DS_Store 3 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "disallowEmptyBlocks": true, 4 | "disallowIdenticalDestructuringNames": true, 5 | "disallowKeywordsOnNewLine": ["else"], 6 | "disallowMixedSpacesAndTabs": true, 7 | "disallowMultiLineTernary": true, 8 | "disallowMultipleLineBreaks": true, 9 | "disallowMultipleSpaces": true, 10 | "disallowNestedTernaries": true, 11 | "disallowNewlineBeforeBlockStatements": true, 12 | "disallowPaddingNewlinesInBlocks": true, 13 | "disallowParenthesesAroundArrowParam": true, 14 | "disallowQuotedKeysInObjects": { "allExcept": ["reserved"] }, 15 | "disallowShorthandArrowFunctions": true, 16 | "disallowSpaceBeforeComma": true, 17 | "disallowSpaceBeforeSemicolon": true, 18 | "disallowSpacesInCallExpression": true, 19 | "disallowSpacesInsideArrayBrackets": true, 20 | "disallowSpacesInsideParentheses": true, 21 | "disallowSpacesInsideParenthesizedExpression": true, 22 | "disallowTabs": true, 23 | "disallowTrailingComma": true, 24 | "disallowTrailingWhitespace": true, 25 | "disallowYodaConditions": true, 26 | "jsDoc": { 27 | "checkParamNames": true, 28 | "checkRedundantParams": true, 29 | "requireParamTypes": true 30 | }, 31 | "maximumLineLength": 120, 32 | "requireBlocksOnNewline": true, 33 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 34 | "requireCapitalizedComments": true, 35 | "requireCapitalizedConstructors": true, 36 | "requireCommaBeforeLineBreak": true, 37 | "requireCurlyBraces": [ 38 | "if", 39 | "else", 40 | "for", 41 | "while", 42 | "do", 43 | "try", 44 | "catch" 45 | ], 46 | "requireDollarBeforejQueryAssignment": true, 47 | "requireLineBreakAfterVariableAssignment": true, 48 | "requireLineFeedAtFileEnd": true, 49 | "requireObjectKeysOnNewLine": true, 50 | "requireParenthesesAroundIIFE": true, 51 | "requireSpaceAfterBinaryOperators": true, 52 | "requireSpaceAfterComma": true, 53 | "requireSpaceAfterKeywords": true, 54 | "requireSpaceAfterLineComment": true, 55 | "requireSpaceBeforeBinaryOperators": true, 56 | "requireSpaceBeforeBlockStatements": true, 57 | "requireSpaceBeforeKeywords": [ 58 | "else", 59 | "while", 60 | "catch" 61 | ], 62 | "requireSpaceBeforeObjectValues": true, 63 | "requireSpaceBetweenArguments": true, 64 | "requireSpacesInConditionalExpression": true, 65 | "requireSpacesInForStatement": true, 66 | "requireSpacesInFunction": { 67 | "beforeOpeningRoundBrace": true, 68 | "beforeOpeningCurlyBrace": true 69 | }, 70 | "requireSpacesInsideObjectBrackets": "all", 71 | "requireSpread": true, 72 | "requireTemplateStrings": { 73 | "allExcept": ["stringConcatenation"] 74 | }, 75 | "requireSemicolons": true, 76 | "requireVarDeclFirst": true, 77 | "validateQuoteMarks": "'", 78 | "validateIndentation": 2 79 | } 80 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "quotmark": "single", 14 | "undef": true, 15 | "unused": true, 16 | "mocha": true, 17 | "globals": { 18 | "$": false 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | import Sequelize from 'sequelize'; 2 | import Faker from 'faker'; 3 | import _ from 'lodash'; 4 | 5 | const Conn = new Sequelize( 6 | 'relay', 7 | 'postgres', 8 | 'postgres', 9 | { 10 | dialect: 'postgres', 11 | host: 'localhost' 12 | } 13 | ); 14 | 15 | const Person = Conn.define('person', { 16 | firstName: { 17 | type: Sequelize.STRING, 18 | allowNull: false 19 | }, 20 | lastName: { 21 | type: Sequelize.STRING, 22 | allowNull: false 23 | }, 24 | email: { 25 | type: Sequelize.STRING, 26 | validate: { 27 | isEmail: true 28 | } 29 | } 30 | }); 31 | 32 | const Post = Conn.define('post', { 33 | title: { 34 | type: Sequelize.STRING, 35 | allowNull: false 36 | }, 37 | content: { 38 | type: Sequelize.STRING, 39 | allowNull: false 40 | } 41 | }); 42 | 43 | // Relations 44 | Person.hasMany(Post); 45 | Post.belongsTo(Person); 46 | 47 | Conn.sync({ force: true }).then(()=> { 48 | _.times(10, ()=> { 49 | return Person.create({ 50 | firstName: Faker.name.firstName(), 51 | lastName: Faker.name.lastName(), 52 | email: Faker.internet.email() 53 | }).then(person => { 54 | return person.createPost({ 55 | title: `Sample post by ${person.firstName}`, 56 | content: 'here is some content' 57 | }); 58 | }); 59 | }); 60 | }); 61 | 62 | export default Conn; 63 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | require('babel-polyfill'); 3 | require('./server'); 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "relay-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node ." 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-core": "^6.4.5", 14 | "babel-polyfill": "^6.3.14", 15 | "babel-preset-node5": "^10.5.0", 16 | "babel-register": "^6.4.3", 17 | "babel-relay-plugin": "^0.6.3", 18 | "bluebird": "^3.2.1", 19 | "express": "^4.13.4", 20 | "express-graphql": "^0.4.4", 21 | "faker": "^3.0.1", 22 | "graphql": "^0.4.16", 23 | "graphql-relay": "^0.3.6", 24 | "lodash": "^4.2.1", 25 | "pg": "^4.4.4", 26 | "pg-hstore": "^2.3.2", 27 | "sequelize": "^3.19.1" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /schema-relay.js: -------------------------------------------------------------------------------- 1 | import Db from './db'; 2 | 3 | import { 4 | GraphQLObjectType, 5 | GraphQLString, 6 | GraphQLInt, 7 | GraphQLSchema, 8 | GraphQLList 9 | } from 'graphql'; 10 | 11 | import { 12 | nodeDefinitions, 13 | fromGlobalId, 14 | globalIdField, 15 | connectionArgs, 16 | connectionDefinitions, 17 | connectionFromPromisedArray 18 | } from 'graphql-relay'; 19 | 20 | const Post = new GraphQLObjectType({ 21 | name: 'Post', 22 | description: 'Blog post', 23 | fields () { 24 | return { 25 | title: { 26 | type: GraphQLString, 27 | resolve (post) { 28 | return post.title; 29 | } 30 | }, 31 | content: { 32 | type: GraphQLString, 33 | resolve (post) { 34 | return post.content; 35 | } 36 | }, 37 | person: { 38 | type: personType, 39 | resolve (post) { 40 | return post.getPerson(); 41 | } 42 | } 43 | }; 44 | } 45 | }); 46 | 47 | const { nodeInterface, nodeField } = nodeDefinitions( 48 | globalId => { 49 | const { type, id } = fromGlobalId(globalId); 50 | 51 | console.log('type=', type); 52 | console.log('id=', id); 53 | 54 | if (type === 'Person') { 55 | return Db.models.person.findById(id); 56 | } 57 | return null; 58 | }, 59 | obj => { 60 | return personType; 61 | } 62 | ); 63 | 64 | const personType = new GraphQLObjectType({ 65 | name: 'Person', 66 | description: 'This represents a Person', 67 | fields: () => { 68 | return { 69 | id: globalIdField('Person'), 70 | firstName: { 71 | type: GraphQLString, 72 | resolve (person) { 73 | return person.firstName; 74 | } 75 | }, 76 | lastName: { 77 | type: GraphQLString, 78 | resolve (person) { 79 | return person.lastName; 80 | } 81 | }, 82 | email: { 83 | type: GraphQLString, 84 | resolve (person) { 85 | return person.email; 86 | } 87 | }, 88 | posts: { 89 | type: new GraphQLList(Post), 90 | resolve (person) { 91 | return person.getPosts(); 92 | } 93 | } 94 | }; 95 | }, 96 | interfaces: [nodeInterface] 97 | }); 98 | 99 | // Connections 100 | const { connectionType: PersonConnection } = connectionDefinitions({ 101 | name: 'Person', 102 | nodeType: personType 103 | }); 104 | 105 | const queryType = new GraphQLObjectType({ 106 | name: 'Query', 107 | description: 'Root query', 108 | fields: () => ({ 109 | node: nodeField, 110 | peopleRelay: { 111 | type: PersonConnection, 112 | description: 'Person connection test', 113 | args: connectionArgs, 114 | resolve (root, args) { 115 | return connectionFromPromisedArray(Db.models.person.findAll(), args); 116 | } 117 | }, 118 | person: { 119 | type: personType, 120 | resolve (root, args) { 121 | return Db.models.person.findOne({ where: args }); 122 | } 123 | }, 124 | people: { 125 | type: new GraphQLList(personType), 126 | args: { 127 | id: { 128 | type: GraphQLInt 129 | }, 130 | email: { 131 | type: GraphQLString 132 | } 133 | }, 134 | resolve (root, args) { 135 | return Db.models.person.findAll({ where: args }); 136 | } 137 | } 138 | }) 139 | }); 140 | 141 | export default new GraphQLSchema({ 142 | query: queryType 143 | }); 144 | -------------------------------------------------------------------------------- /schema.js: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLObjectType, 3 | GraphQLString, 4 | GraphQLInt, 5 | GraphQLSchema, 6 | GraphQLList, 7 | GraphQLNonNull 8 | } from 'graphql'; 9 | 10 | import Db from './db'; 11 | 12 | const Post = new GraphQLObjectType({ 13 | name: 'Post', 14 | description: 'Blog post', 15 | fields () { 16 | return { 17 | title: { 18 | type: GraphQLString, 19 | resolve (post) { 20 | return post.title; 21 | } 22 | }, 23 | content: { 24 | type: GraphQLString, 25 | resolve (post) { 26 | return post.content; 27 | } 28 | }, 29 | person: { 30 | type: Person, 31 | resolve (post) { 32 | return post.getPerson(); 33 | } 34 | } 35 | }; 36 | } 37 | }); 38 | 39 | const Person = new GraphQLObjectType({ 40 | name: 'Person', 41 | description: 'This represents a Person', 42 | fields: () => { 43 | return { 44 | id: { 45 | type: GraphQLInt, 46 | resolve (person) { 47 | return person.id; 48 | } 49 | }, 50 | firstName: { 51 | type: GraphQLString, 52 | resolve (person) { 53 | return person.firstName; 54 | } 55 | }, 56 | lastName: { 57 | type: GraphQLString, 58 | resolve (person) { 59 | return person.lastName; 60 | } 61 | }, 62 | email: { 63 | type: GraphQLString, 64 | resolve (person) { 65 | return person.email; 66 | } 67 | }, 68 | posts: { 69 | type: new GraphQLList(Post), 70 | resolve (person) { 71 | return person.getPosts(); 72 | } 73 | } 74 | }; 75 | } 76 | }); 77 | 78 | const Query = new GraphQLObjectType({ 79 | name: 'Query', 80 | description: 'Root query object', 81 | fields: () => { 82 | return { 83 | people: { 84 | type: new GraphQLList(Person), 85 | args: { 86 | id: { 87 | type: GraphQLInt 88 | }, 89 | email: { 90 | type: GraphQLString 91 | } 92 | }, 93 | resolve (root, args) { 94 | return Db.models.person.findAll({ where: args }); 95 | } 96 | }, 97 | posts: { 98 | type: new GraphQLList(Post), 99 | resolve (root, args) { 100 | return Db.models.post.findAll({ where: args }); 101 | } 102 | } 103 | }; 104 | } 105 | }); 106 | 107 | const Mutation = new GraphQLObjectType({ 108 | name: 'Mutations', 109 | description: 'Functions to set stuff', 110 | fields () { 111 | return { 112 | addPerson: { 113 | type: Person, 114 | args: { 115 | firstName: { 116 | type: new GraphQLNonNull(GraphQLString) 117 | }, 118 | lastName: { 119 | type: new GraphQLNonNull(GraphQLString) 120 | }, 121 | email: { 122 | type: new GraphQLNonNull(GraphQLString) 123 | } 124 | }, 125 | resolve (source, args) { 126 | return Db.models.person.create({ 127 | firstName: args.firstName, 128 | lastName: args.lastName, 129 | email: args.email.toLowerCase() 130 | }); 131 | } 132 | }, 133 | addPost: { 134 | type: Post, 135 | args: { 136 | userId: { 137 | type: GraphQLNonNull(GraphQLInt) 138 | }, 139 | title: { 140 | type: GraphQLNonNull(GraphQLString) 141 | }, 142 | content: { 143 | type: GraphQLNonNull(GraphQLString) 144 | } 145 | }, 146 | resolve (source, args) { 147 | return Db.models.user.findById(args.userId).then( user => { 148 | return user.createPost({ 149 | title: args.title, 150 | content: args.content 151 | }); 152 | }); 153 | } 154 | } 155 | }; 156 | } 157 | }); 158 | 159 | const Schema = new GraphQLSchema({ 160 | query: Query, 161 | mutation: Mutation 162 | }); 163 | 164 | export default Schema; 165 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import Express from 'express'; 2 | import GraphHTTP from 'express-graphql'; 3 | import Schema from './schema'; 4 | 5 | // Config 6 | const APP_PORT = 3000; 7 | 8 | // Start 9 | const app = Express(); 10 | 11 | // GraphQL 12 | app.use('/graphql', GraphHTTP({ 13 | schema: Schema, 14 | pretty: true, 15 | graphiql: true 16 | })); 17 | 18 | app.listen(APP_PORT, ()=> { 19 | console.log(`App listening on port ${APP_PORT}`); 20 | }); 21 | --------------------------------------------------------------------------------