Skip to content

Commit 42e3889

Browse files
authored
GraphQL support via cli (parse-community#5697)
* Including GraphQL options in CLI - now it was auto-generated * Improving the way that the headers are passed to the playground * Including README notes about GraphQL * Improving final text
1 parent e178e8b commit 42e3889

File tree

8 files changed

+314
-6
lines changed

8 files changed

+314
-6
lines changed

README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,148 @@ Live queries are meant to be used in real-time reactive applications, where just
356356

357357
Take a look at [Live Query Guide](https://docs.parseplatform.org/parse-server/guide/#live-queries), [Live Query Server Setup Guide](https://docs.parseplatform.org/parse-server/guide/#scalability) and [Live Query Protocol Specification](https://github.com/parse-community/parse-server/wiki/Parse-LiveQuery-Protocol-Specification). You can setup a standalone server or multiple instances for scalability (recommended).
358358

359+
# GraphQL
360+
361+
[GraphQL](https://graphql.org/), developed by Facebook, is an open-source data query and manipulation language for APIs. In addition to the traditional REST API, Parse Server automatically generates a GraphQL API based on your current application schema.
362+
363+
## Running
364+
365+
```
366+
$ npm install -g parse-server mongodb-runner
367+
$ mongodb-runner start
368+
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground
369+
```
370+
371+
After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.
372+
373+
***Note:*** Do ***NOT*** use --mountPlayground option in production.
374+
375+
## Checking the API health
376+
377+
Run the following:
378+
379+
```graphql
380+
query Health {
381+
health
382+
}
383+
```
384+
385+
You should receive the following response:
386+
387+
```json
388+
{
389+
"data": {
390+
"health": true
391+
}
392+
}
393+
```
394+
395+
## Creating your first object
396+
397+
Since your application does not have a schema yet, you can use the generic `create` mutation to create your first object. Run the following:
398+
399+
```graphql
400+
mutation CreateObject {
401+
objects {
402+
create(className: "GameScore" fields: { score: 1337 playerName: "Sean Plott" cheatMode: false }) {
403+
objectId
404+
createdAt
405+
}
406+
}
407+
}
408+
```
409+
410+
You should receive a response similar to this:
411+
412+
```json
413+
{
414+
"data": {
415+
"objects": {
416+
"create": {
417+
"objectId": "7jfBmbGgyF",
418+
"createdAt": "2019-06-20T23:50:50.825Z"
419+
}
420+
}
421+
}
422+
}
423+
```
424+
425+
## Using automatically generated operations
426+
427+
Parse Server learned from the first object that you created and now you have the `GameScore` class in your schema. You can now start using the automatically generated operations!
428+
429+
Run the following to create a second object:
430+
431+
```graphql
432+
mutation CreateGameScore {
433+
objects {
434+
createGameScore(fields: { score: 2558 playerName: "Luke Skywalker" cheatMode: false }) {
435+
objectId
436+
createdAt
437+
}
438+
}
439+
}
440+
```
441+
442+
You should receive a response similar to this:
443+
444+
```json
445+
{
446+
"data": {
447+
"objects": {
448+
"createGameScore": {
449+
"objectId": "gySYolb2CL",
450+
"createdAt": "2019-06-20T23:56:37.114Z"
451+
}
452+
}
453+
}
454+
}
455+
```
456+
457+
You can also run a query to this new class:
458+
459+
```graphql
460+
query FindGameScore {
461+
objects {
462+
findGameScore {
463+
results {
464+
playerName
465+
score
466+
}
467+
}
468+
}
469+
}
470+
```
471+
472+
You should receive a response similar to this:
473+
474+
```json
475+
{
476+
"data": {
477+
"objects": {
478+
"findGameScore": {
479+
"results": [
480+
{
481+
"playerName": "Sean Plott",
482+
"score": 1337
483+
},
484+
{
485+
"playerName": "Luke Skywalker",
486+
"score": 2558
487+
}
488+
]
489+
}
490+
}
491+
}
492+
}
493+
```
494+
495+
## Learning more
496+
497+
Please look at the right side of your GraphQL Playground. You will see the `DOCS` and `SCHEMA` menus. They are automatically generated by analysing your application schema. Please refer to them and learn more about everything that you can do with your Parse GraphQL API.
498+
499+
Additionally, the [GraphQL Learn Section](https://graphql.org/learn/) is a very good source to start learning about the power of the GraphQL language.
500+
359501
# Upgrading to 3.0.0
360502

361503
Starting 3.0.0, parse-server uses the JS SDK version 2.0.

spec/CLI.spec.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const commander = require('../lib/cli/utils/commander').default;
33
const definitions = require('../lib/cli/definitions/parse-server').default;
44
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server')
55
.default;
6+
const path = require('path');
7+
const { spawn } = require('child_process');
68

79
const testDefinitions = {
810
arg0: 'PROGRAM_ARG_0',
@@ -231,3 +233,84 @@ describe('LiveQuery definitions', () => {
231233
}
232234
});
233235
});
236+
237+
describe('execution', () => {
238+
const binPath = path.resolve(__dirname, '../bin/parse-server');
239+
let childProcess;
240+
241+
afterEach(async () => {
242+
if (childProcess) {
243+
childProcess.kill();
244+
}
245+
});
246+
247+
it('shoud start Parse Server', done => {
248+
childProcess = spawn(binPath, [
249+
'--appId',
250+
'test',
251+
'--masterKey',
252+
'test',
253+
'--databaseURI',
254+
'mongodb://localhost/test',
255+
]);
256+
childProcess.stdout.on('data', data => {
257+
data = data.toString();
258+
if (data.includes('parse-server running on')) {
259+
done();
260+
}
261+
});
262+
childProcess.stderr.on('data', data => {
263+
done.fail(data.toString());
264+
});
265+
});
266+
267+
it('shoud start Parse Server with GraphQL', done => {
268+
childProcess = spawn(binPath, [
269+
'--appId',
270+
'test',
271+
'--masterKey',
272+
'test',
273+
'--databaseURI',
274+
'mongodb://localhost/test',
275+
'--mountGraphQL',
276+
]);
277+
let output = '';
278+
childProcess.stdout.on('data', data => {
279+
data = data.toString();
280+
output += data;
281+
if (data.includes('GraphQL running on')) {
282+
expect(output).toMatch('parse-server running on');
283+
done();
284+
}
285+
});
286+
childProcess.stderr.on('data', data => {
287+
done.fail(data.toString());
288+
});
289+
});
290+
291+
it('shoud start Parse Server with GraphQL and Playground', done => {
292+
childProcess = spawn(binPath, [
293+
'--appId',
294+
'test',
295+
'--masterKey',
296+
'test',
297+
'--databaseURI',
298+
'mongodb://localhost/test',
299+
'--mountGraphQL',
300+
'--mountPlayground',
301+
]);
302+
let output = '';
303+
childProcess.stdout.on('data', data => {
304+
data = data.toString();
305+
output += data;
306+
if (data.includes('Playground running on')) {
307+
expect(output).toMatch('GraphQL running on');
308+
expect(output).toMatch('parse-server running on');
309+
done();
310+
}
311+
});
312+
childProcess.stderr.on('data', data => {
313+
done.fail(data.toString());
314+
});
315+
});
316+
});

src/GraphQL/ParseGraphQLServer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ class ParseGraphQLServer {
7676
renderPlaygroundPage({
7777
endpoint: this.config.graphQLPath,
7878
subscriptionEndpoint: this.config.subscriptionsPath,
79+
headers: {
80+
'X-Parse-Application-Id': this.parseServer.config.appId,
81+
'X-Parse-Master-Key': this.parseServer.config.masterKey,
82+
},
7983
})
8084
);
8185
res.end();

src/Options/Definitions.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,11 @@ module.exports.ParseServerOptions = {
153153
help: 'Adapter module for the files sub-system',
154154
action: parsers.moduleOrObjectParser,
155155
},
156+
graphQLPath: {
157+
env: 'PARSE_SERVER_GRAPHQL_PATH',
158+
help: 'Mount path for the GraphQL endpoint, defaults to /graphql',
159+
default: '/graphql',
160+
},
156161
host: {
157162
env: 'PARSE_SERVER_HOST',
158163
help: 'The host to serve ParseServer on, defaults to 0.0.0.0',
@@ -219,11 +224,23 @@ module.exports.ParseServerOptions = {
219224
env: 'PARSE_SERVER_MIDDLEWARE',
220225
help: 'middleware for express server, can be string or function',
221226
},
227+
mountGraphQL: {
228+
env: 'PARSE_SERVER_MOUNT_GRAPHQL',
229+
help: 'Mounts the GraphQL endpoint',
230+
action: parsers.booleanParser,
231+
default: false,
232+
},
222233
mountPath: {
223234
env: 'PARSE_SERVER_MOUNT_PATH',
224235
help: 'Mount path for the server, defaults to /parse',
225236
default: '/parse',
226237
},
238+
mountPlayground: {
239+
env: 'PARSE_SERVER_MOUNT_PLAYGROUND',
240+
help: 'Mounts the GraphQL Playground - never use this option in production',
241+
action: parsers.booleanParser,
242+
default: false,
243+
},
227244
objectIdSize: {
228245
env: 'PARSE_SERVER_OBJECT_ID_SIZE',
229246
help: "Sets the number of characters in generated object id's, default 10",
@@ -235,6 +252,11 @@ module.exports.ParseServerOptions = {
235252
help: 'Password policy for enforcing password related rules',
236253
action: parsers.objectParser,
237254
},
255+
playgroundPath: {
256+
env: 'PARSE_SERVER_PLAYGROUND_PATH',
257+
help: 'Mount path for the GraphQL Playground, defaults to /playground',
258+
default: '/playground',
259+
},
238260
port: {
239261
env: 'PORT',
240262
help: 'The port to run the ParseServer, defaults to 1337.',

src/Options/docs.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true
2828
* @property {String} fileKey Key for your files
2929
* @property {Adapter<FilesAdapter>} filesAdapter Adapter module for the files sub-system
30+
* @property {String} graphQLPath Mount path for the GraphQL endpoint, defaults to /graphql
3031
* @property {String} host The host to serve ParseServer on, defaults to 0.0.0.0
3132
* @property {String} javascriptKey Key for the Javascript SDK
3233
* @property {Boolean} jsonLogs Log as structured JSON objects
@@ -40,9 +41,12 @@
4041
* @property {Number} maxLimit Max value for limit option on queries, defaults to unlimited
4142
* @property {String} maxUploadSize Max file size for uploads, defaults to 20mb
4243
* @property {Union} middleware middleware for express server, can be string or function
44+
* @property {Boolean} mountGraphQL Mounts the GraphQL endpoint
4345
* @property {String} mountPath Mount path for the server, defaults to /parse
46+
* @property {Boolean} mountPlayground Mounts the GraphQL Playground - never use this option in production
4447
* @property {Number} objectIdSize Sets the number of characters in generated object id's, default 10
4548
* @property {Any} passwordPolicy Password policy for enforcing password related rules
49+
* @property {String} playgroundPath Mount path for the GraphQL Playground, defaults to /playground
4650
* @property {Number} port The port to run the ParseServer, defaults to 1337.
4751
* @property {Boolean} preserveFileName Enable (or disable) the addition of a unique hash to the file names
4852
* @property {Boolean} preventLoginWithUnverifiedEmail Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false

src/Options/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,22 @@ export interface ParseServerOptions {
180180
startLiveQueryServer: ?boolean;
181181
/* Live query server configuration options (will start the liveQuery server) */
182182
liveQueryServerOptions: ?LiveQueryServerOptions;
183+
/* Mounts the GraphQL endpoint
184+
:ENV: PARSE_SERVER_MOUNT_GRAPHQL
185+
:DEFAULT: false */
186+
mountGraphQL: ?boolean;
187+
/* Mount path for the GraphQL endpoint, defaults to /graphql
188+
:ENV: PARSE_SERVER_GRAPHQL_PATH
189+
:DEFAULT: /graphql */
190+
graphQLPath: ?string;
191+
/* Mounts the GraphQL Playground - never use this option in production
192+
:ENV: PARSE_SERVER_MOUNT_PLAYGROUND
193+
:DEFAULT: false */
194+
mountPlayground: ?boolean;
195+
/* Mount path for the GraphQL Playground, defaults to /playground
196+
:ENV: PARSE_SERVER_PLAYGROUND_PATH
197+
:DEFAULT: /playground */
198+
playgroundPath: ?string;
183199

184200
serverStartComplete: ?(error: ?Error) => void;
185201
}

src/ParseServer.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ import { UsersRouter } from './Routers/UsersRouter';
3434
import { PurgeRouter } from './Routers/PurgeRouter';
3535
import { AudiencesRouter } from './Routers/AudiencesRouter';
3636
import { AggregateRouter } from './Routers/AggregateRouter';
37-
3837
import { ParseServerRESTController } from './ParseServerRESTController';
3938
import * as controllers from './Controllers';
39+
import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';
40+
4041
// Mutate the Parse object to add the Cloud Code handlers
4142
addParseCloud();
4243

@@ -264,6 +265,22 @@ class ParseServer {
264265
}
265266

266267
app.use(options.mountPath, this.app);
268+
269+
if (options.mountGraphQL === true || options.mountPlayground === true) {
270+
const parseGraphQLServer = new ParseGraphQLServer(this, {
271+
graphQLPath: options.graphQLPath,
272+
playgroundPath: options.playgroundPath,
273+
});
274+
275+
if (options.mountGraphQL) {
276+
parseGraphQLServer.applyGraphQL(app);
277+
}
278+
279+
if (options.mountPlayground) {
280+
parseGraphQLServer.applyPlayground(app);
281+
}
282+
}
283+
267284
const server = app.listen(options.port, options.host, callback);
268285
this.server = server;
269286

0 commit comments

Comments
 (0)