2018-06-11 14:25:59 +02:00
|
|
|
import express from 'express';
|
|
|
|
import corsMiddleware from 'cors';
|
2018-05-10 22:51:26 -07:00
|
|
|
import { json, OptionsJson } from 'body-parser';
|
2018-06-01 15:16:16 -07:00
|
|
|
import { createServer } from 'http';
|
2018-05-01 06:09:48 -07:00
|
|
|
import gui from 'graphql-playground-middleware-express';
|
2018-05-29 15:58:52 -07:00
|
|
|
import { ApolloServerBase, formatApolloErrors } from 'apollo-server-core';
|
2018-06-11 14:25:59 +02:00
|
|
|
import accepts from 'accepts';
|
2018-05-01 06:09:48 -07:00
|
|
|
|
|
|
|
import { graphqlExpress } from './expressApollo';
|
|
|
|
|
2018-05-29 15:58:52 -07:00
|
|
|
import {
|
|
|
|
processRequest as processFileUploads,
|
|
|
|
GraphQLUpload,
|
|
|
|
} from 'apollo-upload-server';
|
|
|
|
|
2018-06-12 17:46:56 -07:00
|
|
|
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
|
|
|
|
import { GraphQLOptions } from 'apollo-server-core';
|
|
|
|
|
2018-05-29 15:58:52 -07:00
|
|
|
const gql = String.raw;
|
|
|
|
|
2018-06-12 17:46:56 -07:00
|
|
|
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 });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-01 06:09:48 -07:00
|
|
|
export interface ServerRegistration {
|
|
|
|
app: express.Application;
|
2018-06-12 17:46:56 -07:00
|
|
|
server: ApolloServer;
|
2018-05-01 06:09:48 -07:00
|
|
|
path?: string;
|
|
|
|
cors?: corsMiddleware.CorsOptions;
|
2018-05-10 22:51:26 -07:00
|
|
|
bodyParserConfig?: OptionsJson;
|
2018-05-22 21:58:37 -07:00
|
|
|
onHealthCheck?: (req: express.Request) => Promise<any>;
|
|
|
|
disableHealthCheck?: boolean;
|
2018-06-12 22:56:09 -07:00
|
|
|
enableGUI?: boolean;
|
2018-05-29 15:58:52 -07:00
|
|
|
//https://github.com/jaydenseric/apollo-upload-server#options
|
|
|
|
uploads?: boolean | Record<string, any>;
|
2018-05-01 06:09:48 -07:00
|
|
|
}
|
|
|
|
|
2018-05-29 15:58:52 -07:00
|
|
|
const fileUploadMiddleware = (
|
|
|
|
uploadsConfig: Record<string, any>,
|
2018-06-12 17:46:56 -07:00
|
|
|
server: ApolloServerBase,
|
2018-05-29 15:58:52 -07:00
|
|
|
) => (
|
|
|
|
req: express.Request,
|
|
|
|
res: express.Response,
|
|
|
|
next: express.NextFunction,
|
|
|
|
) => {
|
|
|
|
if (req.is('multipart/form-data')) {
|
|
|
|
processFileUploads(req, uploadsConfig)
|
|
|
|
.then(body => {
|
|
|
|
req.body = body;
|
|
|
|
next();
|
|
|
|
})
|
|
|
|
.catch(error => {
|
|
|
|
if (error.status && error.expose) res.status(error.status);
|
|
|
|
|
|
|
|
next(
|
|
|
|
formatApolloErrors([error], {
|
|
|
|
formatter: server.requestOptions.formatError,
|
|
|
|
debug: server.requestOptions.debug,
|
|
|
|
logFunction: server.requestOptions.logFunction,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2018-05-01 06:09:48 -07:00
|
|
|
export const registerServer = async ({
|
|
|
|
app,
|
|
|
|
server,
|
|
|
|
path,
|
|
|
|
cors,
|
2018-05-10 22:51:26 -07:00
|
|
|
bodyParserConfig,
|
2018-05-22 21:58:37 -07:00
|
|
|
disableHealthCheck,
|
2018-06-12 22:56:09 -07:00
|
|
|
enableGUI,
|
2018-05-22 21:58:37 -07:00
|
|
|
onHealthCheck,
|
2018-05-29 15:58:52 -07:00
|
|
|
uploads,
|
2018-05-01 06:09:48 -07:00
|
|
|
}: ServerRegistration) => {
|
2018-05-01 11:05:26 -07:00
|
|
|
if (!path) path = '/graphql';
|
|
|
|
|
2018-05-22 21:58:37 -07:00
|
|
|
if (!disableHealthCheck) {
|
|
|
|
//uses same path as engine
|
2018-06-01 15:16:16 -07:00
|
|
|
app.use('/.well-known/apollo/server-health', (req, res) => {
|
2018-05-22 21:58:37 -07:00
|
|
|
//Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01
|
|
|
|
res.type('application/health+json');
|
|
|
|
|
|
|
|
if (onHealthCheck) {
|
|
|
|
onHealthCheck(req)
|
|
|
|
.then(() => {
|
|
|
|
res.json({ status: 'pass' });
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
res.status(503).json({ status: 'fail' });
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
res.json({ status: 'pass' });
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-05-29 15:58:52 -07:00
|
|
|
let uploadsMiddleware;
|
|
|
|
if (uploads !== false) {
|
|
|
|
server.enhanceSchema({
|
|
|
|
typeDefs: gql`
|
|
|
|
scalar Upload
|
|
|
|
`,
|
|
|
|
resolvers: { Upload: GraphQLUpload },
|
|
|
|
});
|
|
|
|
|
|
|
|
uploadsMiddleware = fileUploadMiddleware(
|
|
|
|
typeof uploads !== 'boolean' ? uploads : {},
|
|
|
|
server,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-05-01 06:09:48 -07:00
|
|
|
// XXX multiple paths?
|
2018-05-01 11:05:26 -07:00
|
|
|
server.use({
|
|
|
|
path,
|
|
|
|
getHttp: () => createServer(app),
|
|
|
|
});
|
2018-05-01 06:09:48 -07:00
|
|
|
|
2018-05-10 22:51:26 -07:00
|
|
|
app.use(
|
|
|
|
path,
|
|
|
|
corsMiddleware(cors),
|
|
|
|
json(bodyParserConfig),
|
2018-06-01 15:16:16 -07:00
|
|
|
uploadsMiddleware ? uploadsMiddleware : (_req, _res, next) => next(),
|
2018-05-10 22:51:26 -07:00
|
|
|
(req, res, next) => {
|
|
|
|
// make sure we check to see if graphql gui should be on
|
2018-06-12 22:56:09 -07:00
|
|
|
// enableGUI takes precedence over the server tools setting
|
|
|
|
if (
|
|
|
|
(enableGUI || (enableGUI === undefined && !server.disableTools)) &&
|
|
|
|
req.method === 'GET'
|
|
|
|
) {
|
2018-05-10 22:51:26 -07:00
|
|
|
//perform more expensive content-type check only if necessary
|
|
|
|
const accept = accepts(req);
|
|
|
|
const types = accept.types() as string[];
|
|
|
|
const prefersHTML =
|
|
|
|
types.find(
|
|
|
|
(x: string) => x === 'text/html' || x === 'application/json',
|
|
|
|
) === 'text/html';
|
2018-05-01 06:09:48 -07:00
|
|
|
|
2018-05-10 22:51:26 -07:00
|
|
|
if (prefersHTML) {
|
|
|
|
return gui({
|
|
|
|
endpoint: path,
|
2018-06-01 12:07:40 -07:00
|
|
|
subscriptionEndpoint: server.subscriptionsPath,
|
2018-05-10 22:51:26 -07:00
|
|
|
})(req, res, next);
|
|
|
|
}
|
2018-05-01 06:09:48 -07:00
|
|
|
}
|
2018-06-12 17:46:56 -07:00
|
|
|
return graphqlExpress(server.createGraphQLServerOptions.bind(server))(
|
2018-05-30 12:12:17 -07:00
|
|
|
req,
|
|
|
|
res,
|
|
|
|
next,
|
|
|
|
);
|
2018-05-10 22:51:26 -07:00
|
|
|
},
|
|
|
|
);
|
2018-05-01 06:09:48 -07:00
|
|
|
};
|