Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GraphQL support via cli #5697

Merged
merged 5 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,148 @@ Live queries are meant to be used in real-time reactive applications, where just

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).

# GraphQL

[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.

## Running

```
$ npm install -g parse-server mongodb-runner
$ mongodb-runner start
$ parse-server --appId APPLICATION_ID --masterKey MASTER_KEY --databaseURI mongodb://localhost/test --mountGraphQL --mountPlayground
```

After starting the server, you can visit http://localhost:1337/playground in your browser to start playing with your GraphQL API.

***Note:*** Do ***NOT*** use --mountPlayground option in production.

## Checking the API health

Run the following:

```graphql
query Health {
health
}
```

You should receive the following response:

```json
{
"data": {
"health": true
}
}
```

## Creating your first object

Since your application does not have a schema yet, you can use the generic `create` mutation to create your first object. Run the following:

```graphql
mutation CreateObject {
objects {
create(className: "GameScore" fields: { score: 1337 playerName: "Sean Plott" cheatMode: false }) {
objectId
createdAt
}
}
}
```

You should receive a response similar to this:

```json
{
"data": {
"objects": {
"create": {
"objectId": "7jfBmbGgyF",
"createdAt": "2019-06-20T23:50:50.825Z"
}
}
}
}
```

## Using automatically generated operations

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!

Run the following to create a second object:

```graphql
mutation CreateGameScore {
objects {
createGameScore(fields: { score: 2558 playerName: "Luke Skywalker" cheatMode: false }) {
objectId
createdAt
}
}
}
```

You should receive a response similar to this:

```json
{
"data": {
"objects": {
"createGameScore": {
"objectId": "gySYolb2CL",
"createdAt": "2019-06-20T23:56:37.114Z"
}
}
}
}
```

You can also run a query to this new class:

```graphql
query FindGameScore {
objects {
findGameScore {
results {
playerName
score
}
}
}
}
```

You should receive a response similar to this:

```json
{
"data": {
"objects": {
"findGameScore": {
"results": [
{
"playerName": "Sean Plott",
"score": 1337
},
{
"playerName": "Luke Skywalker",
"score": 2558
}
]
}
}
}
}
```

## Learning more

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.

Additionally, the [GraphQL Learn Section](https://graphql.org/learn/) is a very good source to start learning about the power of the GraphQL language.

# Upgrading to 3.0.0

Starting 3.0.0, parse-server uses the JS SDK version 2.0.
Expand Down
83 changes: 83 additions & 0 deletions spec/CLI.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const commander = require('../lib/cli/utils/commander').default;
const definitions = require('../lib/cli/definitions/parse-server').default;
const liveQueryDefinitions = require('../lib/cli/definitions/parse-live-query-server')
.default;
const path = require('path');
const { spawn } = require('child_process');

const testDefinitions = {
arg0: 'PROGRAM_ARG_0',
Expand Down Expand Up @@ -231,3 +233,84 @@ describe('LiveQuery definitions', () => {
}
});
});

describe('execution', () => {
const binPath = path.resolve(__dirname, '../bin/parse-server');
let childProcess;

afterEach(async () => {
if (childProcess) {
childProcess.kill();
}
});

it('shoud start Parse Server', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
]);
childProcess.stdout.on('data', data => {
data = data.toString();
if (data.includes('parse-server running on')) {
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});

it('shoud start Parse Server with GraphQL', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--mountGraphQL',
]);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
output += data;
if (data.includes('GraphQL running on')) {
expect(output).toMatch('parse-server running on');
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});

