1 | import express from 'express';
|
2 | import corsMiddleware from 'cors';
|
3 | import { json, OptionsJson } from 'body-parser';
|
4 | import {
|
5 | renderPlaygroundPage,
|
6 | RenderPageOptions as PlaygroundRenderPageOptions,
|
7 | } from '@apollographql/graphql-playground-html';
|
8 | import {
|
9 | GraphQLOptions,
|
10 | FileUploadOptions,
|
11 | ApolloServerBase,
|
12 | formatApolloErrors,
|
13 | processFileUploads,
|
14 | ContextFunction,
|
15 | Context,
|
16 | Config,
|
17 | } from 'apollo-server-core';
|
18 | import { ExecutionParams } from 'subscriptions-transport-ws';
|
19 | import accepts from 'accepts';
|
20 | import typeis from 'type-is';
|
21 | import { graphqlExpress } from './expressApollo';
|
22 |
|
23 | export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
|
24 |
|
25 | export interface GetMiddlewareOptions {
|
26 | path?: string;
|
27 | cors?: corsMiddleware.CorsOptions | corsMiddleware.CorsOptionsDelegate | boolean;
|
28 | bodyParserConfig?: OptionsJson | boolean;
|
29 | onHealthCheck?: (req: express.Request) => Promise<any>;
|
30 | disableHealthCheck?: boolean;
|
31 | }
|
32 |
|
33 | export interface ServerRegistration extends GetMiddlewareOptions {
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | app: express.Application;
|
41 | }
|
42 |
|
43 | const fileUploadMiddleware = (
|
44 | uploadsConfig: FileUploadOptions,
|
45 | server: ApolloServerBase,
|
46 | ) => (
|
47 | req: express.Request,
|
48 | res: express.Response,
|
49 | next: express.NextFunction,
|
50 | ) => {
|
51 |
|
52 | if (
|
53 | typeof processFileUploads === 'function' &&
|
54 | typeis(req, ['multipart/form-data'])
|
55 | ) {
|
56 | processFileUploads(req, res, uploadsConfig)
|
57 | .then(body => {
|
58 | req.body = body;
|
59 | next();
|
60 | })
|
61 | .catch(error => {
|
62 | if (error.status && error.expose) res.status(error.status);
|
63 |
|
64 | next(
|
65 | formatApolloErrors([error], {
|
66 | formatter: server.requestOptions.formatError,
|
67 | debug: server.requestOptions.debug,
|
68 | }),
|
69 | );
|
70 | });
|
71 | } else {
|
72 | next();
|
73 | }
|
74 | };
|
75 |
|
76 | export interface ExpressContext {
|
77 | req: express.Request;
|
78 | res: express.Response;
|
79 | connection?: ExecutionParams;
|
80 | }
|
81 |
|
82 | export interface ApolloServerExpressConfig extends Config {
|
83 | context?: ContextFunction<ExpressContext, Context> | Context;
|
84 | }
|
85 |
|
86 | export class ApolloServer extends ApolloServerBase {
|
87 | constructor(config: ApolloServerExpressConfig) {
|
88 | super(config);
|
89 | }
|
90 |
|
91 |
|
92 |
|
93 |
|
94 | async createGraphQLServerOptions(
|
95 | req: express.Request,
|
96 | res: express.Response,
|
97 | ): Promise<GraphQLOptions> {
|
98 | return super.graphQLServerOptions({ req, res });
|
99 | }
|
100 |
|
101 | protected supportsSubscriptions(): boolean {
|
102 | return true;
|
103 | }
|
104 |
|
105 | protected supportsUploads(): boolean {
|
106 | return true;
|
107 | }
|
108 |
|
109 | public applyMiddleware({ app, ...rest }: ServerRegistration) {
|
110 | app.use(this.getMiddleware(rest));
|
111 | }
|
112 |
|
113 |
|
114 |
|
115 |
|
116 | public getMiddleware({
|
117 | path,
|
118 | cors,
|
119 | bodyParserConfig,
|
120 | disableHealthCheck,
|
121 | onHealthCheck,
|
122 | }: GetMiddlewareOptions = {}): express.Router {
|
123 | if (!path) path = '/graphql';
|
124 |
|
125 | const router = express.Router();
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 |
|
132 |
|
133 |
|
134 |
|
135 |
|
136 |
|
137 |
|
138 |
|
139 | const promiseWillStart = this.willStart();
|
140 |
|
141 | router.use(path, (_req, _res, next) => {
|
142 | promiseWillStart.then(() => next()).catch(next);
|
143 | });
|
144 |
|
145 | if (!disableHealthCheck) {
|
146 | router.use('/.well-known/apollo/server-health', (req, res) => {
|
147 |
|
148 | res.type('application/health+json');
|
149 |
|
150 | if (onHealthCheck) {
|
151 | onHealthCheck(req)
|
152 | .then(() => {
|
153 | res.json({ status: 'pass' });
|
154 | })
|
155 | .catch(() => {
|
156 | res.status(503).json({ status: 'fail' });
|
157 | });
|
158 | } else {
|
159 | res.json({ status: 'pass' });
|
160 | }
|
161 | });
|
162 | }
|
163 |
|
164 | let uploadsMiddleware;
|
165 | if (this.uploadsConfig && typeof processFileUploads === 'function') {
|
166 | uploadsMiddleware = fileUploadMiddleware(this.uploadsConfig, this);
|
167 | }
|
168 |
|
169 |
|
170 | this.graphqlPath = path;
|
171 |
|
172 |
|
173 |
|
174 | if (cors === true) {
|
175 | router.use(path, corsMiddleware());
|
176 | } else if (cors !== false) {
|
177 | router.use(path, corsMiddleware(cors));
|
178 | }
|
179 |
|
180 | if (bodyParserConfig === true) {
|
181 | router.use(path, json());
|
182 | } else if (bodyParserConfig !== false) {
|
183 | router.use(path, json(bodyParserConfig));
|
184 | }
|
185 |
|
186 | if (uploadsMiddleware) {
|
187 | router.use(path, uploadsMiddleware);
|
188 | }
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 | router.use(path, (req, res, next) => {
|
195 | if (this.playgroundOptions && req.method === 'GET') {
|
196 |
|
197 |
|
198 |
|
199 | const accept = accepts(req);
|
200 | const types = accept.types() as string[];
|
201 | const prefersHTML =
|
202 | types.find(
|
203 | (x: string) => x === 'text/html' || x === 'application/json',
|
204 | ) === 'text/html';
|
205 |
|
206 | if (prefersHTML) {
|
207 | const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
|
208 | endpoint: req.originalUrl,
|
209 | subscriptionEndpoint: this.subscriptionsPath,
|
210 | ...this.playgroundOptions,
|
211 | };
|
212 | res.setHeader('Content-Type', 'text/html');
|
213 | const playground = renderPlaygroundPage(playgroundRenderPageOptions);
|
214 | res.write(playground);
|
215 | res.end();
|
216 | return;
|
217 | }
|
218 | }
|
219 |
|
220 | return graphqlExpress(() => this.createGraphQLServerOptions(req, res))(
|
221 | req,
|
222 | res,
|
223 | next,
|
224 | );
|
225 | });
|
226 |
|
227 | return router;
|
228 | }
|
229 | }
|