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 #5674

Merged
merged 146 commits into from
Jun 20, 2019
Merged

GraphQL Support #5674

merged 146 commits into from
Jun 20, 2019

Conversation

davimacedo
Copy link
Member

This PR aims to empower Parse Server with GraphQL support.

It contains a minimum set of queries and mutations to have a GraphQL API running on top of Parse Server, including: Auto Schema, Users Authentication/Authorization, Files, Class Objects supporting all data types and CRUD operations for MongoDB and Postgres.

Thanks to @douglasmuraoka for helping me on this.

There is still a lot of things that can be done, such as Cloud Code Functions, Subscriptions, and support to other Parse Server routes and we aim to keep working and sending new PRs.

The ParseGraphQLServer is an optional feature (such as ParseLiveQuery) that can play together with ParseServer or solo. The implementation includes a new set of files and do not change the current Parse Server implementation. A new set of specs were also included to keep the same coverage levels that we have in the repository.

Let me know about any question.

@davimacedo davimacedo merged commit fe2e956 into parse-community:master Jun 20, 2019
@dani-mp
Copy link

dani-mp commented Jun 22, 2019

OMG, this is so cool! I guess this is something we could run along with a normal Parse Server instance right? So we can start writing new code with GraphQL support, while we use our legacy cloud fns?

@davimacedo
Copy link
Member Author

davimacedo commented Jun 26, 2019

Yes. You're right! You can run rest and graphql apis at the same time over the same database.

@dani-mp
Copy link

dani-mp commented Jun 26, 2019

@davimacedo awesome! Has this been released already? Is there any docs written?

@davimacedo
Copy link
Member Author

A short getting started section was included here: https://github.com/parse-community/parse-server/#graphql

The new Parse Server version was not yet released but it will probably be in the following few days. Meanwhile you can try it out from the master branch.

I am working on a brand new /graphql/guide with more complete instructions to be included in the docs center.

If you have the chance to play with it, please let me know any feedback you have.

@wfilleman
Copy link

wfilleman commented Jul 19, 2019

@davimacedo I'm considering using this graphQL feature for a web app upgrade I'm exploring for my product but when I follow the guide I get this error on startup:

TypeError: Cannot read property 'databaseController' of undefined at new ParseGraphQLServer (/parse-server/lib/GraphQL/ParseGraphQLServer.js:41:98)

Here is the code with sensitive data removed:

const ParseServer = require('parse-server').ParseServer;
const ParseGraphQLServer = require('parse-server').ParseGraphQLServer;

let parseApi = new ParseServer({****});
const parseGraphQLServer = new ParseGraphQLServer(
        parseApi,
        {
            graphQLPath: '/graphqlclient',
            playgroundPath: '/playground'
        }
    );

// Serve the Parse API on the /parse URL prefix
app.use('/parse', parseApi);
parseGraphQLServer.applyGraphQL(app);

Long-time parse server in production running parse server 3.6.0.

@davimacedo
Copy link
Member Author

davimacedo commented Jul 19, 2019

try:

const ParseServer = require('parse-server').default;
const ParseGraphQLServer = require('parse-server').ParseGraphQLServer;

let parseApi = new ParseServer({****});
const parseGraphQLServer = new ParseGraphQLServer(
        parseApi,
        {
            graphQLPath: '/graphqlclient',
            playgroundPath: '/playground'
        }
    );

// Serve the Parse API on the /parse URL prefix
app.use('/parse', parseApi.app);
parseGraphQLServer.applyGraphQL(app);

@wfilleman
Copy link

wfilleman commented Jul 20, 2019

Awesome, thanks @davimacedo. I made that change and then also had to make this change:
app.use('/parse', parseApi.app);

The dev server starts up now, but both the playground and ParseDashboard can't reach the graphQL server. Playground says "Server cannot be reached"

{
 "error": "Response not successful: Received status code 500"
}

@davimacedo
Copy link
Member Author

davimacedo commented Jul 20, 2019

In the code you shared there is not the line of code that mounts the playground. Can you share? Can you also share how you are starting the Parse Dashboard? You need to make sure that you are passing --graphQLServerURL to the correct endpoint. In your case it is http://.../graphqlclient. In most of the examples the endpoint ends with /graphql and I am afraid you are connecting to the wrong path.

@wfilleman
Copy link

wfilleman commented Jul 20, 2019

Ah, getting closer. In the dashboard setup, I added the graphQLServerURL and it now appears in the Dashboard, but I still get the same error message:

{
 "error": "Response not successful: Received status code 500"
}

