Skip to content

Commit

Permalink
Allow an optional function to resolve the rootValue (#1555)
Browse files Browse the repository at this point in the history
* Allow an optional function to resolve the rootValue

Passes the parsed DocumentNode AST to determine the root value,
useful when providing a different rootValue for query vs mutation

* Add API docs for rootValue
  • Loading branch information
tgriesser authored and evans committed Sep 20, 2018
1 parent 3b21ade commit 4175f1b
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ All of the packages in the `apollo-server` repo are released with the same versi

### vNEXT

- Allow an optional function to resolve the `rootValue`, passing the `DocumentNode` AST to determine the value. [PR #1555](https://github.com/apollographql/apollo-server/pull/1555)
- Follow-up on the work in [PR #1516](https://github.com/apollographql/apollo-server/pull/1516) to also fix missing insertion cursor/caret when a custom GraphQL configuration is specified which doesn't specify its own `cursorShape` property. [PR #1607](https://github.com/apollographql/apollo-server/pull/1607)

### v2.1.0
Expand Down
15 changes: 15 additions & 0 deletions docs/source/api/apollo-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ new ApolloServer({
authScope: getScope(req.headers.authorization)
}),
});
```

* `rootValue`: <`Any`> | <`Function`>

A value or function called with the parsed `Document`, creating the root value passed to the graphql executor. The function is useful if you wish to provide a different root value based on the query operation type.

```js
new ApolloServer({
typeDefs,
resolvers,
rootValue: (documentAST) => ({
const op = getOperationAST(documentNode)
return op === 'mutation' ? mutationRoot : queryRoot;
})
});
```

* `mocks`: <`Object`> | <`Boolean`>
Expand Down
17 changes: 17 additions & 0 deletions packages/apollo-server-core/src/__tests__/runQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
GraphQLInt,
GraphQLNonNull,
parse,
DocumentNode,
} from 'graphql';

import { runQuery } from '../runQuery';
Expand Down Expand Up @@ -175,6 +176,22 @@ describe('runQuery', () => {
});
});

it('correctly evaluates a rootValue function', () => {
const query = `{ testRootValue }`;
const expected = { testRootValue: 'it also works' };
return runQuery({
schema,
queryString: query,
rootValue: (doc: DocumentNode) => {
expect(doc.kind).toEqual('Document');
return 'it also';
},
request: new MockReq(),
}).then(res => {
expect(res.data).toEqual(expected);
});
});

it('correctly passes in the context', () => {
const query = `{ testContextValue }`;
const expected = { testContextValue: 'it still works' };
Expand Down
8 changes: 5 additions & 3 deletions packages/apollo-server-core/src/graphqlOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
GraphQLSchema,
ValidationContext,
GraphQLFieldResolver,
DocumentNode,
} from 'graphql';
import { GraphQLExtension } from 'graphql-extensions';
import { CacheControlExtensionOptions } from 'apollo-cache-control';
Expand All @@ -13,7 +14,7 @@ import { DataSource } from 'apollo-datasource';
*
* - schema: an executable GraphQL schema used to fulfill requests.
* - (optional) formatError: Formatting function applied to all errors before response is sent
* - (optional) rootValue: rootValue passed to GraphQL execution
* - (optional) rootValue: rootValue passed to GraphQL execution, or a function to resolving the rootValue from the DocumentNode
* - (optional) context: the context passed to GraphQL execution
* - (optional) validationRules: extra validation rules applied to requests
* - (optional) formatResponse: a function applied to each graphQL execution result
Expand All @@ -25,11 +26,12 @@ import { DataSource } from 'apollo-datasource';
export interface GraphQLServerOptions<
TContext =
| (() => Promise<Record<string, any>> | Record<string, any>)
| Record<string, any>
| Record<string, any>,
TRootVal = ((parsedQuery: DocumentNode) => any) | any
> {
schema: GraphQLSchema;
formatError?: Function;
rootValue?: any;
rootValue?: TRootVal;
context?: TContext;
validationRules?: Array<(context: ValidationContext) => any>;
formatResponse?: Function;
Expand Down
7 changes: 5 additions & 2 deletions packages/apollo-server-core/src/runQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export interface QueryOptions {
// a mutation), throw this error.
nonQueryError?: Error;

rootValue?: any;
rootValue?: ((parsedQuery: DocumentNode) => any) | any;
context?: any;
variables?: { [key: string]: any };
operationName?: string;
Expand Down Expand Up @@ -222,7 +222,10 @@ function doRunQuery(options: QueryOptions): Promise<GraphQLResponse> {
const executionArgs: ExecutionArgs = {
schema: options.schema,
document: documentAST,
rootValue: options.rootValue,
rootValue:
typeof options.rootValue === 'function'
? options.rootValue(documentAST)
: options.rootValue,
contextValue: context,
variableValues: options.variables,
operationName: options.operationName,
Expand Down
36 changes: 36 additions & 0 deletions packages/apollo-server-integration-testsuite/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
GraphQLScalarType,
introspectionQuery,
BREAK,
DocumentNode,
getOperationAST,
} from 'graphql';

import request = require('supertest');
Expand Down Expand Up @@ -842,6 +844,40 @@ export default (createApp: CreateAppFunc, destroyApp?: DestroyAppFunc) => {
});
});

it('passes the rootValue function result to the resolver', async () => {
const expectedQuery = 'query: it passes rootValue';
const expectedMutation = 'mutation: it passes rootValue';
app = await createApp({
graphqlOptions: {
schema,
rootValue: (documentNode: DocumentNode) => {
const op = getOperationAST(documentNode, undefined);
return op.operation === 'query'
? expectedQuery
: expectedMutation;
},
},
});
const queryReq = request(app)
.post('/graphql')
.send({
query: 'query test{ testRootValue }',
});
return queryReq.then(res => {
expect(res.status).toEqual(200);
expect(res.body.data.testRootValue).toEqual(expectedQuery);
});
const mutationReq = request(app)
.post('/graphql')
.send({
query: 'mutation test{ testMutation(echo: "ping") }',
});
return mutationReq.then(res => {
expect(res.status).toEqual(200);
expect(res.body.data.testRootValue).toEqual(expectedMutation);
});
});

it('returns errors', async () => {
const expected = 'Secret error message';
app = await createApp({
Expand Down

0 comments on commit 4175f1b

Please sign in to comment.