Skip to content

Commit

Permalink
Add config.graphql.apolloConfig (#4912)
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie authored Mar 16, 2021
1 parent ff92921 commit d31acf6
Show file tree
Hide file tree
Showing 8 changed files with 91 additions and 35 deletions.
7 changes: 7 additions & 0 deletions .changeset/tricky-fans-serve.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@keystone-next/website': minor
'@keystone-next/keystone': minor
'@keystone-next/types': minor
---

Added a `config.graphql.apolloConfig` option to allow developers to configure the `ApolloServer` object provided by Keystone.
3 changes: 3 additions & 0 deletions docs-next/pages/apis/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -267,11 +267,14 @@ Options:

- `queryLimits` (default: `undefined`): Allows you to limit the total number of results returned from a query to your GraphQL API.
See also the per-list `graphql.queryLimits` option in the [Schema API](./schema).
- `apolloConfig` (default: `undefined`): Allows you to pass extra options into the `ApolloServer` constructor.
See the [Apollo docs](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructor) for supported options.

```typescript
export default config({
graphql: {
queryLimits: { maxTotalResults: 100 },
apolloConfig: { /* ... */ },
},
/* ... */
});
Expand Down
1 change: 1 addition & 0 deletions packages-next/admin-ui/src/templates/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const apolloServer = createApolloServerMicro({
graphQLSchema,
createContext,
sessionStrategy: initializedKeystoneConfig.session ? initializedKeystoneConfig.session() : undefined,
apolloConfig: config.graphql?.apolloConfig,
connectionPromise: keystone.connect(),
});
Expand Down
1 change: 1 addition & 0 deletions packages-next/keystone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"@types/uid-safe": "^2.1.2",
"apollo-server-express": "^2.21.1",
"apollo-server-micro": "^2.21.1",
"apollo-server-types": "^0.6.3",
"cookie": "^0.4.1",
"cors": "^2.8.5",
"express": "^4.17.1",
Expand Down
87 changes: 54 additions & 33 deletions packages-next/keystone/src/lib/createApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { IncomingMessage, ServerResponse } from 'http';
import { GraphQLSchema } from 'graphql';
import { ApolloServer as ApolloServerMicro } from 'apollo-server-micro';
import { ApolloServer as ApolloServerExpress } from 'apollo-server-express';
import type { Config } from 'apollo-server-express';

// @ts-ignore
import { formatError } from '@keystone-next/keystone-legacy/lib/Keystone/format-error';
Expand All @@ -12,52 +13,73 @@ export const createApolloServerMicro = ({
graphQLSchema,
createContext,
sessionStrategy,
apolloConfig,
connectionPromise,
}: {
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy<any>;
apolloConfig?: Config;
connectionPromise: Promise<any>;
}) => {
return new ApolloServerMicro({
uploads: false,
schema: graphQLSchema,
playground: { settings: { 'request.credentials': 'same-origin' } },
formatError,
context: async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
await connectionPromise;
return createContext({
sessionContext: sessionStrategy
? await createSessionContext(sessionStrategy, req, res, createContext)
: undefined,
req,
});
},
});
const context = async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) => {
await connectionPromise;
return createContext({
sessionContext: sessionStrategy
? await createSessionContext(sessionStrategy, req, res, createContext)
: undefined,
req,
});
};
const serverConfig = _createApolloServerConfig({ graphQLSchema, apolloConfig });
return new ApolloServerMicro({ ...serverConfig, context });
};

export const createApolloServerExpress = ({
graphQLSchema,
createContext,
sessionStrategy,
apolloConfig,
}: {
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy<any>;
apolloConfig?: Config;
}) => {
return new ApolloServerExpress({
const context = async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) =>
createContext({
sessionContext: sessionStrategy
? await createSessionContext(sessionStrategy, req, res, createContext)
: undefined,
req,
});
const serverConfig = _createApolloServerConfig({ graphQLSchema, apolloConfig });
return new ApolloServerExpress({ ...serverConfig, context });
};

const _createApolloServerConfig = ({
graphQLSchema,
apolloConfig,
}: {
graphQLSchema: GraphQLSchema;
apolloConfig?: Config;
}) => {
// Playground config
const pp = apolloConfig?.playground;
let playground: Config['playground'];
const settings = { 'request.credentials': 'same-origin' };
if (typeof pp === 'boolean' && !pp) {
playground = undefined;
} else if (typeof pp === 'undefined' || typeof pp === 'boolean') {
playground = { settings };
} else {
playground = { ...pp, settings: { ...settings, ...pp.settings } };
}

return {
uploads: false,
schema: graphQLSchema,
// FIXME: allow the dev to control where/when they get a playground
playground: { settings: { 'request.credentials': 'same-origin' } },
formatError, // TODO: this needs to be discussed
context: async ({ req, res }: { req: IncomingMessage; res: ServerResponse }) =>
createContext({
sessionContext: sessionStrategy
? await createSessionContext(sessionStrategy, req, res, createContext)
: undefined,
req,
}),
// FIXME: support for apollo studio tracing
// ...(process.env.ENGINE_API_KEY || process.env.APOLLO_KEY
// ? { tracing: true }
Expand All @@ -68,12 +90,11 @@ export const createApolloServerExpress = ({
// // disabled.
// tracing: dev,
// }),
// FIXME: Support for generic custom apollo configuration
// ...apolloConfig,
});
// FIXME: Support custom API path via config.graphql.path.
// Note: Core keystone uses '/admin/api' as the default.
// FIXME: Support for file handling configuration
// maxFileSize: 200 * 1024 * 1024,
// maxFiles: 5,
...apolloConfig,
// Carefully inject the playground
playground,
// FIXME: Support for file handling configuration
// maxFileSize: 200 * 1024 * 1024,
// maxFiles: 5,
};
};
20 changes: 18 additions & 2 deletions packages-next/keystone/src/lib/createExpressServer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Config } from 'apollo-server-express';
import cors, { CorsOptions } from 'cors';
import express from 'express';
import { GraphQLSchema } from 'graphql';
Expand All @@ -11,14 +12,23 @@ const addApolloServer = ({
graphQLSchema,
createContext,
sessionStrategy,
apolloConfig,
}: {
server: express.Express;
graphQLSchema: GraphQLSchema;
createContext: CreateContext;
sessionStrategy?: SessionStrategy<any>;
apolloConfig?: Config;
}) => {
const apolloServer = createApolloServerExpress({ graphQLSchema, createContext, sessionStrategy });
const apolloServer = createApolloServerExpress({
graphQLSchema,
createContext,
sessionStrategy,
apolloConfig,
});
server.use(graphqlUploadExpress());
// FIXME: Support custom API path via config.graphql.path.
// Note: Core keystone uses '/admin/api' as the default.
apolloServer.applyMiddleware({ app: server, path: '/api/graphql', cors: false });
};

Expand All @@ -45,7 +55,13 @@ export const createExpressServer = async (
const sessionStrategy = config.session ? config.session() : undefined;

if (isVerbose) console.log('✨ Preparing GraphQL Server');
addApolloServer({ server, graphQLSchema, createContext, sessionStrategy });
addApolloServer({
server,
graphQLSchema,
createContext,
sessionStrategy,
apolloConfig: config.graphql?.apolloConfig,
});

if (config.ui?.isDisabled) {
if (isVerbose) console.log('✨ Skipping Admin UI app');
Expand Down
1 change: 1 addition & 0 deletions packages-next/types/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"node": ">=10.0.0"
},
"dependencies": {
"apollo-server-types": "^0.6.3",
"cors": "^2.8.5",
"graphql": "^15.5.0"
},
Expand Down
6 changes: 6 additions & 0 deletions packages-next/types/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ConnectOptions } from 'mongoose';
import { CorsOptions } from 'cors';
import type { GraphQLSchema } from 'graphql';
import { IncomingMessage } from 'http';
import type { Config } from 'apollo-server-express';

import type { ListHooks } from './hooks';
import type { ListAccessControl, FieldAccessControl } from './access-control';
Expand Down Expand Up @@ -121,6 +122,11 @@ export type GraphQLConfig = {
queryLimits?: {
maxTotalResults?: number;
};
/**
* Additional options to pass into the ApolloServer constructor.
* @see https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructor
*/
apolloConfig?: Config;
};

// config.extendGraphqlSchema
Expand Down

1 comment on commit d31acf6

@vercel
Copy link

@vercel vercel bot commented on d31acf6 Mar 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.