Here's the code again (I removed playgrounds to focus on the Dashboard)

const ParseServer = require('parse-server').default;
const ParseGraphQLServer = require('parse-server').ParseGraphQLServer;

let parseApi = new ParseServer({
     ****
     graphQLServerURL: '127.0.0.1:5000/graphqlclient',
     ****
});
let parseDashboard = new ParseDashboard({
        "apps": [
            {
                "serverURL": ****,
                "appId": ****,
                "masterKey": ****,
                "appName": "****",
                graphQLServerURL: `http://127.0.0.1:5000/graphqlclient`,
            }
        ],
        "users": [****],
        "useEncryptedPasswords": false,
        "trustProxy": 1
    }, options);

    const parseGraphQLServer = new ParseGraphQLServer(
        parseApi,
        {
            graphQLPath: '/graphqlclient',
            playgroundPath: '/playground'
        }
    );

    // Serve the Parse API on the /parse URL prefix
    app.use('/parse', parseApi.app);
    app.use('/dashboard', parseDashboard);

    parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API

The first question I wanted to experiment with is does this support Roles and Users? IE could I build a web app that logs into Parse Server or provides credentials so that when the web app runs GraphQL queries, only the user's data is returned?

I'm guessing this could be done with the X-Parse-Some-Header...maybe a session token? In the Dashboard this is set to the master key.

@davimacedo
Copy link
Member Author

Is the dashboard working for the other operations (through the REST API)? If yes. Just make sure that your are using the same dns and port for serverURL and graphQLServerURL. For example:

"serverURL": http://127.0.0.1:5000/parse,
"graphQLServerURL": http://127.0.0.1:5000/graphqlclient,

@davimacedo
Copy link
Member Author

davimacedo commented Jul 20, 2019

The config below should work properly:

const express = require('express');
const { default: ParseServer, ParseGraphQLServer } = require('parse-server');
const ParseDashboard = require('parse-dashboard');

const app = express();

const parseServer = new ParseServer({
  databaseURI: 'mongodb://localhost:27017/test',
  appId: 'APPLICATION_ID',
  masterKey: 'MASTER_KEY',
  serverURL: 'http://localhost:1337/parse'
});

const parseGraphQLServer = new ParseGraphQLServer(
  parseServer,
  {
    graphQLPath: '/graphql',
    playgroundPath: '/playground'
  }
);

const parseDashboard = new ParseDashboard({
  apps: [
    {
      serverURL: 'http://localhost:1337/parse',
      appId: 'APPLICATION_ID',
      masterKey: 'MASTER_KEY',
      appName: 'MyApp',
      graphQLServerURL: 'http://localhost:1337/graphql'
    }
  ]
});

app.use('/parse', parseServer.app); // (Optional) Mounts the REST API
app.use('/dashboard', parseDashboard);
parseGraphQLServer.applyGraphQL(app); // Mounts the GraphQL API
parseGraphQLServer.applyPlayground(app); // (Optional) Mounts the GraphQL Playground - do NOT use in Production

app.listen(1337, function() {
  console.log('REST API running on http://localhost:1337/parse');
  console.log('Parse Dashboard running on http://localhost:1337/dashboard');
  console.log('GraphQL API running on http://localhost:1337/graphql');
  console.log('GraphQL Playground running on http://localhost:1337/playground');
});

@wfilleman
Copy link

Thanks @davimacedo, after changing names to match your sample, I still get the same error in the API Console:

{
  "error": "Response not successful: Received status code 500"
}

Is there logging I can turn on somewhere to trap why the response is 500?

@davimacedo
Copy link
Member Author

Any error should be logged using the Parse default log system. Can you try to set verbose: true to parse server and check your logs? Are you mounting this to an existing database or is it a brand new one? Is the dashboard working properly for the other functionalities? Can you share again your current complete code? I am particularly interested in see all options you are passing to Parse Server and how you are starting your http server.

@buithuyen
Copy link

I have the same one. Both of playground and dashboard are Server cannot be reached.

// Parse server
var parseServer = new ParseServer({
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
  appId: process.env.APP_ID || 'myAppId',
  masterKey: process.env.MASTER_KEY || 'myMasterKey', 
  serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse'
});

// GraphQL
var parseGraphQLServer = new ParseGraphQLServer(parseServer,
  {
    graphQLPath: '/graphql',
    playgroundPath: '/playground'
  }
);