it('shoud start Parse Server with GraphQL and Playground', done => {
childProcess = spawn(binPath, [
'--appId',
'test',
'--masterKey',
'test',
'--databaseURI',
'mongodb://localhost/test',
'--mountGraphQL',
'--mountPlayground',
]);
let output = '';
childProcess.stdout.on('data', data => {
data = data.toString();
output += data;
if (data.includes('Playground running on')) {
expect(output).toMatch('GraphQL running on');
expect(output).toMatch('parse-server running on');
done();
}
});
childProcess.stderr.on('data', data => {
done.fail(data.toString());
});
});
});
4 changes: 4 additions & 0 deletions src/GraphQL/ParseGraphQLServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ class ParseGraphQLServer {
renderPlaygroundPage({
endpoint: this.config.graphQLPath,
subscriptionEndpoint: this.config.subscriptionsPath,
headers: {
'X-Parse-Application-Id': this.parseServer.config.appId,
'X-Parse-Master-Key': this.parseServer.config.masterKey,
},
})
);
res.end();
Expand Down
22 changes: 22 additions & 0 deletions src/Options/Definitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ module.exports.ParseServerOptions = {
help: 'Adapter module for the files sub-system',
action: parsers.moduleOrObjectParser,
},
graphQLPath: {
env: 'PARSE_SERVER_GRAPHQL_PATH',
help: 'Mount path for the GraphQL endpoint, defaults to /graphql',
default: '/graphql',
},
host: {
env: 'PARSE_SERVER_HOST',
help: 'The host to serve ParseServer on, defaults to 0.0.0.0',
Expand Down Expand Up @@ -219,11 +224,23 @@ module.exports.ParseServerOptions = {
env: 'PARSE_SERVER_MIDDLEWARE',
help: 'middleware for express server, can be string or function',
},
mountGraphQL: {
env: 'PARSE_SERVER_MOUNT_GRAPHQL',
help: 'Mounts the GraphQL endpoint',
action: parsers.booleanParser,
default: false,
},
mountPath: {
env: 'PARSE_SERVER_MOUNT_PATH',
help: 'Mount path for the server, defaults to /parse',
default: '/parse',
},
mountPlayground: {
env: 'PARSE_SERVER_MOUNT_PLAYGROUND',
help: 'Mounts the GraphQL Playground - never use this option in production',
action: parsers.booleanParser,
default: false,
},
objectIdSize: {
env: 'PARSE_SERVER_OBJECT_ID_SIZE',
help: "Sets the number of characters in generated object id's, default 10",
Expand All @@ -235,6 +252,11 @@ module.exports.ParseServerOptions = {
help: 'Password policy for enforcing password related rules',
action: parsers.objectParser,
},
playgroundPath: {
env: 'PARSE_SERVER_PLAYGROUND_PATH',
help: 'Mount path for the GraphQL Playground, defaults to /playground',
default: '/playground',
},
port: {
env: 'PORT',
help: 'The port to run the ParseServer, defaults to 1337.',
Expand Down
4 changes: 4 additions & 0 deletions src/Options/docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* @property {Boolean} expireInactiveSessions Sets wether we should expire the inactive sessions, defaults to true
* @property {String} fileKey Key for your files
* @property {Adapter<FilesAdapter>} filesAdapter Adapter module for the files sub-system
* @property {String} graphQLPath Mount path for the GraphQL endpoint, defaults to /graphql
* @property {String} host The host to serve ParseServer on, defaults to 0.0.0.0
* @property {String} javascriptKey Key for the Javascript SDK
* @property {Boolean} jsonLogs Log as structured JSON objects
Expand All @@ -40,9 +41,12 @@
* @property {Number} maxLimit Max value for limit option on queries, defaults to unlimited
* @property {String} maxUploadSize Max file size for uploads, defaults to 20mb
* @property {Union} middleware middleware for express server, can be string or function
* @property {Boolean} mountGraphQL Mounts the GraphQL endpoint
* @property {String} mountPath Mount path for the server, defaults to /parse
* @property {Boolean} mountPlayground Mounts the GraphQL Playground - never use this option in production
* @property {Number} objectIdSize Sets the number of characters in generated object id's, default 10
* @property {Any} passwordPolicy Password policy for enforcing password related rules
* @property {String} playgroundPath Mount path for the GraphQL Playground, defaults to /playground
* @property {Number} port The port to run the ParseServer, defaults to 1337.
* @property {Boolean} preserveFileName Enable (or disable) the addition of a unique hash to the file names
* @property {Boolean} preventLoginWithUnverifiedEmail Prevent user from login if email is not verified and PARSE_SERVER_VERIFY_USER_EMAILS is true, defaults to false
Expand Down
16 changes: 16 additions & 0 deletions src/Options/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,22 @@ export interface ParseServerOptions {
startLiveQueryServer: ?boolean;
/* Live query server configuration options (will start the liveQuery server) */
liveQueryServerOptions: ?LiveQueryServerOptions;
/* Mounts the GraphQL endpoint
:ENV: PARSE_SERVER_MOUNT_GRAPHQL
:DEFAULT: false */
mountGraphQL: ?boolean;
/* Mount path for the GraphQL endpoint, defaults to /graphql
:ENV: PARSE_SERVER_GRAPHQL_PATH
:DEFAULT: /graphql */
graphQLPath: ?string;
/* Mounts the GraphQL Playground - never use this option in production
:ENV: PARSE_SERVER_MOUNT_PLAYGROUND
:DEFAULT: false */
mountPlayground: ?boolean;
/* Mount path for the GraphQL Playground, defaults to /playground
:ENV: PARSE_SERVER_PLAYGROUND_PATH
:DEFAULT: /playground */
playgroundPath: ?string;

serverStartComplete: ?(error: ?Error) => void;
}
Expand Down
19 changes: 18 additions & 1 deletion src/ParseServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ import { UsersRouter } from './Routers/UsersRouter';
import { PurgeRouter } from './Routers/PurgeRouter';
import { AudiencesRouter } from './Routers/AudiencesRouter';
import { AggregateRouter } from './Routers/AggregateRouter';

import { ParseServerRESTController } from './ParseServerRESTController';
import * as controllers from './Controllers';
import { ParseGraphQLServer } from './GraphQL/ParseGraphQLServer';

// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();

Expand Down Expand Up @@ -264,6 +265,22 @@ class ParseServer {
}

app.use(options.mountPath, this.app);

if (options.mountGraphQL === true || options.mountPlayground === true) {
const parseGraphQLServer = new ParseGraphQLServer(this, {
graphQLPath: options.graphQLPath,
playgroundPath: options.playgroundPath,
});

if (options.mountGraphQL) {
parseGraphQLServer.applyGraphQL(app);
}

if (options.mountPlayground) {
parseGraphQLServer.applyPlayground(app);
}
}

const server = app.listen(options.port, options.host, callback);
this.server = server;

Expand Down
Loading