Skip to content

Commit 71d92ae

Browse files
davimacedodouglasmuraoka
authored andcommitted
GraphQL Custom Schema (#5821)
This PR empowers the Parse GraphQL API with custom user-defined schema. The developers can now write their own types, queries, and mutations, which will merged with the ones that are automatically generated. The new types are resolved by the application's cloud code functions. Therefore, regarding #5777, this PR closes the cloud functions needs and also addresses the graphql customization topic. In my view, I think that this PR, together with #5782 and #5818, when merged, closes the issue. How it works: 1. When initializing ParseGraphQLServer, now the developer can pass a custom schema that will be merged to the auto-generated one: ``` parseGraphQLServer = new ParseGraphQLServer(parseServer, { graphQLPath: '/graphql', graphQLCustomTypeDefs: gql` extend type Query { custom: Custom @namespace } type Custom { hello: String @resolve hello2: String @resolve(to: "hello") userEcho(user: _UserFields!): _UserClass! @resolve } `, }); ``` Note: - This PR includes a @namespace directive that can be used to the top level field of the nested queries and mutations (it basically just returns an empty object); - This PR includes a @resolve directive that can be used to notify the Parse GraphQL Server to resolve that field using a cloud code function. The `to` argument specifies the function name. If the `to` argument is not passed, the Parse GraphQL Server will look for a function with the same name of the field; - This PR allows creating custom types using the auto-generated ones as in `userEcho(user: _UserFields!): _UserClass! @resolve`; - This PR allows to extend the auto-generated types, as in `extend type Query { ... }`. 2. Once the schema was set, you just need to write regular cloud code functions: ``` Parse.Cloud.define('hello', async () => { return 'Hello world!'; }); Parse.Cloud.define('userEcho', async req => { return req.params.user; }); ``` 3. Now you are ready to play with your new custom api: ``` query { custom { hello hello2 userEcho(user: { username: "somefolk" }) { username } } } ``` should return ``` { "data": { "custom": { "hello": "Hello world!", "hello2": "Hello world!", "userEcho": { "username": "somefolk" } } } } ```
1 parent 0b86a86 commit 71d92ae

File tree

6 files changed

+239
-38
lines changed

6 files changed

+239
-38
lines changed

package-lock.json

Lines changed: 12 additions & 33 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"follow-redirects": "1.7.0",
3535
"graphql": "14.4.2",
3636
"graphql-list-fields": "2.0.2",
37+
"graphql-tools": "^4.0.5",
3738
"graphql-upload": "8.0.7",
3839
"intersect": "1.0.1",
3940
"jsonwebtoken": "8.5.1",
@@ -76,6 +77,7 @@
7677
"flow-bin": "0.102.0",
7778
"form-data": "2.5.0",
7879
"gaze": "1.1.3",
80+
"graphql-tag": "^2.10.1",
7981
"husky": "3.0.0",
8082
"jasmine": "3.4.0",
8183
"jsdoc": "3.6.3",

spec/ParseGraphQLServer.spec.js

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,9 @@ describe('ParseGraphQLServer', () => {
189189
});
190190
});
191191

192-
describe('API', () => {
192+
describe('Auto API', () => {
193+
let httpServer;
194+
193195
const headers = {
194196
'X-Parse-Application-Id': 'test',
195197
'X-Parse-Javascript-Key': 'test',
@@ -340,7 +342,7 @@ describe('ParseGraphQLServer', () => {
340342

341343
beforeAll(async () => {
342344
const expressApp = express();
343-
const httpServer = http.createServer(expressApp);
345+
httpServer = http.createServer(expressApp);
344346
expressApp.use('/parse', parseServer.app);
345347
ParseServer.createLiveQueryServer(httpServer, {
346348
port: 1338,
@@ -384,6 +386,10 @@ describe('ParseGraphQLServer', () => {
384386
});
385387
});
386388

389+
afterAll(async () => {
390+
await httpServer.close();
391+
});
392+
387393
describe('GraphQL', () => {
388394
it('should be healthy', async () => {
389395
const health = (await apolloClient.query({
@@ -5243,4 +5249,113 @@ describe('ParseGraphQLServer', () => {
52435249
});
52445250
});
52455251
});
5252+
5253+
describe('Custom API', () => {
5254+
let httpServer;
5255+
const headers = {
5256+
'X-Parse-Application-Id': 'test',
5257+
'X-Parse-Javascript-Key': 'test',
5258+
};
5259+
let apolloClient;
5260+
5261+
beforeAll(async () => {
5262+
const expressApp = express();
5263+
httpServer = http.createServer(expressApp);
5264+
parseGraphQLServer = new ParseGraphQLServer(parseServer, {
5265+
graphQLPath: '/graphql',
5266+
graphQLCustomTypeDefs: gql`
5267+
extend type Query {
5268+
custom: Custom @namespace
5269+
}
5270+
5271+
type Custom {
5272+
hello: String @resolve
5273+
hello2: String @resolve(to: "hello")
5274+
userEcho(user: _UserFields!): _UserClass! @resolve
5275+
}
5276+
`,
5277+
});
5278+
parseGraphQLServer.applyGraphQL(expressApp);
5279+
await new Promise(resolve => httpServer.listen({ port: 13377 }, resolve));
5280+
const httpLink = createUploadLink({
5281+
uri: 'http://localhost:13377/graphql',
5282+
fetch,
5283+
headers,
5284+
});
5285+
apolloClient = new ApolloClient({
5286+
link: httpLink,
5287+
cache: new InMemoryCache(),
5288+
defaultOptions: {
5289+
query: {
5290+
fetchPolicy: 'no-cache',
5291+
},
5292+
},
5293+
});
5294+
});
5295+
5296+
afterAll(async () => {
5297+
await httpServer.close();
5298+
});
5299+
5300+
it('can resolve a custom query using default function name', async () => {
5301+
Parse.Cloud.define('hello', async () => {
5302+
return 'Hello world!';
5303+
});
5304+
5305+
const result = await apolloClient.query({
5306+
query: gql`
5307+
query Hello {
5308+
custom {
5309+
hello
5310+
}
5311+
}
5312+
`,
5313+
});
5314+
5315+
expect(result.data.custom.hello).toEqual('Hello world!');
5316+
});
5317+
5318+
it('can resolve a custom query using function name set by "to" argument', async () => {
5319+
Parse.Cloud.define('hello', async () => {
5320+
return 'Hello world!';
5321+
});
5322+
5323+
const result = await apolloClient.query({
5324+
query: gql`
5325+
query Hello {
5326+
custom {
5327+
hello2
5328+
}
5329+
}
5330+
`,
5331+
});
5332+
5333+
expect(result.data.custom.hello2).toEqual('Hello world!');
5334+
});
5335+
5336+
it('should resolve auto types', async () => {
5337+
Parse.Cloud.define('userEcho', async req => {
5338+
return req.params.user;
5339+
});
5340+
5341+
const result = await apolloClient.query({
5342+
query: gql`
5343+
query UserEcho($user: _UserFields!) {
5344+
custom {
5345+
userEcho(user: $user) {
5346+
username
5347+
}
5348+
}
5349+
}
5350+
`,
5351+
variables: {
5352+
user: {
5353+
username: 'somefolk',
5354+
},
5355+
},
5356+
});
5357+
5358+
expect(result.data.custom.userEcho.username).toEqual('somefolk');
5359+
});
5360+
});
52465361
});

0 commit comments

Comments
 (0)