// Dashboard
var dashboard = new ParseDashboard({
  apps: [
    {
      appId: process.env.APP_ID || 'myAppId',
      masterKey: process.env.MASTER_KEY || 'myMasterKey',
      serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse',
      appName: process.env.APP_NAME || 'MyApp',
      graphQLServerURL: process.env.GRAPHQL || 'http://localhost:1337/graphql'
    },
  ],
  users: [
    { user: process.env.USERNAME, pass: process.env.PASSWORD }
  ],
  trustProxy: 1
});

// Express
var app = express();

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, parseServer.app); 
parseGraphQLServer.applyGraphQL(app); 
parseGraphQLServer.applyPlayground(app);

I have followed this issue but not work for me. #6016

@omairvaiyani
Copy link
Contributor

@buithuyen take a look at this discussion, it may be related #5761

@davimacedo
Copy link
Member Author

You can also take a look in this guide: http://docs.parseplatform.org/graphql/guide/#using-expressjs

It contains a complete example of how to run it using an Express.js applcation.

@davimacedo
Copy link
Member Author

Make sure that you are requiring Parse Server like this const { default: ParseServer, ParseGraphQLServer } = require('parse-server');

@buithuyen
Copy link

Thank you guys for your reply.
I have followed everything that you told me.

var express = require('express');
var { default: ParseServer, ParseGraphQLServer } = require('parse-server');
var ParseDashboard = require('parse-dashboard');

// Database
var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || 'mongodb://localhost:27017/dev';
if (!databaseUri) {
  console.log('DATABASE_URI not specified, falling back to localhost.');
}

var serverConfig = {
  databaseURI: databaseUri || 'mongodb://localhost:27017/dev',
  cloud: process.env.CLOUD_CODE_MAIN || __dirname + '/cloud/main.js',
  appId: process.env.APP_ID || 'myAppId',
  masterKey: process.env.MASTER_KEY || 'myMasterKey', 
  serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse',
  allowClientClassCreation: false,
  mountGraphQL: true,
  mountPlayground: true
}

// Parse server
var parseServer = new ParseServer(serverConfig);

// GraphQL
// var parseGraphQLServer = new ParseGraphQLServer(parseServer, {
    // graphQLPath: '/graphql',
    // playgroundPath: '/playground'
//   }
// );

// Dashboard
var dashboard = new ParseDashboard({
  apps: [
    {
      appId: process.env.APP_ID || 'myAppId',
      masterKey: process.env.MASTER_KEY || 'myMasterKey',
      serverURL: process.env.SERVER_URL || 'http://localhost:1337/parse',
      appName: process.env.APP_NAME || 'MyApp',
      graphQLServerURL: process.env.GRAPHQL || 'http://localhost:1337/graphql'
    },
  ],
  users: [
    { user: process.env.USERNAME, pass: process.env.PASSWORD }
  ],
  trustProxy: 1
});

// Express
var app = express();

