Skip to content

Commit

Permalink
Integrations export their own ApolloServer (#1161)
Browse files Browse the repository at this point in the history
* feat: ApolloServer is created and exported by all variants

* docs: add initial docs around exporting ApolloServer

* feat: export gql from integrations

* docs: change apollo-server examples to use express with registerServer

* server: remove registerExpressServer

* core, docs: comment functions, fix api reference, context creation more like middleware args

* docs: fix integrationed typo
  • Loading branch information
evans authored Jun 13, 2018
1 parent df8e487 commit ba31cf7
Show file tree
Hide file tree
Showing 16 changed files with 121 additions and 48 deletions.
15 changes: 9 additions & 6 deletions docs/source/api/apollo-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ new ApolloServer({

## registerServer

The `registerServer` method is from `apollo-server-express`. Middleware registration has been greatly simplified with this new method.
The `registerServer` method is provided by all the `apollo-server-{integration}` packages. This function connects ApolloServer to a specific framework. Use it instead of the `listen` method to customize the app's web behavior.

### Parameters

* `options`: <`Object`>

* `app`: <`HttpServer`> _(required)_

Pass the handle to your nexpress server here.
Pass an instance of the server integration here.

* `server`: <`ApolloServer`> _(required)_

Expand All @@ -133,13 +133,16 @@ The `registerServer` method is from `apollo-server-express`. Middleware registra

Specify a custom path. It defaults to `/graphql` if no path is specified.

* `cors`: <`Object`>
* `cors`: <`Object`> ([express](https://github.com/expressjs/cors#cors), [hapi](https://hapijs.com/api#-routeoptionscors))
Pass the integration-specific cors options.

Pass the cors options.
* `bodyParser`: <`Object`> ([express](https://github.com/expressjs/body-parser#body-parser))

Pass the body-parser options.

### Usage

The `registerServer` method from `apollo-server-express` allows you to easily register your middleware as shown in the example below:
The `registerServer` method from `apollo-server-express` registration of middleware as shown in the example below:

```js
const { ApolloServer } = require('apollo-server');
Expand All @@ -166,7 +169,7 @@ In the case of GraphQL, the `gql` tag is used to surround GraphQL operation and

### Usage

Import the `gql` template literal tag into the current context from the `apollo-server` module:
Import the `gql` template literal tag into the current context from the `apollo-server` or `apollo-server-{integration}` modules:

```js
const { gql } = require('apollo-server');
Expand Down
3 changes: 1 addition & 2 deletions docs/source/essentials/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ For existing applications, we'll pass it into the `registerServer` method as `ap
> The existing application is frequently already named `app`, especially when using Express. If the application is identified by a different variable, pass the existing variable in place of `app`.
```js
const { ApolloServer, gql } = require('apollo-server');
const { registerServer } = require('apollo-server-express');
const { ApolloServer, gql, registerServer } = require('apollo-server-express');
const { typeDefs, resolvers } = require('./schema');

const server = new ApolloServer({
Expand Down
9 changes: 4 additions & 5 deletions docs/source/migration-two-dot.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,7 @@ Now, you can just do this instead:

```js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server');
const { registerServer } = require('apollo-server-express');
const { ApolloServer, gql, registerServer } = require('apollo-server-express');

const app = express();

Expand All @@ -131,7 +130,7 @@ server.listen().then(({ url }) => {

<h2 id="Stand-alone">Stand-alone</h2>

If you are simply focused on running a production-ready GraphQL server quickly, Apollo Server 2.0 ships with a built-in server and starting your own server (e.g. Express, Koa, etc.) is no longer necessary.
For starting a production-ready GraphQL server quickly, Apollo Server 2.0 ships with a built-in server, so starting a server (e.g. Express, Koa, etc.) is no longer necessary.

For these cases, it's possible to remove the existing `apollo-server-{integrations}` package and add the new `apollo-server` beta. If using Express, this can be done by running:

Expand Down Expand Up @@ -166,14 +165,14 @@ server.listen().then(({ url }) => {
});
```


<h2 id="add-middleware">Adding Additional Middleware to Apollo Server 2</h2>

For middleware that is collocated with the GraphQL endpoint, Apollo Server 2 allows middleware mounted on the same path before `registerServer` is called. For example, this server runs an authentication middleware before GraphQL execution.

```js
const express = require('express');
const { ApolloServer, gql } = require('apollo-server');
const { registerServer } = require('apollo-server-express');
const { ApolloServer, gql, registerServer } = require('apollo-server-express');

const app = express();
const path = '/graphql';
Expand Down
11 changes: 10 additions & 1 deletion packages/apollo-server-cloudflare/src/ApolloServer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { graphqlCloudflare } from './cloudflareApollo';

import { ApolloServerBase } from 'apollo-server-core';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions } from 'apollo-server-core';

export class ApolloServer extends ApolloServerBase {
//This translates the arguments from the middleware into graphQL options It
//provides typings for the integration specific behavior, ideally this would
//be propagated with a generic to the super class
async createGraphQLServerOptions(request: Request): Promise<GraphQLOptions> {
return super.graphQLServerOptions({ request });
}

public async listen() {
const graphql = this.graphQLServerOptionsForRequest.bind(this);
const graphql = this.createGraphQLServerOptions.bind(this);
addEventListener('fetch', (event: FetchEvent) => {
event.respondWith(graphqlCloudflare(graphql)(event.request));
});
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-server-cloudflare/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { graphqlCloudflare } from './cloudflareApollo';
export { ApolloServer } from './ApolloServer';
export { gql } from 'apollo-server-core';
4 changes: 2 additions & 2 deletions packages/apollo-server-core/src/ApolloServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const schema = new GraphQLSchema({
query: queryType,
});

function createHttpServer(server) {
function createHttpServer(server: ApolloServerBase) {
return http.createServer(async (req, res) => {
let body: any = [];
req
Expand All @@ -79,7 +79,7 @@ function createHttpServer(server) {
// do whatever we need to in order to respond to this request.
runHttpQuery([req, res], {
method: req.method,
options: server.graphQLServerOptionsForRequest(req as any),
options: (server as any).graphQLServerOptions({ req, res }),
query:
req.method.toUpperCase() === 'GET'
? url.parse(req.url, true)
Expand Down
18 changes: 11 additions & 7 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ const NoIntrospection = (context: ValidationContext) => ({
},
});

export class ApolloServerBase<Request = RequestInit> {
export class ApolloServerBase {
public disableTools: boolean;
// set in the listen function if subscriptions are enabled
public subscriptionsPath: string;
Expand All @@ -78,6 +78,7 @@ export class ApolloServerBase<Request = RequestInit> {
private subscriptionServer?: SubscriptionServer;
protected getHttp: () => HttpServer;

//The constructor should be universal across all environments. All environment specific behavior should be set in an exported registerServer or in by overriding listen
constructor(config: Config) {
const {
context,
Expand Down Expand Up @@ -400,15 +401,18 @@ const typeDefs = gql\`${startSchema}\`
}
}

async graphQLServerOptionsForRequest(request: Request) {
let context: Context = this.context ? this.context : { request };
//This function is used by the integrations to generate the graphQLOptions
//from an object containing the request and other integration specific
//options
protected async graphQLServerOptions(
integrationContextArgument?: Record<string, any>,
) {
let context: Context = this.context ? this.context : {};

try {
context =
typeof this.context === 'function'
? await this.context({
req: request,
})
? await this.context(integrationContextArgument || {})
: context;
} catch (error) {
//Defer context error resolution to inside of runQuery
Expand All @@ -432,6 +436,6 @@ const typeDefs = gql\`${startSchema}\`
any
>,
...this.requestOptions,
};
} as GraphQLOptions;
}
}
1 change: 1 addition & 0 deletions packages/apollo-server-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Server as HttpServer } from 'http';
import { ListenOptions as HttpListenOptions } from 'net';
import { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingOptions } from 'apollo-engine-reporting';
export { GraphQLExtension } from 'graphql-extensions';

import {
GraphQLServerOptions as GraphQLOptions,
Expand Down
9 changes: 6 additions & 3 deletions packages/apollo-server-express/src/ApolloServer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ const resolvers = {
describe('apollo-server-express', () => {
//to remove the circular dependency, we reference it directly
const ApolloServer = require('../../apollo-server/dist/index').ApolloServer;
let server: ApolloServerBase<express.Request>;
let server: ApolloServerBase & {
createGraphQLServerOptions: (
req: express.Request,
res: express.Response,
) => any;
};
let app: express.Application;

afterEach(async () => {
Expand Down Expand Up @@ -153,8 +158,6 @@ describe('apollo-server-express', () => {
});

describe('healthchecks', () => {
let server: ApolloServerBase<express.Request>;

afterEach(async () => {
await server.stop();
});
Expand Down
21 changes: 18 additions & 3 deletions packages/apollo-server-express/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,26 @@ import {
GraphQLUpload,
} from 'apollo-upload-server';

export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions } from 'apollo-server-core';

const gql = String.raw;

export class ApolloServer extends ApolloServerBase {
//This translates the arguments from the middleware into graphQL options It
//provides typings for the integration specific behavior, ideally this would
//be propagated with a generic to the super class
async createGraphQLServerOptions(
req: express.Request,
res: express.Response,
): Promise<GraphQLOptions> {
return super.graphQLServerOptions({ req, res });
}
}

export interface ServerRegistration {
app: express.Application;
server: ApolloServerBase<express.Request>;
server: ApolloServer;
path?: string;
cors?: corsMiddleware.CorsOptions;
bodyParserConfig?: OptionsJson;
Expand All @@ -29,7 +44,7 @@ export interface ServerRegistration {

const fileUploadMiddleware = (
uploadsConfig: Record<string, any>,
server: ApolloServerBase<express.Request>,
server: ApolloServerBase,
) => (
req: express.Request,
res: express.Response,
Expand Down Expand Up @@ -133,7 +148,7 @@ export const registerServer = async ({
})(req, res, next);
}
}
return graphqlExpress(server.graphQLServerOptionsForRequest.bind(server))(
return graphqlExpress(server.createGraphQLServerOptions.bind(server))(
req,
res,
next,
Expand Down
4 changes: 2 additions & 2 deletions packages/apollo-server-express/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Expose types which can be used by both middleware flavors.
export { GraphQLOptions } from 'apollo-server-core';
export { GraphQLOptions, gql } from 'apollo-server-core';
export {
ApolloError,
toApolloError,
Expand All @@ -22,4 +22,4 @@ export {
export { graphqlConnect, graphiqlConnect } from './connectApollo';

// ApolloServer integration
export { registerServer } from './ApolloServer';
export { registerServer, ServerRegistration } from './ApolloServer';
22 changes: 17 additions & 5 deletions packages/apollo-server-hapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ description: Setting up Apollo Server with Hapi
This is the Hapi integration of Apollo Server. Apollo Server is a community-maintained open-source Apollo Server that works with all Node.js HTTP server frameworks: Express, Connect, Hapi, Koa and Restify. [Read the docs](https://www.apollographql.com/docs/apollo-server/). [Read the CHANGELOG.](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md)

```sh
npm install apollo-server@beta apollo-server-hapi@beta
npm install apollo-server-hapi@beta
```

## Usage
Expand All @@ -18,8 +18,7 @@ After constructing Apollo server, a hapi server can be enabled with a call to `r
The code below requires Hapi 17 or higher.

```js
const { ApolloServer, gql } = require('apollo-server');
const { registerServer } = require('apollo-server-hapi');
const { registerServer, ApolloServer, gql } = require('apollo-server-hapi');

const HOST = 'localhost';

Expand Down Expand Up @@ -57,8 +56,7 @@ StartServer().catch(error => console.log(error));
For more advanced use cases or migrating from 1.x, a Hapi server can be constructed and passed into `registerServer`.

```js
const { ApolloServer, gql } = require('apollo-server');
const { registerServer } = require('apollo-server-hapi');
const { ApolloServer, gql, registerServer } = require('apollo-server-hapi');
const Hapi = require('hapi');

async function StartServer() {
Expand All @@ -83,6 +81,20 @@ async function StartServer() {
StartServer().catch(error => console.log(error));
```

### Context

The context is created for each request. The following code snippet shows the creation of a context. The arguments are the `request`, the request, and `h`, the response toolkit.

```js
new ApolloServer({
typeDefs,
resolvers,
context: async ({ request, h }) => {
return { ... };
},
})
```

## Principles

Apollo Server is built with the following principles in mind:
Expand Down
29 changes: 22 additions & 7 deletions packages/apollo-server-hapi/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,31 @@ import {

import { graphqlHapi } from './hapiApollo';

export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions } from 'apollo-server-core';

const gql = String.raw;

export class ApolloServer extends ApolloServerBase {
//This translates the arguments from the middleware into graphQL options It
//provides typings for the integration specific behavior, ideally this would
//be propagated with a generic to the super class
async createGraphQLServerOptions(
request: hapi.Request,
h: hapi.ResponseToolkit,
): Promise<GraphQLOptions> {
return super.graphQLServerOptions({ request, h });
}
}

export interface ServerRegistration {
app?: hapi.Server;
//The options type should exclude port
options?: hapi.ServerOptions;
server: ApolloServerBase<hapi.Request>;
server: ApolloServer;
path?: string;
cors?: boolean;
onHealthCheck?: (req: hapi.Request) => Promise<any>;
onHealthCheck?: (request: hapi.Request) => Promise<any>;
disableHealthCheck?: boolean;
uploads?: boolean | Record<string, any>;
}
Expand All @@ -33,11 +48,11 @@ export interface HapiListenOptions {
}

const handleFileUploads = (uploadsConfig: Record<string, any>) => async (
req: hapi.Request,
request: hapi.Request,
) => {
if (req.mime === 'multipart/form-data') {
Object.defineProperty(req, 'payload', {
value: await processFileUploads(req, uploadsConfig),
if (request.mime === 'multipart/form-data') {
Object.defineProperty(request, 'payload', {
value: await processFileUploads(request, uploadsConfig),
writable: false,
});
}
Expand Down Expand Up @@ -158,7 +173,7 @@ server.listen({ http: { port: YOUR_PORT_HERE } });
plugin: graphqlHapi,
options: {
path: path,
graphqlOptions: server.graphQLServerOptionsForRequest.bind(server),
graphqlOptions: server.createGraphQLServerOptions.bind(server),
route: {
cors: typeof cors === 'boolean' ? cors : true,
},
Expand Down
Loading

0 comments on commit ba31cf7

Please sign in to comment.