├── .babelrc ├── .editorconfig ├── .env-sample ├── .eslintrc.js ├── .flowconfig ├── .gitignore ├── .yarnclean ├── README.md ├── package.json ├── src ├── __tests__ │ ├── index.js │ └── schemas.js ├── config.js ├── database.js ├── entity │ └── Cat.js ├── genSchema.js ├── index.js ├── loaders │ ├── CatLoader.js │ └── index.js ├── models │ ├── Cat.js │ └── index.js ├── mutations │ ├── AddCat.js │ ├── RemoveCat.js │ ├── __tests__ │ │ └── AddCat.js │ └── index.js ├── queries │ ├── AllCats.js │ ├── Cat.js │ └── index.js ├── schema.js ├── subscriptions │ ├── NewCat.js │ ├── RemovedCat.js │ └── index.js └── types │ ├── Cat.js │ └── GraphqlContextType.js ├── test ├── helper.js └── mongodb.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["latest", "stage-2", "flow"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | [package.json] 6 | indent_style = space 7 | indent_size = 2 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | 12 | [COMMIT_EDITMSG] 13 | trim_trailing_whitespace = false 14 | 15 | [*.js] 16 | curly_bracket_next_line = false 17 | spaces_around_operators = true 18 | indent_brace_style = 1TBS 19 | spaces_around_brackets = both 20 | end_of_line = lf 21 | indent_size = 2 22 | indent_style = space 23 | charset = utf-8 24 | trim_trailing_whitespace = true 25 | insert_final_newline = true 26 | -------------------------------------------------------------------------------- /.env-sample: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | 3 | MONGO_URI="mongodb://localhost:27017/" 4 | MONGO_DATABASE_NAME="cats" 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "airbnb-base", 3 | "plugins": [ 4 | "import" 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env* 15 | !.env-sample 16 | 17 | npm-debug.log* 18 | yarn-debug.log* 19 | yarn-error.log* 20 | -------------------------------------------------------------------------------- /.yarnclean: -------------------------------------------------------------------------------- 1 | # test directories 2 | __tests__ 3 | test 4 | tests 5 | powered-test 6 | 7 | # asset directories 8 | docs 9 | doc 10 | website 11 | images 12 | assets 13 | !istanbul-reports/lib/html/assets 14 | 15 | # examples 16 | example 17 | examples 18 | 19 | # code coverage directories 20 | coverage 21 | .nyc_output 22 | 23 | # build scripts 24 | Makefile 25 | Gulpfile.js 26 | Gruntfile.js 27 | 28 | # configs 29 | appveyor.yml 30 | circle.yml 31 | codeship-services.yml 32 | codeship-steps.yml 33 | wercker.yml 34 | .tern-project 35 | .gitattributes 36 | .editorconfig 37 | .*ignore 38 | .eslintrc 39 | .jshintrc 40 | .flowconfig 41 | .documentup.json 42 | .yarn-metadata.json 43 | .travis.yml 44 | 45 | # misc 46 | *.md 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Graphql Subscription server 2 | 3 | Simple and super scalable GraphQL Subscriptions server with MongoDB as a database. As server middleware has been used [Graphcool Playground](https://github.com/graphcool/graphql-playground) which gives automatic schema reloading and better support for GraphQL Subscriptions. 4 | 5 | ## How it works? 6 | 7 | ![GraphQL Subscriptions](https://media.giphy.com/media/wK2PEoXM41oROSBBc6/giphy.gif) 8 | 9 | ## Before you start 10 | First rename `.env-sample` file to `.env`. It contains all default values for proper work on your local machine. In case you going to run GraphQL server in production you need to provide relevant MongoDB URL and database name. 11 | ``` 12 | PORT=8080 13 | 14 | MONGO_URI="mongodb://localhost:27017/" 15 | MONGO_DATABASE_NAME="cats" 16 | ``` 17 | 18 | ## Start your server 19 | ``` 20 | yarn install 21 | yarn start 22 | ``` 23 | That's it. Your server is up and run. 24 | 25 | ## Queries 26 | 27 | Get list of all cats 28 | ``` 29 | query allCats { 30 | allCats { 31 | edges { 32 | node { 33 | id 34 | name 35 | nickName 36 | description 37 | createdAt 38 | avatarUrl 39 | age 40 | } 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | Get information about exact cat: 47 | ``` 48 | query Cat($id: String!) { 49 | cat(id: $id) { 50 | id 51 | name 52 | nickName 53 | description 54 | createdAt 55 | avatarUrl 56 | age 57 | } 58 | } 59 | ``` 60 | 61 | ## Mutations 62 | Add a cat: 63 | ``` 64 | mutation addCat($name: String!, $nickName: String!, $description: String!, $avatarUrl: String!, $age: Int!) { 65 | addCat(name: $name, nickName: $nickName, description: $description, avatarUrl: $avatarUrl, age: $age) { 66 | id 67 | name 68 | nickName 69 | description 70 | createdAt 71 | avatarUrl 72 | age 73 | } 74 | } 75 | ``` 76 | Remove a cat: 77 | ``` 78 | mutation removeCat($id: String!) { 79 | removeCat(id: $id) { 80 | id 81 | } 82 | } 83 | ``` 84 | 85 | ## Subscriptions 86 | 87 | Listens if new cat was added: 88 | ``` 89 | subscription newCat { 90 | newCat { 91 | id 92 | name 93 | nickName 94 | description 95 | createdAt 96 | avatarUrl 97 | age 98 | } 99 | } 100 | ``` 101 | 102 | Listens if cat was removed: 103 | ``` 104 | subscription removedCat { 105 | removedCat { 106 | id 107 | } 108 | } 109 | 110 | ``` 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-graphql-server", 3 | "description": "Simple GraphQL Server with Subscriptions in Express", 4 | "version": "1.0.0", 5 | "main": "src/index.js", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "babel-cli": "^6.26.0", 9 | "babel-jest": "^23.0.0", 10 | "babel-preset-flow": "^6.23.0", 11 | "babel-preset-latest": "^6.24.1", 12 | "babel-preset-stage-2": "^6.24.1", 13 | "eslint": "^4.19.1", 14 | "eslint-config-airbnb-base": "^12.1.0", 15 | "eslint-plugin-import": "^2.12.0", 16 | "flow-bin": "^0.73.0", 17 | "jest": "^23.0.0", 18 | "jest-environment-node": "^23.0.0", 19 | "nodemon": "^1.17.5", 20 | "prettier": "^1.12.1", 21 | "mongodb-memory-server": "^1.7.4", 22 | "graphql-request": "^1.6.0" 23 | }, 24 | "dependencies": { 25 | "@entria/graphql-mongoose-loader": "^1.8.1", 26 | "dataloader": "^1.4.0", 27 | "dotenv": "^5.0.1", 28 | "graphql": "^0.13.2", 29 | "graphql-iso-date": "^3.5.0", 30 | "graphql-relay": "^0.5.5", 31 | "graphql-yoga": "^1.14.5", 32 | "mongoose": "^5.1.2" 33 | }, 34 | "scripts": { 35 | "start": "nodemon --exec babel-node src/index.js", 36 | "prettier": 37 | "prettier --single-quote --trailing-comma all --write 'src/**/*.js'", 38 | "flow": "flow check --color=always", 39 | "test": "jest", 40 | "test:watch": "jest --watch" 41 | }, 42 | "jest": { 43 | "testEnvironment": "/test/mongodb", 44 | "testPathIgnorePatterns": ["/node_modules/", "./dist"], 45 | "coverageReporters": ["lcov", "html"], 46 | "coverageDirectory": "./coverage/", 47 | "resetModules": true 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import { request } from 'graphql-request' 2 | import { 3 | connectMongoose, 4 | clearDatabase, 5 | disconnectMongoose 6 | } from '../../test/helper' 7 | 8 | const query = `{ 9 | allCats(first: 1) { 10 | count 11 | edges { 12 | node { 13 | name 14 | id 15 | createdAt 16 | updatedAt 17 | avatarUrl 18 | } 19 | } 20 | } 21 | }` 22 | 23 | beforeAll(connectMongoose) 24 | beforeEach(clearDatabase) 25 | afterAll(disconnectMongoose) 26 | 27 | describe('Test the root path', () => { 28 | test('adds 1 + 2 to equal 3', async () => { 29 | const response = await request('http://localhost:8080/', query) 30 | expect(response).toEqual({ allCats: { count: 0, edges: [] } }) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/__tests__/schemas.js: -------------------------------------------------------------------------------- 1 | import { addMockFunctionsToSchema, mockServer } from 'graphql-tools' 2 | import { graphql, buildClientSchema } from 'graphql' 3 | import { schema } from '../schema' 4 | 5 | const testCaseA = { 6 | id: 'queryType', 7 | query: ` 8 | query { 9 | __schema { 10 | queryType { 11 | name 12 | } 13 | } 14 | } 15 | `, 16 | variables: {}, 17 | context: {}, 18 | expected: { data: { __schema: { queryType: { name: 'RootQuery' } } } } 19 | } 20 | 21 | describe('Schema', () => { 22 | // Array of case types 23 | const cases = [testCaseA] 24 | addMockFunctionsToSchema({ 25 | schema 26 | }) 27 | 28 | test('has valid type definitions', async () => { 29 | expect(async () => { 30 | const MockServer = mockServer(schema) 31 | 32 | await MockServer.query(`{ __schema { types { name } } }`) 33 | }).not.toThrow() 34 | }) 35 | 36 | cases.forEach(obj => { 37 | const { id, query, variables, context: ctx, expected } = obj 38 | 39 | test(`query: ${id}`, async () => { 40 | return await expect( 41 | graphql(schema, query, null, { ctx }, variables) 42 | ).resolves.toEqual(expected) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /src/config.js: -------------------------------------------------------------------------------- 1 | import config from 'dotenv'; 2 | import { PubSub } from 'graphql-yoga'; 3 | import * as loaders from './loaders'; 4 | 5 | config.config(); 6 | 7 | export const dataloaders = Object.keys(loaders).reduce( 8 | (dataloaders, loaderKey) => ({ 9 | ...dataloaders, 10 | [loaderKey]: loaders[loaderKey].getLoader(), 11 | }), 12 | {} 13 | ); 14 | 15 | export const pubsub = new PubSub(); 16 | export const PORT = process.env.PORT || 8080; 17 | export const BASE_URI = process.env.BASE_URI || `http://localhost:${PORT}`; 18 | export const WS_BASE_URI = 19 | process.env.WS_BAS_URI || BASE_URI.replace(/^https?/, 'ws'); 20 | export const MONGO_URI = process.env.MONGO_URI; 21 | export const MONGO_DATABASE_NAME = 22 | process.env.NODE_ENV === 'test' ? 'test' : process.env.MONGO_DATABASE_NAME; 23 | -------------------------------------------------------------------------------- /src/database.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import mongoose from 'mongoose' 4 | 5 | import { MONGO_URI, MONGO_DATABASE_NAME } from './config' 6 | 7 | export const connectDatabase = () => { 8 | return new Promise((resolve, reject) => { 9 | mongoose.Promise = global.Promise 10 | mongoose.connection 11 | .on('error', error => reject(error)) 12 | .on('close', () => console.log('Database connection closed.')) 13 | .on('open', () => console.log('Database connection openned.')) 14 | .once('open', () => resolve(mongoose.connections[0])) 15 | 16 | mongoose.connect(`${MONGO_URI}${MONGO_DATABASE_NAME}`, { autoIndex: false }) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /src/entity/Cat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { 4 | GraphQLNonNull, 5 | GraphQLID, 6 | GraphQLObjectType, 7 | GraphQLString, 8 | GraphQLInt 9 | } from 'graphql' 10 | import { GraphQLDateTime } from 'graphql-iso-date' 11 | 12 | export default new GraphQLObjectType({ 13 | name: 'Cat', 14 | fields: { 15 | id: { 16 | type: new GraphQLNonNull(GraphQLID) 17 | }, 18 | name: { 19 | type: new GraphQLNonNull(GraphQLString) 20 | }, 21 | nickName: { 22 | type: new GraphQLNonNull(GraphQLString) 23 | }, 24 | description: { 25 | type: new GraphQLNonNull(GraphQLString) 26 | }, 27 | createdAt: { 28 | type: new GraphQLNonNull(GraphQLDateTime) 29 | }, 30 | updatedAt: { 31 | type: GraphQLDateTime 32 | }, 33 | avatarUrl: { 34 | type: new GraphQLNonNull(GraphQLString) 35 | }, 36 | age: { 37 | type: new GraphQLNonNull(GraphQLInt) 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /src/genSchema.js: -------------------------------------------------------------------------------- 1 | export const genSchema = { 2 | "data": { 3 | "__schema": { 4 | "queryType": { 5 | "name": "RootQuery" 6 | }, 7 | "mutationType": { 8 | "name": "RootMutation" 9 | }, 10 | "subscriptionType": { 11 | "name": "RootSubscription" 12 | }, 13 | "types": [ 14 | { 15 | "kind": "OBJECT", 16 | "name": "RootQuery", 17 | "description": "Root Query", 18 | "fields": [ 19 | { 20 | "name": "cat", 21 | "description": null, 22 | "args": [ 23 | { 24 | "name": "id", 25 | "description": null, 26 | "type": { 27 | "kind": "SCALAR", 28 | "name": "ID", 29 | "ofType": null 30 | }, 31 | "defaultValue": null 32 | } 33 | ], 34 | "type": { 35 | "kind": "OBJECT", 36 | "name": "Cat", 37 | "ofType": null 38 | }, 39 | "isDeprecated": false, 40 | "deprecationReason": null 41 | }, 42 | { 43 | "name": "allCats", 44 | "description": null, 45 | "args": [ 46 | { 47 | "name": "after", 48 | "description": null, 49 | "type": { 50 | "kind": "SCALAR", 51 | "name": "String", 52 | "ofType": null 53 | }, 54 | "defaultValue": null 55 | }, 56 | { 57 | "name": "first", 58 | "description": null, 59 | "type": { 60 | "kind": "SCALAR", 61 | "name": "Int", 62 | "ofType": null 63 | }, 64 | "defaultValue": null 65 | }, 66 | { 67 | "name": "before", 68 | "description": null, 69 | "type": { 70 | "kind": "SCALAR", 71 | "name": "String", 72 | "ofType": null 73 | }, 74 | "defaultValue": null 75 | }, 76 | { 77 | "name": "last", 78 | "description": null, 79 | "type": { 80 | "kind": "SCALAR", 81 | "name": "Int", 82 | "ofType": null 83 | }, 84 | "defaultValue": null 85 | } 86 | ], 87 | "type": { 88 | "kind": "OBJECT", 89 | "name": "CatConnection", 90 | "ofType": null 91 | }, 92 | "isDeprecated": false, 93 | "deprecationReason": null 94 | } 95 | ], 96 | "inputFields": null, 97 | "interfaces": [], 98 | "enumValues": null, 99 | "possibleTypes": null 100 | }, 101 | { 102 | "kind": "SCALAR", 103 | "name": "ID", 104 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", 105 | "fields": null, 106 | "inputFields": null, 107 | "interfaces": null, 108 | "enumValues": null, 109 | "possibleTypes": null 110 | }, 111 | { 112 | "kind": "OBJECT", 113 | "name": "Cat", 114 | "description": null, 115 | "fields": [ 116 | { 117 | "name": "id", 118 | "description": null, 119 | "args": [], 120 | "type": { 121 | "kind": "NON_NULL", 122 | "name": null, 123 | "ofType": { 124 | "kind": "SCALAR", 125 | "name": "ID", 126 | "ofType": null 127 | } 128 | }, 129 | "isDeprecated": false, 130 | "deprecationReason": null 131 | }, 132 | { 133 | "name": "name", 134 | "description": null, 135 | "args": [], 136 | "type": { 137 | "kind": "NON_NULL", 138 | "name": null, 139 | "ofType": { 140 | "kind": "SCALAR", 141 | "name": "String", 142 | "ofType": null 143 | } 144 | }, 145 | "isDeprecated": false, 146 | "deprecationReason": null 147 | }, 148 | { 149 | "name": "nickName", 150 | "description": null, 151 | "args": [], 152 | "type": { 153 | "kind": "NON_NULL", 154 | "name": null, 155 | "ofType": { 156 | "kind": "SCALAR", 157 | "name": "String", 158 | "ofType": null 159 | } 160 | }, 161 | "isDeprecated": false, 162 | "deprecationReason": null 163 | }, 164 | { 165 | "name": "description", 166 | "description": null, 167 | "args": [], 168 | "type": { 169 | "kind": "NON_NULL", 170 | "name": null, 171 | "ofType": { 172 | "kind": "SCALAR", 173 | "name": "String", 174 | "ofType": null 175 | } 176 | }, 177 | "isDeprecated": false, 178 | "deprecationReason": null 179 | }, 180 | { 181 | "name": "createdAt", 182 | "description": null, 183 | "args": [], 184 | "type": { 185 | "kind": "NON_NULL", 186 | "name": null, 187 | "ofType": { 188 | "kind": "SCALAR", 189 | "name": "DateTime", 190 | "ofType": null 191 | } 192 | }, 193 | "isDeprecated": false, 194 | "deprecationReason": null 195 | }, 196 | { 197 | "name": "updatedAt", 198 | "description": null, 199 | "args": [], 200 | "type": { 201 | "kind": "SCALAR", 202 | "name": "DateTime", 203 | "ofType": null 204 | }, 205 | "isDeprecated": false, 206 | "deprecationReason": null 207 | }, 208 | { 209 | "name": "avatarUrl", 210 | "description": null, 211 | "args": [], 212 | "type": { 213 | "kind": "NON_NULL", 214 | "name": null, 215 | "ofType": { 216 | "kind": "SCALAR", 217 | "name": "String", 218 | "ofType": null 219 | } 220 | }, 221 | "isDeprecated": false, 222 | "deprecationReason": null 223 | }, 224 | { 225 | "name": "age", 226 | "description": null, 227 | "args": [], 228 | "type": { 229 | "kind": "NON_NULL", 230 | "name": null, 231 | "ofType": { 232 | "kind": "SCALAR", 233 | "name": "Int", 234 | "ofType": null 235 | } 236 | }, 237 | "isDeprecated": false, 238 | "deprecationReason": null 239 | } 240 | ], 241 | "inputFields": null, 242 | "interfaces": [], 243 | "enumValues": null, 244 | "possibleTypes": null 245 | }, 246 | { 247 | "kind": "SCALAR", 248 | "name": "String", 249 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", 250 | "fields": null, 251 | "inputFields": null, 252 | "interfaces": null, 253 | "enumValues": null, 254 | "possibleTypes": null 255 | }, 256 | { 257 | "kind": "SCALAR", 258 | "name": "DateTime", 259 | "description": "A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.", 260 | "fields": null, 261 | "inputFields": null, 262 | "interfaces": null, 263 | "enumValues": null, 264 | "possibleTypes": null 265 | }, 266 | { 267 | "kind": "SCALAR", 268 | "name": "Int", 269 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", 270 | "fields": null, 271 | "inputFields": null, 272 | "interfaces": null, 273 | "enumValues": null, 274 | "possibleTypes": null 275 | }, 276 | { 277 | "kind": "OBJECT", 278 | "name": "CatConnection", 279 | "description": "A connection to a list of items.", 280 | "fields": [ 281 | { 282 | "name": "pageInfo", 283 | "description": "Information to aid in pagination.", 284 | "args": [], 285 | "type": { 286 | "kind": "NON_NULL", 287 | "name": null, 288 | "ofType": { 289 | "kind": "OBJECT", 290 | "name": "PageInfo", 291 | "ofType": null 292 | } 293 | }, 294 | "isDeprecated": false, 295 | "deprecationReason": null 296 | }, 297 | { 298 | "name": "edges", 299 | "description": "A list of edges.", 300 | "args": [], 301 | "type": { 302 | "kind": "LIST", 303 | "name": null, 304 | "ofType": { 305 | "kind": "OBJECT", 306 | "name": "CatEdge", 307 | "ofType": null 308 | } 309 | }, 310 | "isDeprecated": false, 311 | "deprecationReason": null 312 | }, 313 | { 314 | "name": "count", 315 | "description": null, 316 | "args": [], 317 | "type": { 318 | "kind": "SCALAR", 319 | "name": "Int", 320 | "ofType": null 321 | }, 322 | "isDeprecated": false, 323 | "deprecationReason": null 324 | } 325 | ], 326 | "inputFields": null, 327 | "interfaces": [], 328 | "enumValues": null, 329 | "possibleTypes": null 330 | }, 331 | { 332 | "kind": "OBJECT", 333 | "name": "PageInfo", 334 | "description": "Information about pagination in a connection.", 335 | "fields": [ 336 | { 337 | "name": "hasNextPage", 338 | "description": "When paginating forwards, are there more items?", 339 | "args": [], 340 | "type": { 341 | "kind": "NON_NULL", 342 | "name": null, 343 | "ofType": { 344 | "kind": "SCALAR", 345 | "name": "Boolean", 346 | "ofType": null 347 | } 348 | }, 349 | "isDeprecated": false, 350 | "deprecationReason": null 351 | }, 352 | { 353 | "name": "hasPreviousPage", 354 | "description": "When paginating backwards, are there more items?", 355 | "args": [], 356 | "type": { 357 | "kind": "NON_NULL", 358 | "name": null, 359 | "ofType": { 360 | "kind": "SCALAR", 361 | "name": "Boolean", 362 | "ofType": null 363 | } 364 | }, 365 | "isDeprecated": false, 366 | "deprecationReason": null 367 | }, 368 | { 369 | "name": "startCursor", 370 | "description": "When paginating backwards, the cursor to continue.", 371 | "args": [], 372 | "type": { 373 | "kind": "SCALAR", 374 | "name": "String", 375 | "ofType": null 376 | }, 377 | "isDeprecated": false, 378 | "deprecationReason": null 379 | }, 380 | { 381 | "name": "endCursor", 382 | "description": "When paginating forwards, the cursor to continue.", 383 | "args": [], 384 | "type": { 385 | "kind": "SCALAR", 386 | "name": "String", 387 | "ofType": null 388 | }, 389 | "isDeprecated": false, 390 | "deprecationReason": null 391 | } 392 | ], 393 | "inputFields": null, 394 | "interfaces": [], 395 | "enumValues": null, 396 | "possibleTypes": null 397 | }, 398 | { 399 | "kind": "SCALAR", 400 | "name": "Boolean", 401 | "description": "The `Boolean` scalar type represents `true` or `false`.", 402 | "fields": null, 403 | "inputFields": null, 404 | "interfaces": null, 405 | "enumValues": null, 406 | "possibleTypes": null 407 | }, 408 | { 409 | "kind": "OBJECT", 410 | "name": "CatEdge", 411 | "description": "An edge in a connection.", 412 | "fields": [ 413 | { 414 | "name": "node", 415 | "description": "The item at the end of the edge", 416 | "args": [], 417 | "type": { 418 | "kind": "OBJECT", 419 | "name": "Cat", 420 | "ofType": null 421 | }, 422 | "isDeprecated": false, 423 | "deprecationReason": null 424 | }, 425 | { 426 | "name": "cursor", 427 | "description": "A cursor for use in pagination", 428 | "args": [], 429 | "type": { 430 | "kind": "NON_NULL", 431 | "name": null, 432 | "ofType": { 433 | "kind": "SCALAR", 434 | "name": "String", 435 | "ofType": null 436 | } 437 | }, 438 | "isDeprecated": false, 439 | "deprecationReason": null 440 | } 441 | ], 442 | "inputFields": null, 443 | "interfaces": [], 444 | "enumValues": null, 445 | "possibleTypes": null 446 | }, 447 | { 448 | "kind": "OBJECT", 449 | "name": "RootMutation", 450 | "description": "Root Mutation", 451 | "fields": [ 452 | { 453 | "name": "addCat", 454 | "description": null, 455 | "args": [ 456 | { 457 | "name": "name", 458 | "description": null, 459 | "type": { 460 | "kind": "NON_NULL", 461 | "name": null, 462 | "ofType": { 463 | "kind": "SCALAR", 464 | "name": "String", 465 | "ofType": null 466 | } 467 | }, 468 | "defaultValue": null 469 | }, 470 | { 471 | "name": "nickName", 472 | "description": null, 473 | "type": { 474 | "kind": "NON_NULL", 475 | "name": null, 476 | "ofType": { 477 | "kind": "SCALAR", 478 | "name": "String", 479 | "ofType": null 480 | } 481 | }, 482 | "defaultValue": null 483 | }, 484 | { 485 | "name": "description", 486 | "description": null, 487 | "type": { 488 | "kind": "NON_NULL", 489 | "name": null, 490 | "ofType": { 491 | "kind": "SCALAR", 492 | "name": "String", 493 | "ofType": null 494 | } 495 | }, 496 | "defaultValue": null 497 | }, 498 | { 499 | "name": "avatarUrl", 500 | "description": null, 501 | "type": { 502 | "kind": "NON_NULL", 503 | "name": null, 504 | "ofType": { 505 | "kind": "SCALAR", 506 | "name": "String", 507 | "ofType": null 508 | } 509 | }, 510 | "defaultValue": null 511 | }, 512 | { 513 | "name": "age", 514 | "description": null, 515 | "type": { 516 | "kind": "NON_NULL", 517 | "name": null, 518 | "ofType": { 519 | "kind": "SCALAR", 520 | "name": "Int", 521 | "ofType": null 522 | } 523 | }, 524 | "defaultValue": null 525 | } 526 | ], 527 | "type": { 528 | "kind": "OBJECT", 529 | "name": "Cat", 530 | "ofType": null 531 | }, 532 | "isDeprecated": false, 533 | "deprecationReason": null 534 | }, 535 | { 536 | "name": "removeCat", 537 | "description": null, 538 | "args": [ 539 | { 540 | "name": "id", 541 | "description": null, 542 | "type": { 543 | "kind": "NON_NULL", 544 | "name": null, 545 | "ofType": { 546 | "kind": "SCALAR", 547 | "name": "ID", 548 | "ofType": null 549 | } 550 | }, 551 | "defaultValue": null 552 | } 553 | ], 554 | "type": { 555 | "kind": "OBJECT", 556 | "name": "Cat", 557 | "ofType": null 558 | }, 559 | "isDeprecated": false, 560 | "deprecationReason": null 561 | } 562 | ], 563 | "inputFields": null, 564 | "interfaces": [], 565 | "enumValues": null, 566 | "possibleTypes": null 567 | }, 568 | { 569 | "kind": "OBJECT", 570 | "name": "RootSubscription", 571 | "description": "Root Subscription", 572 | "fields": [ 573 | { 574 | "name": "newCat", 575 | "description": null, 576 | "args": [], 577 | "type": { 578 | "kind": "OBJECT", 579 | "name": "Cat", 580 | "ofType": null 581 | }, 582 | "isDeprecated": false, 583 | "deprecationReason": null 584 | }, 585 | { 586 | "name": "removedCat", 587 | "description": null, 588 | "args": [], 589 | "type": { 590 | "kind": "OBJECT", 591 | "name": "Cat", 592 | "ofType": null 593 | }, 594 | "isDeprecated": false, 595 | "deprecationReason": null 596 | } 597 | ], 598 | "inputFields": null, 599 | "interfaces": [], 600 | "enumValues": null, 601 | "possibleTypes": null 602 | }, 603 | { 604 | "kind": "OBJECT", 605 | "name": "__Schema", 606 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", 607 | "fields": [ 608 | { 609 | "name": "types", 610 | "description": "A list of all types supported by this server.", 611 | "args": [], 612 | "type": { 613 | "kind": "NON_NULL", 614 | "name": null, 615 | "ofType": { 616 | "kind": "LIST", 617 | "name": null, 618 | "ofType": { 619 | "kind": "NON_NULL", 620 | "name": null, 621 | "ofType": { 622 | "kind": "OBJECT", 623 | "name": "__Type", 624 | "ofType": null 625 | } 626 | } 627 | } 628 | }, 629 | "isDeprecated": false, 630 | "deprecationReason": null 631 | }, 632 | { 633 | "name": "queryType", 634 | "description": "The type that query operations will be rooted at.", 635 | "args": [], 636 | "type": { 637 | "kind": "NON_NULL", 638 | "name": null, 639 | "ofType": { 640 | "kind": "OBJECT", 641 | "name": "__Type", 642 | "ofType": null 643 | } 644 | }, 645 | "isDeprecated": false, 646 | "deprecationReason": null 647 | }, 648 | { 649 | "name": "mutationType", 650 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.", 651 | "args": [], 652 | "type": { 653 | "kind": "OBJECT", 654 | "name": "__Type", 655 | "ofType": null 656 | }, 657 | "isDeprecated": false, 658 | "deprecationReason": null 659 | }, 660 | { 661 | "name": "subscriptionType", 662 | "description": "If this server support subscription, the type that subscription operations will be rooted at.", 663 | "args": [], 664 | "type": { 665 | "kind": "OBJECT", 666 | "name": "__Type", 667 | "ofType": null 668 | }, 669 | "isDeprecated": false, 670 | "deprecationReason": null 671 | }, 672 | { 673 | "name": "directives", 674 | "description": "A list of all directives supported by this server.", 675 | "args": [], 676 | "type": { 677 | "kind": "NON_NULL", 678 | "name": null, 679 | "ofType": { 680 | "kind": "LIST", 681 | "name": null, 682 | "ofType": { 683 | "kind": "NON_NULL", 684 | "name": null, 685 | "ofType": { 686 | "kind": "OBJECT", 687 | "name": "__Directive", 688 | "ofType": null 689 | } 690 | } 691 | } 692 | }, 693 | "isDeprecated": false, 694 | "deprecationReason": null 695 | } 696 | ], 697 | "inputFields": null, 698 | "interfaces": [], 699 | "enumValues": null, 700 | "possibleTypes": null 701 | }, 702 | { 703 | "kind": "OBJECT", 704 | "name": "__Type", 705 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", 706 | "fields": [ 707 | { 708 | "name": "kind", 709 | "description": null, 710 | "args": [], 711 | "type": { 712 | "kind": "NON_NULL", 713 | "name": null, 714 | "ofType": { 715 | "kind": "ENUM", 716 | "name": "__TypeKind", 717 | "ofType": null 718 | } 719 | }, 720 | "isDeprecated": false, 721 | "deprecationReason": null 722 | }, 723 | { 724 | "name": "name", 725 | "description": null, 726 | "args": [], 727 | "type": { 728 | "kind": "SCALAR", 729 | "name": "String", 730 | "ofType": null 731 | }, 732 | "isDeprecated": false, 733 | "deprecationReason": null 734 | }, 735 | { 736 | "name": "description", 737 | "description": null, 738 | "args": [], 739 | "type": { 740 | "kind": "SCALAR", 741 | "name": "String", 742 | "ofType": null 743 | }, 744 | "isDeprecated": false, 745 | "deprecationReason": null 746 | }, 747 | { 748 | "name": "fields", 749 | "description": null, 750 | "args": [ 751 | { 752 | "name": "includeDeprecated", 753 | "description": null, 754 | "type": { 755 | "kind": "SCALAR", 756 | "name": "Boolean", 757 | "ofType": null 758 | }, 759 | "defaultValue": "false" 760 | } 761 | ], 762 | "type": { 763 | "kind": "LIST", 764 | "name": null, 765 | "ofType": { 766 | "kind": "NON_NULL", 767 | "name": null, 768 | "ofType": { 769 | "kind": "OBJECT", 770 | "name": "__Field", 771 | "ofType": null 772 | } 773 | } 774 | }, 775 | "isDeprecated": false, 776 | "deprecationReason": null 777 | }, 778 | { 779 | "name": "interfaces", 780 | "description": null, 781 | "args": [], 782 | "type": { 783 | "kind": "LIST", 784 | "name": null, 785 | "ofType": { 786 | "kind": "NON_NULL", 787 | "name": null, 788 | "ofType": { 789 | "kind": "OBJECT", 790 | "name": "__Type", 791 | "ofType": null 792 | } 793 | } 794 | }, 795 | "isDeprecated": false, 796 | "deprecationReason": null 797 | }, 798 | { 799 | "name": "possibleTypes", 800 | "description": null, 801 | "args": [], 802 | "type": { 803 | "kind": "LIST", 804 | "name": null, 805 | "ofType": { 806 | "kind": "NON_NULL", 807 | "name": null, 808 | "ofType": { 809 | "kind": "OBJECT", 810 | "name": "__Type", 811 | "ofType": null 812 | } 813 | } 814 | }, 815 | "isDeprecated": false, 816 | "deprecationReason": null 817 | }, 818 | { 819 | "name": "enumValues", 820 | "description": null, 821 | "args": [ 822 | { 823 | "name": "includeDeprecated", 824 | "description": null, 825 | "type": { 826 | "kind": "SCALAR", 827 | "name": "Boolean", 828 | "ofType": null 829 | }, 830 | "defaultValue": "false" 831 | } 832 | ], 833 | "type": { 834 | "kind": "LIST", 835 | "name": null, 836 | "ofType": { 837 | "kind": "NON_NULL", 838 | "name": null, 839 | "ofType": { 840 | "kind": "OBJECT", 841 | "name": "__EnumValue", 842 | "ofType": null 843 | } 844 | } 845 | }, 846 | "isDeprecated": false, 847 | "deprecationReason": null 848 | }, 849 | { 850 | "name": "inputFields", 851 | "description": null, 852 | "args": [], 853 | "type": { 854 | "kind": "LIST", 855 | "name": null, 856 | "ofType": { 857 | "kind": "NON_NULL", 858 | "name": null, 859 | "ofType": { 860 | "kind": "OBJECT", 861 | "name": "__InputValue", 862 | "ofType": null 863 | } 864 | } 865 | }, 866 | "isDeprecated": false, 867 | "deprecationReason": null 868 | }, 869 | { 870 | "name": "ofType", 871 | "description": null, 872 | "args": [], 873 | "type": { 874 | "kind": "OBJECT", 875 | "name": "__Type", 876 | "ofType": null 877 | }, 878 | "isDeprecated": false, 879 | "deprecationReason": null 880 | } 881 | ], 882 | "inputFields": null, 883 | "interfaces": [], 884 | "enumValues": null, 885 | "possibleTypes": null 886 | }, 887 | { 888 | "kind": "ENUM", 889 | "name": "__TypeKind", 890 | "description": "An enum describing what kind of type a given `__Type` is.", 891 | "fields": null, 892 | "inputFields": null, 893 | "interfaces": null, 894 | "enumValues": [ 895 | { 896 | "name": "SCALAR", 897 | "description": "Indicates this type is a scalar.", 898 | "isDeprecated": false, 899 | "deprecationReason": null 900 | }, 901 | { 902 | "name": "OBJECT", 903 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", 904 | "isDeprecated": false, 905 | "deprecationReason": null 906 | }, 907 | { 908 | "name": "INTERFACE", 909 | "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", 910 | "isDeprecated": false, 911 | "deprecationReason": null 912 | }, 913 | { 914 | "name": "UNION", 915 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.", 916 | "isDeprecated": false, 917 | "deprecationReason": null 918 | }, 919 | { 920 | "name": "ENUM", 921 | "description": "Indicates this type is an enum. `enumValues` is a valid field.", 922 | "isDeprecated": false, 923 | "deprecationReason": null 924 | }, 925 | { 926 | "name": "INPUT_OBJECT", 927 | "description": "Indicates this type is an input object. `inputFields` is a valid field.", 928 | "isDeprecated": false, 929 | "deprecationReason": null 930 | }, 931 | { 932 | "name": "LIST", 933 | "description": "Indicates this type is a list. `ofType` is a valid field.", 934 | "isDeprecated": false, 935 | "deprecationReason": null 936 | }, 937 | { 938 | "name": "NON_NULL", 939 | "description": "Indicates this type is a non-null. `ofType` is a valid field.", 940 | "isDeprecated": false, 941 | "deprecationReason": null 942 | } 943 | ], 944 | "possibleTypes": null 945 | }, 946 | { 947 | "kind": "OBJECT", 948 | "name": "__Field", 949 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", 950 | "fields": [ 951 | { 952 | "name": "name", 953 | "description": null, 954 | "args": [], 955 | "type": { 956 | "kind": "NON_NULL", 957 | "name": null, 958 | "ofType": { 959 | "kind": "SCALAR", 960 | "name": "String", 961 | "ofType": null 962 | } 963 | }, 964 | "isDeprecated": false, 965 | "deprecationReason": null 966 | }, 967 | { 968 | "name": "description", 969 | "description": null, 970 | "args": [], 971 | "type": { 972 | "kind": "SCALAR", 973 | "name": "String", 974 | "ofType": null 975 | }, 976 | "isDeprecated": false, 977 | "deprecationReason": null 978 | }, 979 | { 980 | "name": "args", 981 | "description": null, 982 | "args": [], 983 | "type": { 984 | "kind": "NON_NULL", 985 | "name": null, 986 | "ofType": { 987 | "kind": "LIST", 988 | "name": null, 989 | "ofType": { 990 | "kind": "NON_NULL", 991 | "name": null, 992 | "ofType": { 993 | "kind": "OBJECT", 994 | "name": "__InputValue", 995 | "ofType": null 996 | } 997 | } 998 | } 999 | }, 1000 | "isDeprecated": false, 1001 | "deprecationReason": null 1002 | }, 1003 | { 1004 | "name": "type", 1005 | "description": null, 1006 | "args": [], 1007 | "type": { 1008 | "kind": "NON_NULL", 1009 | "name": null, 1010 | "ofType": { 1011 | "kind": "OBJECT", 1012 | "name": "__Type", 1013 | "ofType": null 1014 | } 1015 | }, 1016 | "isDeprecated": false, 1017 | "deprecationReason": null 1018 | }, 1019 | { 1020 | "name": "isDeprecated", 1021 | "description": null, 1022 | "args": [], 1023 | "type": { 1024 | "kind": "NON_NULL", 1025 | "name": null, 1026 | "ofType": { 1027 | "kind": "SCALAR", 1028 | "name": "Boolean", 1029 | "ofType": null 1030 | } 1031 | }, 1032 | "isDeprecated": false, 1033 | "deprecationReason": null 1034 | }, 1035 | { 1036 | "name": "deprecationReason", 1037 | "description": null, 1038 | "args": [], 1039 | "type": { 1040 | "kind": "SCALAR", 1041 | "name": "String", 1042 | "ofType": null 1043 | }, 1044 | "isDeprecated": false, 1045 | "deprecationReason": null 1046 | } 1047 | ], 1048 | "inputFields": null, 1049 | "interfaces": [], 1050 | "enumValues": null, 1051 | "possibleTypes": null 1052 | }, 1053 | { 1054 | "kind": "OBJECT", 1055 | "name": "__InputValue", 1056 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", 1057 | "fields": [ 1058 | { 1059 | "name": "name", 1060 | "description": null, 1061 | "args": [], 1062 | "type": { 1063 | "kind": "NON_NULL", 1064 | "name": null, 1065 | "ofType": { 1066 | "kind": "SCALAR", 1067 | "name": "String", 1068 | "ofType": null 1069 | } 1070 | }, 1071 | "isDeprecated": false, 1072 | "deprecationReason": null 1073 | }, 1074 | { 1075 | "name": "description", 1076 | "description": null, 1077 | "args": [], 1078 | "type": { 1079 | "kind": "SCALAR", 1080 | "name": "String", 1081 | "ofType": null 1082 | }, 1083 | "isDeprecated": false, 1084 | "deprecationReason": null 1085 | }, 1086 | { 1087 | "name": "type", 1088 | "description": null, 1089 | "args": [], 1090 | "type": { 1091 | "kind": "NON_NULL", 1092 | "name": null, 1093 | "ofType": { 1094 | "kind": "OBJECT", 1095 | "name": "__Type", 1096 | "ofType": null 1097 | } 1098 | }, 1099 | "isDeprecated": false, 1100 | "deprecationReason": null 1101 | }, 1102 | { 1103 | "name": "defaultValue", 1104 | "description": "A GraphQL-formatted string representing the default value for this input value.", 1105 | "args": [], 1106 | "type": { 1107 | "kind": "SCALAR", 1108 | "name": "String", 1109 | "ofType": null 1110 | }, 1111 | "isDeprecated": false, 1112 | "deprecationReason": null 1113 | } 1114 | ], 1115 | "inputFields": null, 1116 | "interfaces": [], 1117 | "enumValues": null, 1118 | "possibleTypes": null 1119 | }, 1120 | { 1121 | "kind": "OBJECT", 1122 | "name": "__EnumValue", 1123 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", 1124 | "fields": [ 1125 | { 1126 | "name": "name", 1127 | "description": null, 1128 | "args": [], 1129 | "type": { 1130 | "kind": "NON_NULL", 1131 | "name": null, 1132 | "ofType": { 1133 | "kind": "SCALAR", 1134 | "name": "String", 1135 | "ofType": null 1136 | } 1137 | }, 1138 | "isDeprecated": false, 1139 | "deprecationReason": null 1140 | }, 1141 | { 1142 | "name": "description", 1143 | "description": null, 1144 | "args": [], 1145 | "type": { 1146 | "kind": "SCALAR", 1147 | "name": "String", 1148 | "ofType": null 1149 | }, 1150 | "isDeprecated": false, 1151 | "deprecationReason": null 1152 | }, 1153 | { 1154 | "name": "isDeprecated", 1155 | "description": null, 1156 | "args": [], 1157 | "type": { 1158 | "kind": "NON_NULL", 1159 | "name": null, 1160 | "ofType": { 1161 | "kind": "SCALAR", 1162 | "name": "Boolean", 1163 | "ofType": null 1164 | } 1165 | }, 1166 | "isDeprecated": false, 1167 | "deprecationReason": null 1168 | }, 1169 | { 1170 | "name": "deprecationReason", 1171 | "description": null, 1172 | "args": [], 1173 | "type": { 1174 | "kind": "SCALAR", 1175 | "name": "String", 1176 | "ofType": null 1177 | }, 1178 | "isDeprecated": false, 1179 | "deprecationReason": null 1180 | } 1181 | ], 1182 | "inputFields": null, 1183 | "interfaces": [], 1184 | "enumValues": null, 1185 | "possibleTypes": null 1186 | }, 1187 | { 1188 | "kind": "OBJECT", 1189 | "name": "__Directive", 1190 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", 1191 | "fields": [ 1192 | { 1193 | "name": "name", 1194 | "description": null, 1195 | "args": [], 1196 | "type": { 1197 | "kind": "NON_NULL", 1198 | "name": null, 1199 | "ofType": { 1200 | "kind": "SCALAR", 1201 | "name": "String", 1202 | "ofType": null 1203 | } 1204 | }, 1205 | "isDeprecated": false, 1206 | "deprecationReason": null 1207 | }, 1208 | { 1209 | "name": "description", 1210 | "description": null, 1211 | "args": [], 1212 | "type": { 1213 | "kind": "SCALAR", 1214 | "name": "String", 1215 | "ofType": null 1216 | }, 1217 | "isDeprecated": false, 1218 | "deprecationReason": null 1219 | }, 1220 | { 1221 | "name": "locations", 1222 | "description": null, 1223 | "args": [], 1224 | "type": { 1225 | "kind": "NON_NULL", 1226 | "name": null, 1227 | "ofType": { 1228 | "kind": "LIST", 1229 | "name": null, 1230 | "ofType": { 1231 | "kind": "NON_NULL", 1232 | "name": null, 1233 | "ofType": { 1234 | "kind": "ENUM", 1235 | "name": "__DirectiveLocation", 1236 | "ofType": null 1237 | } 1238 | } 1239 | } 1240 | }, 1241 | "isDeprecated": false, 1242 | "deprecationReason": null 1243 | }, 1244 | { 1245 | "name": "args", 1246 | "description": null, 1247 | "args": [], 1248 | "type": { 1249 | "kind": "NON_NULL", 1250 | "name": null, 1251 | "ofType": { 1252 | "kind": "LIST", 1253 | "name": null, 1254 | "ofType": { 1255 | "kind": "NON_NULL", 1256 | "name": null, 1257 | "ofType": { 1258 | "kind": "OBJECT", 1259 | "name": "__InputValue", 1260 | "ofType": null 1261 | } 1262 | } 1263 | } 1264 | }, 1265 | "isDeprecated": false, 1266 | "deprecationReason": null 1267 | }, 1268 | { 1269 | "name": "onOperation", 1270 | "description": null, 1271 | "args": [], 1272 | "type": { 1273 | "kind": "NON_NULL", 1274 | "name": null, 1275 | "ofType": { 1276 | "kind": "SCALAR", 1277 | "name": "Boolean", 1278 | "ofType": null 1279 | } 1280 | }, 1281 | "isDeprecated": true, 1282 | "deprecationReason": "Use `locations`." 1283 | }, 1284 | { 1285 | "name": "onFragment", 1286 | "description": null, 1287 | "args": [], 1288 | "type": { 1289 | "kind": "NON_NULL", 1290 | "name": null, 1291 | "ofType": { 1292 | "kind": "SCALAR", 1293 | "name": "Boolean", 1294 | "ofType": null 1295 | } 1296 | }, 1297 | "isDeprecated": true, 1298 | "deprecationReason": "Use `locations`." 1299 | }, 1300 | { 1301 | "name": "onField", 1302 | "description": null, 1303 | "args": [], 1304 | "type": { 1305 | "kind": "NON_NULL", 1306 | "name": null, 1307 | "ofType": { 1308 | "kind": "SCALAR", 1309 | "name": "Boolean", 1310 | "ofType": null 1311 | } 1312 | }, 1313 | "isDeprecated": true, 1314 | "deprecationReason": "Use `locations`." 1315 | } 1316 | ], 1317 | "inputFields": null, 1318 | "interfaces": [], 1319 | "enumValues": null, 1320 | "possibleTypes": null 1321 | }, 1322 | { 1323 | "kind": "ENUM", 1324 | "name": "__DirectiveLocation", 1325 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", 1326 | "fields": null, 1327 | "inputFields": null, 1328 | "interfaces": null, 1329 | "enumValues": [ 1330 | { 1331 | "name": "QUERY", 1332 | "description": "Location adjacent to a query operation.", 1333 | "isDeprecated": false, 1334 | "deprecationReason": null 1335 | }, 1336 | { 1337 | "name": "MUTATION", 1338 | "description": "Location adjacent to a mutation operation.", 1339 | "isDeprecated": false, 1340 | "deprecationReason": null 1341 | }, 1342 | { 1343 | "name": "SUBSCRIPTION", 1344 | "description": "Location adjacent to a subscription operation.", 1345 | "isDeprecated": false, 1346 | "deprecationReason": null 1347 | }, 1348 | { 1349 | "name": "FIELD", 1350 | "description": "Location adjacent to a field.", 1351 | "isDeprecated": false, 1352 | "deprecationReason": null 1353 | }, 1354 | { 1355 | "name": "FRAGMENT_DEFINITION", 1356 | "description": "Location adjacent to a fragment definition.", 1357 | "isDeprecated": false, 1358 | "deprecationReason": null 1359 | }, 1360 | { 1361 | "name": "FRAGMENT_SPREAD", 1362 | "description": "Location adjacent to a fragment spread.", 1363 | "isDeprecated": false, 1364 | "deprecationReason": null 1365 | }, 1366 | { 1367 | "name": "INLINE_FRAGMENT", 1368 | "description": "Location adjacent to an inline fragment.", 1369 | "isDeprecated": false, 1370 | "deprecationReason": null 1371 | }, 1372 | { 1373 | "name": "SCHEMA", 1374 | "description": "Location adjacent to a schema definition.", 1375 | "isDeprecated": false, 1376 | "deprecationReason": null 1377 | }, 1378 | { 1379 | "name": "SCALAR", 1380 | "description": "Location adjacent to a scalar definition.", 1381 | "isDeprecated": false, 1382 | "deprecationReason": null 1383 | }, 1384 | { 1385 | "name": "OBJECT", 1386 | "description": "Location adjacent to an object type definition.", 1387 | "isDeprecated": false, 1388 | "deprecationReason": null 1389 | }, 1390 | { 1391 | "name": "FIELD_DEFINITION", 1392 | "description": "Location adjacent to a field definition.", 1393 | "isDeprecated": false, 1394 | "deprecationReason": null 1395 | }, 1396 | { 1397 | "name": "ARGUMENT_DEFINITION", 1398 | "description": "Location adjacent to an argument definition.", 1399 | "isDeprecated": false, 1400 | "deprecationReason": null 1401 | }, 1402 | { 1403 | "name": "INTERFACE", 1404 | "description": "Location adjacent to an interface definition.", 1405 | "isDeprecated": false, 1406 | "deprecationReason": null 1407 | }, 1408 | { 1409 | "name": "UNION", 1410 | "description": "Location adjacent to a union definition.", 1411 | "isDeprecated": false, 1412 | "deprecationReason": null 1413 | }, 1414 | { 1415 | "name": "ENUM", 1416 | "description": "Location adjacent to an enum definition.", 1417 | "isDeprecated": false, 1418 | "deprecationReason": null 1419 | }, 1420 | { 1421 | "name": "ENUM_VALUE", 1422 | "description": "Location adjacent to an enum value definition.", 1423 | "isDeprecated": false, 1424 | "deprecationReason": null 1425 | }, 1426 | { 1427 | "name": "INPUT_OBJECT", 1428 | "description": "Location adjacent to an input object type definition.", 1429 | "isDeprecated": false, 1430 | "deprecationReason": null 1431 | }, 1432 | { 1433 | "name": "INPUT_FIELD_DEFINITION", 1434 | "description": "Location adjacent to an input object field definition.", 1435 | "isDeprecated": false, 1436 | "deprecationReason": null 1437 | } 1438 | ], 1439 | "possibleTypes": null 1440 | } 1441 | ], 1442 | "directives": [ 1443 | { 1444 | "name": "include", 1445 | "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", 1446 | "locations": [ 1447 | "FIELD", 1448 | "FRAGMENT_SPREAD", 1449 | "INLINE_FRAGMENT" 1450 | ], 1451 | "args": [ 1452 | { 1453 | "name": "if", 1454 | "description": "Included when true.", 1455 | "type": { 1456 | "kind": "NON_NULL", 1457 | "name": null, 1458 | "ofType": { 1459 | "kind": "SCALAR", 1460 | "name": "Boolean", 1461 | "ofType": null 1462 | } 1463 | }, 1464 | "defaultValue": null 1465 | } 1466 | ] 1467 | }, 1468 | { 1469 | "name": "skip", 1470 | "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", 1471 | "locations": [ 1472 | "FIELD", 1473 | "FRAGMENT_SPREAD", 1474 | "INLINE_FRAGMENT" 1475 | ], 1476 | "args": [ 1477 | { 1478 | "name": "if", 1479 | "description": "Skipped when true.", 1480 | "type": { 1481 | "kind": "NON_NULL", 1482 | "name": null, 1483 | "ofType": { 1484 | "kind": "SCALAR", 1485 | "name": "Boolean", 1486 | "ofType": null 1487 | } 1488 | }, 1489 | "defaultValue": null 1490 | } 1491 | ] 1492 | }, 1493 | { 1494 | "name": "deprecated", 1495 | "description": "Marks an element of a GraphQL schema as no longer supported.", 1496 | "locations": [ 1497 | "FIELD_DEFINITION", 1498 | "ENUM_VALUE" 1499 | ], 1500 | "args": [ 1501 | { 1502 | "name": "reason", 1503 | "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", 1504 | "type": { 1505 | "kind": "SCALAR", 1506 | "name": "String", 1507 | "ofType": null 1508 | }, 1509 | "defaultValue": "\"No longer supported\"" 1510 | } 1511 | ] 1512 | } 1513 | ] 1514 | } 1515 | }, 1516 | "extensions": { 1517 | "cacheControl": { 1518 | "version": 1, 1519 | "hints": [] 1520 | } 1521 | } 1522 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { GraphQLServer } from 'graphql-yoga' 3 | 4 | import { pubsub, dataloaders, BASE_URI, PORT } from './config' 5 | import { schema } from './schema' 6 | import * as models from './models' 7 | import { connectDatabase } from './database' 8 | // 9 | ;(async () => { 10 | try { 11 | const info = await connectDatabase() 12 | console.log(`Connected to ${info.host}:${info.port}/${info.name}`) 13 | } catch (error) { 14 | console.error('Unable to connect to database') 15 | process.exit(1) 16 | } 17 | 18 | const server = new GraphQLServer({ 19 | schema, 20 | context: { 21 | pubsub, 22 | models, 23 | dataloaders 24 | } 25 | }) 26 | 27 | server.start({ port: PORT, cacheControl: true }, () => 28 | console.log(`GraphQL Server is now running on ${BASE_URI}`) 29 | ) 30 | })() 31 | -------------------------------------------------------------------------------- /src/loaders/CatLoader.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import DataLoader from 'dataloader' 3 | import { Cat as CatModel } from '../models' 4 | import { 5 | connectionFromMongoCursor, 6 | mongooseLoader 7 | } from '@entria/graphql-mongoose-loader' 8 | 9 | import type { ConnectionArguments } from 'graphql-relay' 10 | import type { Cat as CatType } from '../types/Cat' 11 | import type { GraphqlContextType } from '../types/GraphqlContextType' 12 | 13 | export default class Cat { 14 | id: string 15 | name: string 16 | nickName: string 17 | description: string 18 | createdAt: string 19 | updatedAt: string 20 | avatarUrl: string 21 | age: number 22 | 23 | constructor (data: CatType, { cat }: GraphqlContextType) { 24 | this.id = data.id 25 | this.name = data.name 26 | this.nickName = data.nickName 27 | this.description = data.description 28 | this.createdAt = data.createdAt 29 | this.updatedAt = data.updatedAt 30 | this.avatarUrl = data.avatarUrl 31 | this.age = data.age 32 | } 33 | } 34 | 35 | export const getLoader = () => 36 | // $FlowFixMe 37 | new DataLoader(ids => mongooseLoader(CatModel, ids)) 38 | 39 | const viewerCanSee = (context, data) => { 40 | // Anyone can see another cat 41 | return true 42 | } 43 | 44 | export const load = async ( 45 | context: GraphqlContextType, 46 | id: string 47 | ): Promise => { 48 | if (!id) { 49 | return null 50 | } 51 | 52 | let data 53 | try { 54 | data = await context.dataloaders.CatLoader.load(id) 55 | } catch (err) { 56 | return null 57 | } 58 | return viewerCanSee(context, data) ? new Cat(data, context) : null 59 | } 60 | 61 | export const clearCache = ({ dataloaders }: GraphqlContextType, id: string) => 62 | dataloaders.CatLoader.clear(id.toString()) 63 | 64 | export const loadCats = async ( 65 | context: GraphqlContextType, 66 | args: ConnectionArguments 67 | ) => { 68 | const { Cat } = context.models 69 | const cursor = Cat.find().sort({ createdAt: -1 }) 70 | 71 | return connectionFromMongoCursor({ 72 | cursor, 73 | context, 74 | args, 75 | loader: load 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /src/loaders/index.js: -------------------------------------------------------------------------------- 1 | import * as CatLoader from './CatLoader' 2 | 3 | export { CatLoader } 4 | -------------------------------------------------------------------------------- /src/models/Cat.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | 3 | const Schema = mongoose.Schema( 4 | { 5 | name: { 6 | type: String, 7 | required: true 8 | }, 9 | nickName: { 10 | type: String, 11 | required: true 12 | }, 13 | description: { 14 | type: String, 15 | required: true 16 | }, 17 | avatarUrl: { 18 | type: String, 19 | required: true 20 | }, 21 | age: { 22 | type: Number, 23 | required: true 24 | } 25 | }, 26 | { 27 | timestamps: { 28 | createdAt: 'createdAt', 29 | updatedAt: 'updatedAt' 30 | }, 31 | collection: 'cats' 32 | } 33 | ) 34 | 35 | Schema.index({ description: 'text', name: 'text' }) 36 | 37 | export default mongoose.model('Cat', Schema) 38 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import Cat from './Cat' 4 | 5 | export { Cat } 6 | -------------------------------------------------------------------------------- /src/mutations/AddCat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { GraphQLNonNull, GraphQLString, GraphQLInt } from 'graphql' 3 | 4 | import { pubsub } from '../config' 5 | import GraphQLCat from '../entity/Cat' 6 | 7 | import type { GraphqlContextType } from '../types/GraphqlContextType' 8 | import type { Cat as CatType } from '../types/Cat' 9 | 10 | type argsType = { 11 | name: string, 12 | nickName: string, 13 | description: string, 14 | avatarUrl: string, 15 | age: number, 16 | } 17 | 18 | export default { 19 | type: GraphQLCat, 20 | args: { 21 | name: { 22 | type: new GraphQLNonNull(GraphQLString) 23 | }, 24 | nickName: { 25 | type: new GraphQLNonNull(GraphQLString) 26 | }, 27 | description: { 28 | type: new GraphQLNonNull(GraphQLString) 29 | }, 30 | avatarUrl: { 31 | type: new GraphQLNonNull(GraphQLString) 32 | }, 33 | age: { 34 | type: new GraphQLNonNull(GraphQLInt) 35 | } 36 | }, 37 | resolve: async ( 38 | _: mixed, 39 | args: argsType, 40 | { models }: GraphqlContextType 41 | ): Promise => { 42 | const payload = { 43 | ...args 44 | } 45 | const cat = await new models.Cat(payload).save() 46 | 47 | pubsub.publish('newCat', cat) 48 | return cat 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/mutations/RemoveCat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLNonNull, GraphQLID } from 'graphql' 4 | 5 | import { pubsub } from '../config' 6 | import GraphQLCat from '../entity/Cat' 7 | 8 | import type { GraphqlContextType } from '../types/GraphqlContextType' 9 | import type { Cat as CatType } from '../types/Cat' 10 | 11 | type argsType = { 12 | id: string, 13 | } 14 | 15 | export default { 16 | type: GraphQLCat, 17 | args: { 18 | id: { 19 | type: new GraphQLNonNull(GraphQLID) 20 | } 21 | }, 22 | resolve: async ( 23 | _: mixed, 24 | { id }: argsType, 25 | { models }: GraphqlContextType 26 | ): Promise => { 27 | const cat = await new models.Cat({ _id: id }).remove() 28 | 29 | pubsub.publish('removedCat', cat) 30 | return cat 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/mutations/__tests__/AddCat.js: -------------------------------------------------------------------------------- 1 | import { request } from 'graphql-request'; 2 | 3 | const host = 'http://localhost:8080/'; 4 | 5 | const query = `{ 6 | mutation { 7 | addCat( 8 | name: "Novinho", 9 | nickName: "pussie", 10 | description: "Ooops, I killed a mouse", 11 | avatarUrl: "http://tachyons.io/img/cat-720.jpg", 12 | age: 5 13 | ) 14 | } 15 | }`; 16 | 17 | test('Add new cat', async () => { 18 | const response = await request(host, query); 19 | expect(response).toEqual({ addCat: true }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/mutations/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLObjectType } from 'graphql'; 4 | 5 | import AddCat from './AddCat'; 6 | import RemoveCat from './RemoveCat'; 7 | 8 | export default new GraphQLObjectType({ 9 | name: 'RootMutation', 10 | description: 'Root Mutation', 11 | fields: { 12 | addCat: AddCat, 13 | removeCat: RemoveCat, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/queries/AllCats.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { GraphQLInt } from 'graphql' 3 | import { connectionDefinitions, connectionArgs } from 'graphql-relay' 4 | 5 | import GraphQLCat from '../entity/Cat' 6 | import { CatLoader } from '../loaders' 7 | import type { GraphqlContextType } from '../types/GraphqlContextType' 8 | 9 | const { connectionType: AllCatsConnection } = connectionDefinitions({ 10 | name: 'Cat', 11 | nodeType: GraphQLCat, 12 | connectionFields: { 13 | count: { 14 | type: GraphQLInt 15 | } 16 | } 17 | }) 18 | 19 | export default { 20 | type: AllCatsConnection, 21 | args: { 22 | ...connectionArgs 23 | }, 24 | resolve: async (_: mixed, args: Object, context: GraphqlContextType) => 25 | CatLoader.loadCats(context, args) 26 | } 27 | -------------------------------------------------------------------------------- /src/queries/Cat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLID } from 'graphql' 4 | 5 | import GraphQLCat from '../entity/Cat' 6 | 7 | import type { GraphqlContextType } from '../types/GraphqlContextType' 8 | import type { Cat as CatType } from '../types/Cat' 9 | 10 | type argsType = { 11 | id: string, 12 | } 13 | 14 | export default { 15 | type: GraphQLCat, 16 | args: { 17 | id: { 18 | type: GraphQLID 19 | } 20 | }, 21 | resolve: async ( 22 | _: mixed, 23 | { id }: argsType, 24 | { models }: GraphqlContextType 25 | ): Promise => { 26 | const cats = await models.Cat.find({ _id: id }) 27 | 28 | return cats[0] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/queries/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLObjectType } from 'graphql' 4 | 5 | import Cat from './Cat' 6 | import AllCats from './AllCats' 7 | 8 | export default new GraphQLObjectType({ 9 | name: 'RootQuery', 10 | description: 'Root Query', 11 | fields: { 12 | cat: Cat, 13 | allCats: AllCats 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLSchema } from 'graphql' 4 | 5 | import RootQuery from './queries' 6 | import RootMutation from './mutations' 7 | import RootSubscription from './subscriptions' 8 | 9 | const schemaDefinition: Object = { 10 | query: RootQuery, 11 | mutation: RootMutation, 12 | subscription: RootSubscription 13 | } 14 | 15 | export const schema = new GraphQLSchema(schemaDefinition) 16 | -------------------------------------------------------------------------------- /src/subscriptions/NewCat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { pubsub } from '../config' 4 | import GraphQLCat from '../entity/Cat' 5 | 6 | import type { Cat } from '../types/Cat' 7 | 8 | export default { 9 | type: GraphQLCat, 10 | resolve: (payload: Cat) => payload, 11 | subscribe: () => pubsub.asyncIterator('newCat') 12 | } 13 | -------------------------------------------------------------------------------- /src/subscriptions/RemovedCat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { pubsub } from '../config' 4 | import GraphQLCat from '../entity/Cat' 5 | 6 | import type { Cat } from '../types/Cat' 7 | 8 | export default { 9 | type: GraphQLCat, 10 | resolve: (payload: Cat) => payload, 11 | subscribe: () => pubsub.asyncIterator('removedCat') 12 | } 13 | -------------------------------------------------------------------------------- /src/subscriptions/index.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { GraphQLObjectType } from 'graphql'; 4 | 5 | import NewCat from './NewCat'; 6 | import RemovedCat from './RemovedCat'; 7 | 8 | export default new GraphQLObjectType({ 9 | name: 'RootSubscription', 10 | description: 'Root Subscription', 11 | fields: { 12 | newCat: NewCat, 13 | removedCat: RemovedCat, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/types/Cat.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type Cat = {| 4 | id: string, 5 | name: string, 6 | nickName: string, 7 | description: string, 8 | createdAt: string, 9 | updatedAt: string, 10 | avatarUrl: string, 11 | age: number, 12 | |} 13 | -------------------------------------------------------------------------------- /src/types/GraphqlContextType.js: -------------------------------------------------------------------------------- 1 | export type GraphqlContextType = {| 2 | pubsub: Object, 3 | models: modelsType, 4 | dataloaders: dataloadersType, 5 | |} 6 | 7 | export type modelsType = {| 8 | Cat: Object, 9 | |} 10 | 11 | export type dataloadersType = {| 12 | CatLoader: Object, 13 | |} 14 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | // https://github.com/entria/graphql-dataloader-boilerplate/blob/master/test/helper.jsimport mongoose from 'mongoose' 2 | import mongoose from 'mongoose' 3 | 4 | const { ObjectId } = mongoose.Types 5 | 6 | process.env.NODE_ENV = 'test' 7 | 8 | const MONGO_URL = 'mongodb://localhost/test' 9 | 10 | const mongooseOptions = { 11 | autoIndex: false, 12 | autoReconnect: true, 13 | reconnectTries: Number.MAX_VALUE, 14 | reconnectInterval: 1000, 15 | connectTimeoutMS: 10000 16 | } 17 | 18 | mongoose.Promise = Promise 19 | 20 | export async function connectMongoose () { 21 | // // $FlowExpectedError setTimeout is not defined on JestObject 22 | // jest.setTimeout(20000); 23 | return mongoose.connect(global.__MONGO_URI__, { 24 | ...mongooseOptions, 25 | dbName: global.__MONGO_DB_NAME__ 26 | }) 27 | } 28 | 29 | export async function disconnectMongoose () { 30 | // await mongoose.connection.close(); 31 | return mongoose.disconnect() 32 | } 33 | 34 | export async function clearDatabase () { 35 | await mongoose.connection.db.dropDatabase() 36 | } 37 | -------------------------------------------------------------------------------- /test/mongodb.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/no-extraneous-dependencies 2 | const NodeEnvironment = require('jest-environment-node') 3 | const MongodbMemoryServer = require('mongodb-memory-server') 4 | 5 | class MongoDbEnvironment extends NodeEnvironment { 6 | constructor (config) { 7 | super(config) 8 | // eslint-disable-next-line new-cap 9 | this.mongod = new MongodbMemoryServer.default({ 10 | binary: { 11 | version: '3.6.4' 12 | } 13 | }) 14 | } 15 | 16 | async setup () { 17 | await super.setup() 18 | // console.log('\n# MongoDB Environment Setup #'); 19 | 20 | this.global.__MONGO_URI__ = await this.mongod.getConnectionString() 21 | this.global.__MONGO_DB_NAME__ = await this.mongod.getDbName() 22 | this.global.__COUNTERS__ = { 23 | user: 0 24 | } 25 | } 26 | 27 | async teardown () { 28 | // console.log('\n# MongoDB Environment Teardown #'); 29 | await super.teardown() 30 | await this.mongod.stop() 31 | } 32 | 33 | runScript (script) { 34 | return super.runScript(script) 35 | } 36 | } 37 | 38 | module.exports = MongoDbEnvironment 39 | --------------------------------------------------------------------------------