const {mountGraphQL, mountPlayground} = serverConfig;
if (mountGraphQL === true || mountPlayground === true) {
  const parseGraphQLServer = new ParseGraphQLServer(parseServer, {
    graphQLPath: '/graphql',
    playgroundPath: '/playground'
  });

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

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

// Serve the Parse API on the /parse URL prefix
var mountPath = process.env.PARSE_MOUNT || '/parse';
app.use(mountPath, parseServer.app); 
// parseGraphQLServer.applyGraphQL(app); 
// parseGraphQLServer.applyPlayground(app);

// make the Parse Dashboard available at /dashboard
app.use('/dashboard', dashboard);

// Parse Server plays nicely with the rest of your web routes
app.get('/', function(req, res) {
  res.status(200).send('I dream of being a website.  Please star the parse-server repo on GitHub!');
});

var port = process.env.PORT || 1337;
var httpServer = require('http').createServer(app);
httpServer.listen(port, function() {
    console.log('parse-server-example running on port ' + port + '.');
});

// This will enable the Live Query real-time server
ParseServer.createLiveQueryServer(httpServer);

Replace "graphql" version in package-lock.json
Insert "resolutions" to packege.json.
But I still get error message

info: Parse LiveQuery Server starts running
error: Error: Cannot use GraphQLScalarType "Upload" from another module or realm.

Ensure that there is only one instance of "graphql" in the node_modules
directory. If different versions of "graphql" are the dependencies of other
relied on modules, use "resolutions" to ensure only one version is installed.

https://yarnpkg.com/en/docs/selective-version-resolutions

Duplicate "graphql" modules cannot be used at the same time since different
versions may have different capabilities and behavior. The data from one
version used in the function from another could produce confusing and
spurious results.
    at instanceOf (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/jsutils/instanceOf.js:28:13)
    at isScalarType (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/type/definition.js:103:34)
    at isType (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/type/definition.js:86:10)
    at isNullableType (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/type/definition.js:380:10)
    at assertNullableType (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/type/definition.js:384:8)
    at new GraphQLNonNull (/Users/ThuyenBV/Repo/node_modules/parse-server/node_modules/graphql/type/definition.js:345:19)
    at Object.load (/Users/ThuyenBV/Repo/node_modules/parse-server/lib/GraphQL/loaders/filesMutations.js:28:15)
    at Object.load (/Users/ThuyenBV/Repo/node_modules/parse-server/lib/GraphQL/loaders/defaultGraphQLMutations.js:20:18)
    at ParseGraphQLSchema.load (/Users/ThuyenBV/Repo/node_modules/parse-server/lib/GraphQL/ParseGraphQLSchema.js:90:29)
    at process._tickCallback (internal/process/next_tick.js:68:7)` ``
![Screen Shot 2019-09-08 at 10 29 16 PM](https://user-images.githubusercontent.com/5087522/64490544-233b8800-d288-11e9-82f1-9238541d2a57.png)

@davimacedo
Copy link
Member Author

can you please share your package.json?

@buithuyen
Copy link

@davimacedo Here you are. Do I have any mistake?

{
  "name": "parse-server-example",
  "version": "1.4.0",
  "description": "An example Parse API server using the parse-server module",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/ParsePlatform/parse-server-example"
  },
  "license": "MIT",
  "dependencies": {
    "express": "~4.11.x",
    "kerberos": "~0.0.x",
    "parse": "~1.8.0",
    "parse-server": "*",
    "parse-dashboard": "^1.0.24",
    "graphql": "~14.5.4"
  },
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": ">=4.3"
  },
  "resolutions": {
    "graphql": "^14.5.4"
  }
}

@davimacedo
Copy link
Member Author

I could reproduce the error with your current package.json. Then I changed to the one below and I got it working. Can you please try this one?

{
  "name": "parse-server-example",
  "version": "1.4.0",
  "description": "An example Parse API server using the parse-server module",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/ParsePlatform/parse-server-example"
  },
  "license": "MIT",
  "dependencies": {
    "express": "~4.11.x",
    "kerberos": "~0.0.x",
    "parse-dashboard": "^2.0.1",
    "parse-server": "^3.8.0"
  },
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": ">=4.3"
  }
}

@buithuyen
Copy link

Could you deploy the example to Heroku?
I get the message everytime I call graphql

2019-09-09T07:15:21.204301+00:00 app[web.1]: error: Error: Expected Upload to be a GraphQL nullable type.
2019-09-09T07:15:21.204318+00:00 app[web.1]:     at assertNullableType (/app/node_modules/parse-server/node_modules/graphql/type/definition.js:385:11)
2019-09-09T07:15:21.204320+00:00 app[web.1]:     at new GraphQLNonNull (/app/node_modules/parse-server/node_modules/graphql/type/definition.js:345:19)
2019-09-09T07:15:21.204322+00:00 app[web.1]:     at Object.load (/app/node_modules/parse-server/lib/GraphQL/loaders/filesMutations.js:28:15)
2019-09-09T07:15:21.204324+00:00 app[web.1]:     at Object.load (/app/node_modules/parse-server/lib/GraphQL/loaders/defaultGraphQLMutations.js:20:18)
2019-09-09T07:15:21.204326+00:00 app[web.1]:     at ParseGraphQLSchema.load (/app/node_modules/parse-server/lib/GraphQL/ParseGraphQLSchema.js:90:29)
2019-09-09T07:15:21.204329+00:00 app[web.1]:     at runMicrotasks (<anonymous>)
2019-09-09T07:15:21.204331+00:00 app[web.1]:     at processTicksAndRejections (internal/process/task_queues.js:93:5)
2019-09-09T07:15:21.204334+00:00 app[web.1]:     at async ParseGraphQLServer._getGraphQLOptions (/app/node_modules/parse-server/lib/GraphQL/ParseGraphQLServer.js:58:17)
2019-09-09T07:15:21.204335+00:00 app[web.1]:     at async /app/node_modules/parse-server/lib/GraphQL/ParseGraphQLServer.js:92:86

Thank you for your kind help.

@davimacedo
Copy link
Member Author

Do you continue having this error with the package.json I sent you? Can you try to remove the node_modules folder and run npm install again?

@buithuyen
Copy link

It still doesn't work.

@davimacedo
Copy link
Member Author

That's really strange. Can you confirm your Node.js and npm versions?

@buithuyen
Copy link

Node version is v10.16.0
NPM version is 6.11.2

@buithuyen
Copy link

I just remove parse-dashboard and update package.json like that

{
  "name": "parse-server-example",
  "version": "1.4.0",
  "description": "An example Parse API server using the parse-server module",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "https://github.com/ParsePlatform/parse-server-example"
  },
  "license": "MIT",
  "dependencies": {
    "express": "~4.11.x",
    "kerberos": "~0.0.x",
    "parse-server": "^3.8.0"
  },
  "scripts": {
    "start": "node index.js"
  },
  "engines": {
    "node": ">=4.3"
  },
  "resolutions": {
    "graphql": "^14.5.4",
    "**/graphql": "^14.5.4"
  }
}

Everything is OK

UnderratedDev pushed a commit to UnderratedDev/parse-server that referenced this pull request Mar 21, 2020
* GraphQL boilerplate

* Create GraphQL schema without using gql

* Introducing loaders

* Generic create mutation

* create mutation is now working for any data type

* Create mutation for each parse class - partial

* Adding more data types to the class

* Get parse class query

* Generic get query

* Generic delete mutation

* Parse class delete mutation

* Parse class find mutation

* Generic update mutation

* Parse class update mutation

* Fixing initialization problems

* Installing node-fetch again

* Basic implementation for Pointer

* Constructor tests

* API tests boilerplate

* _getGraphQLOptions

* applyGraphQL tests

* GraphQL API initial tests

* applyPlayground tests

* createSubscriptions tests

* ParseGrapjQLSchema tests file

* ParseGraphQLSchema tests

* TypeValidationError

* TypeValidationError

* parseStringValue test

* parseIntValue tests

* parseBooleanValue tests

* parseDateValue tests

* parseValue tests

* parseListValues tests

* parseObjectFields tests

* Default types tests

* Get tests

* First permission test at generic Get operation

* Fixing prepare data

* ApolloClient does not work well with different queries runnning in paralell with different headers

* ApolloClient does not work well with different queries runnning in paralell with different headers

* User 3 tests

* User 3 tests

* Get level permission tests

* Get User specific tests

* Get now support keys argument

* Get now supports include argument

* Get now supports read preferences

* Adding tests for read preference enum type

* Find basic test

* Find permissions test

* Find where argument test

* Order, skip and limit tests

* Error handler

* Find now supports count

* Test for FindResult type

* Improving find count

* Find max limit test

* Find now supports keys, include and includeAll

* Find now supports read preferences

* Basic Create test

* Generic create mutation tests

* Basic update test

* UpdateResult object type test

* Update level permissions tests

* Error handler for default mutations

* Delete mutation basic test

* Delete mutation level permission tests

* Test for string

* String test

* Date test

* Pointer test

* Relation tests

* Changing objects mutations location

* Changing objects queries location

* Create file mutation

* Test for file fields

* Test for null values

* Changing parse classes operations location

* Objects mutations refactoring

* Class specific create object mutation now working

* Update class specific mutation now working

* Specific class delete mutation now working

* Get class specific mutation now working

* Find class specific query now working without where and sort

* Find query for custom classes working with where partially

* Almost all data types working for specfic class find where

* Now only missing relation, geopoint, file and ACL

* Additional tests with Parse classes queries and mutations

* Now only missing relation, geopoint, file and ACL

* Files

* Fiels are now working

* Excluding missing order test temporarly

* Refactoring dates

* Refactoring files

* Default types review

* Refeactoring object queries

* Refactoring class scalar type

* Refactoring class types

* Geo queries are now working

* Fixing centerSphere

* Allow sort on class specific queries

* Supporting bytes

* ACL constraint

* Temporarly removing xit tests

* Fixing some tests because of schema cache

* Removing session token from users

* Parse.User queries and mutations

* Remove test using fit

* Fixing include test that was failing because of schema cache

* Fixing count test for postgres. Postgres does not count with where={} (legacy problem). We should solve it later

* Fix null values test for postgres. It is evaluating null as undefined (legacy problem) and we should fix is later.

* Fixing schema change test that was failing because of schema cache

* Add GraphQL File type parseLiteral tests

* Refeactoring users

* Including sign up mutation

* Fix failing test

* Improve default GraphQL types tests coverage

* Including some tests for data types

* Including additional pointer test:

* Fixing some tests

* more data type tests

* Include Bytes and Polygon data types tests

* Polygons test

* Merging other tests

* Fixing some postgres tests
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants