Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrations export their own ApolloServer #1161

Merged
merged 7 commits into from
Jun 13, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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';
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't a-s-e need to export gql now too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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


// 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