Skip to content

Commit

Permalink
Move gui option into constructor and use playground (#1297)
Browse files Browse the repository at this point in the history
* Add a wider diversity of `gui` options

Although I know we want to remain less tied to the GraphQL Playground
GUI options, we definitely want to support a wider variety of options to
be passed in. This adds support for specifying partial options either
statically or dynamically for the gui, which can be extended to allow
for a wider array of guis than only GraphQL playground.

* Add boolean option and configuration for tabs

* move gui setting into ApolloServer Constructor

* document playground configuration in the constructor

* update playground types and fixed micro + koa integrations

* change gui to playground

* docs: change gui to playground

* fix logic for playground creation
  • Loading branch information
zionts authored and evans committed Jul 11, 2018
1 parent f055d28 commit 11b8671
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 98 deletions.
4 changes: 4 additions & 0 deletions docs/source/api/apollo-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ new ApolloServer({

Enables and disables schema introspection

* `playground`: <`Boolean`> | <`Object`>

Enables and disables playground and allows configuration of GraphQL Playground. The options can be found on GraphQL Playground's [documentation](https://github.com/prismagraphql/graphql-playground/#usage)

* `debug`: <`Boolean`>

Enables and disables development mode helpers. Defaults to `true`
Expand Down
51 changes: 29 additions & 22 deletions docs/source/features/playground.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,51 @@ title: GraphQL Playground
description: Visually exploring a Apollo Server
---

[GraphQL Playground](https://github.com/prismagraphql/graphql-playground) is a graphical interactive in-browser GraphQL IDE, created by [Prisma](https://www.prisma.io/), based on [GraphiQL](https://github.com/graphql/graphiql). In development, Apollo Server collocates a GraphQL Playground instance with the GraphQL path. When a browser sends a request to Apollo Server, it receives the GraphQL Playground gui. When `NODE_ENV` is set to production, introspection and Playground are disabled as a production best practice.
[GraphQL Playground](https://github.com/prismagraphql/graphql-playground) is a graphical interactive in-browser GraphQL IDE, created by [Prisma](https://www.prisma.io/), based on [GraphiQL](https://github.com/graphql/graphiql). In development, Apollo Server collocates a GraphQL Playground instance with the GraphQL path. When a browser sends a request to Apollo Server, it receives GraphQL Playground. When `NODE_ENV` is set to production, introspection and Playground are disabled as a production best practice.

<div align="center">
![GraphQL Playground](../images/playground.png)
</div>

## Enabling Playground in Production

To enable Playground in production, an integration package must be installed to provide more control over the middlewares used. The following example uses the express integration:

```bash
npm install --save apollo-server-express@rc graphql
## Configuring Playground

The Apollo Server constructor contains the ability to configure GraphQL Playground with the `playground` configuration option. The options can be found on GraphQL Playground's [documentation](https://github.com/prismagraphql/graphql-playground/#usage)

```js
new ApolloServer({
typeDefs,
resolvers,
playground: {
settings: {
'editor.theme': 'light',
},
tabs: [
{
endpoint,
query: defaultQuery,
},
],
},
});
```

Introspection and the gui can be enabled explicitly in the following manner.
## Enabling Playground in Production

To enable Playground in production, introspection and the playground can be enabled explicitly in the following manner.

```js line=8,16
const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
```js line=7-8
const { ApolloServer } = require('apollo-server');
const { typeDefs, resolvers } = require('./schema');

const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
playground: true,
});

const app = express();

// gui accepts a Playground configuration
server.applyMiddleware({
app,
gui: true,
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});

app.listen({ port: 4000 }, () =>
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`),
);
```

> Note: when using apollo-server-express, you can remove apollo-server from your package.json
16 changes: 11 additions & 5 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ import { FormatErrorExtension } from './formatters';

import { gql } from './index';

import {
createPlaygroundOptions,
PlaygroundRenderPageOptions,
} from './playground';

const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
if (node.name.value === '__schema' || node.name.value === '__type') {
Expand All @@ -64,14 +69,12 @@ export class ApolloServerBase {
protected subscriptionServerOptions?: SubscriptionServerOptions;
protected uploadsConfig?: FileUploadOptions;

// This specifies the version of GraphQL Playground that will be served
// from graphql-playground-html, and is passed to renderPlaygroundPage
// by the integration subclasses
protected playgroundVersion = '1.7.2';

// set by installSubscriptionHandlers.
private subscriptionServer?: SubscriptionServer;

// the default version is specified in playground.ts
protected playgroundOptions?: PlaygroundRenderPageOptions;

// The constructor should be universal across all environments. All environment specific behavior should be set by adding or overriding methods
constructor(config: Config) {
if (!config) throw new Error('ApolloServer requires options.');
Expand All @@ -87,6 +90,7 @@ export class ApolloServerBase {
engine,
subscriptions,
uploads,
playground,
...requestOptions
} = config;

Expand Down Expand Up @@ -235,6 +239,8 @@ export class ApolloServerBase {
);
}
}

this.playgroundOptions = createPlaygroundOptions(playground);
}

// used by integrations to synchronize the path with subscriptions, some
Expand Down
2 changes: 2 additions & 0 deletions packages/apollo-server-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export {

export { convertNodeHttpToRequest } from './nodeHttpToRequest';

export { createPlaygroundOptions } from './playground';

// ApolloServer Base class
export { ApolloServerBase } from './ApolloServer';
export * from './types';
Expand Down
52 changes: 52 additions & 0 deletions packages/apollo-server-core/src/playground.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {
RenderPageOptions as PlaygroundRenderPageOptions,
Theme,
} from '@apollographql/graphql-playground-html/dist/render-playground-page';
export {
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html/dist/render-playground-page';

// This specifies the version of GraphQL Playground that will be served
// from graphql-playground-html, and is passed to renderPlaygroundPage
// by the integration subclasses
const playgroundVersion = '1.7.2';

export type PlaygroundConfig = Partial<PlaygroundRenderPageOptions> | boolean;

export const defaultPlaygroundOptions = {
version: playgroundVersion,
settings: {
'general.betaUpdates': false,
'editor.theme': 'dark' as Theme,
'editor.reuseHeaders': true,
'tracing.hideTracingResponse': true,
'editor.fontSize': 14,
'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,
'request.credentials': 'omit',
},
};

export function createPlaygroundOptions(
playground: PlaygroundConfig = {},
): PlaygroundRenderPageOptions | undefined {
const isDev = process.env.NODE_ENV !== 'production';
const enabled: boolean = typeof playground === 'boolean' ? playground : isDev;

if (!enabled) {
return undefined;
}

const playgroundOverrides =
typeof playground === 'boolean' ? {} : playground || {};

const playgroundOptions: PlaygroundRenderPageOptions = {
...defaultPlaygroundOptions,
...playgroundOverrides,
settings: {
...defaultPlaygroundOptions.settings,
...playgroundOverrides.settings,
},
};

return playgroundOptions;
}
13 changes: 6 additions & 7 deletions packages/apollo-server-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { SchemaDirectiveVisitor, IResolvers, IMocks } from 'graphql-tools';
import { ConnectionContext } from 'subscriptions-transport-ws';
import * as WebSocket from 'ws';
import { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingOptions } from 'apollo-engine-reporting';
export { GraphQLExtension } from 'graphql-extensions';

import { EngineReportingOptions } from 'apollo-engine-reporting';

import { PlaygroundConfig } from './playground';
export { PlaygroundConfig, PlaygroundRenderPageOptions } from './playground';

import {
GraphQLServerOptions as GraphQLOptions,
PersistedQueryOptions,
Expand Down Expand Up @@ -59,6 +63,7 @@ export interface Config
subscriptions?: Partial<SubscriptionServerOptions> | string | false;
//https://github.com/jaydenseric/apollo-upload-server#options
uploads?: boolean | FileUploadOptions;
playground?: PlaygroundConfig;
}

export interface FileUploadOptions {
Expand All @@ -69,9 +74,3 @@ export interface FileUploadOptions {
//Max allowed number of files (default: Infinity).
maxFiles?: number;
}

export interface MiddlewareOptions {
path?: string;
gui?: boolean;
subscriptions?: boolean;
}
99 changes: 97 additions & 2 deletions packages/apollo-server-express/src/ApolloServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('apollo-server-express', () => {
// XXX Unclear why this would be something somebody would want (vs enabling
// introspection without graphql-playground, which seems reasonable, eg you
// have your own graphql-playground setup with a custom link)
it('can enable gui separately from introspection during production', async () => {
it('can enable playground separately from introspection during production', async () => {
const INTROSPECTION_QUERY = `
{
__schema {
Expand Down Expand Up @@ -146,7 +146,7 @@ describe('apollo-server-express', () => {
});
});

it('renders GraphQL playground when browser requests', async () => {
it('renders GraphQL playground by default when browser requests', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

Expand Down Expand Up @@ -179,6 +179,101 @@ describe('apollo-server-express', () => {
});
});

it('accepts partial GraphQL Playground Options', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

const defaultQuery = 'query { foo { bar } }';
const endpoint = '/fumanchupacabra';
const { url } = await createServer(
{
typeDefs,
resolvers,
playground: {
// https://github.com/apollographql/graphql-playground/blob/0e452d2005fcd26f10fbdcc4eed3b2e2af935e3a/packages/graphql-playground-html/src/render-playground-page.ts#L16-L24
// must be made partial
settings: {
'editor.theme': 'light',
} as any,
tabs: [
{
query: defaultQuery,
},
{
endpoint,
} as any,
],
},
},
{},
);

return new Promise<http.Server>((resolve, reject) => {
request(
{
url,
method: 'GET',
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
Folo: 'bar',
},
},
(error, response, body) => {
process.env.NODE_ENV = nodeEnv;
if (error) {
reject(error);
} else {
console.log('body', body);
expect(body).to.contain('GraphQLPlayground');
expect(body).to.contain(`"editor.theme": "light"`);
expect(body).to.contain(defaultQuery);
expect(body).to.contain(endpoint);
expect(response.statusCode).to.equal(200);
resolve();
}
},
);
});
});

it('accepts playground options as a boolean', async () => {
const nodeEnv = process.env.NODE_ENV;
delete process.env.NODE_ENV;

const { url } = await createServer(
{
typeDefs,
resolvers,
playground: false,
},
{},
);

return new Promise<http.Server>((resolve, reject) => {
request(
{
url,
method: 'GET',
headers: {
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
},
},
(error, response, body) => {
process.env.NODE_ENV = nodeEnv;
if (error) {
reject(error);
} else {
expect(body).not.to.contain('GraphQLPlayground');
expect(response.statusCode).not.to.equal(200);
resolve();
}
},
);
});
});

it('accepts cors configuration', async () => {
const { url: uri } = await createServer(
{
Expand Down
Loading

0 comments on commit 11b8671

Please sign in to comment.