GraphQL support via cli (#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
This commit is contained in:
Antonio Davi Macedo Coelho de Castro
2019-06-25 14:44:23 -07:00
committed by GitHub
parent 7ffb3b65e0
commit 5bc79cc3db
8 changed files with 315 additions and 7 deletions

142
README.md
View File

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

View File

@@ -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',
@@ -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());
});
});
});

View File

@@ -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();

View File

@@ -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',
@@ -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",
@@ -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.',

View File

@@ -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
@@ -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

View File

@@ -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;
}

View File

@@ -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();
@@ -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;

View File

@@ -84,20 +84,40 @@ runner({
});
} else {
ParseServer.start(options, () => {
console.log(
'[' + process.pid + '] parse-server running on ' + options.serverURL
);
printSuccessMessage();
});
}
} else {
ParseServer.start(options, () => {
logOptions();
console.log('');
console.log(
'[' + process.pid + '] parse-server running on ' + options.serverURL
);
printSuccessMessage();
});
}
function printSuccessMessage() {
console.log(
'[' + process.pid + '] parse-server running on ' + options.serverURL
);
if (options.mountGraphQL) {
console.log(
'[' +
process.pid +
'] GraphQL running on http://localhost:' +
options.port +
options.graphQLPath
);
}
if (options.mountPlayground) {
console.log(
'[' +
process.pid +
'] Playground running on http://localhost:' +
options.port +
options.playgroundPath
);
}
}
},
});