UNPKG

14.5 kBJavaScriptView Raw
1'use strict';
2
3var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /**
4 * Copyright (c) 2015-present, Facebook, Inc.
5 * All rights reserved.
6 *
7 * This source code is licensed under the BSD-style license found in the
8 * LICENSE file in the root directory of this source tree. An additional grant
9 * of patent rights can be found in the PATENTS file in the same directory.
10 *
11 * strict
12 */
13
14var _accepts = require('accepts');
15
16var _accepts2 = _interopRequireDefault(_accepts);
17
18var _graphql = require('graphql');
19
20var _httpErrors = require('http-errors');
21
22var _httpErrors2 = _interopRequireDefault(_httpErrors);
23
24var _url = require('url');
25
26var _url2 = _interopRequireDefault(_url);
27
28var _parseBody = require('./parseBody');
29
30var _renderGraphiQL = require('./renderGraphiQL');
31
32function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
33
34/**
35 * Middleware for express; takes an options object or function as input to
36 * configure behavior, and returns an express middleware.
37 */
38
39
40/**
41 * Used to configure the graphqlHTTP middleware by providing a schema
42 * and other configuration options.
43 *
44 * Options can be provided as an Object, a Promise for an Object, or a Function
45 * that returns an Object or a Promise for an Object.
46 */
47
48
49/**
50 * All information about a GraphQL request.
51 */
52module.exports = graphqlHTTP;
53function graphqlHTTP(options) {
54 if (!options) {
55 throw new Error('GraphQL middleware requires options.');
56 }
57
58 return function graphqlMiddleware(request, response) {
59 // Higher scoped variables are referred to at various stages in the
60 // asynchronous state machine below.
61 var context = void 0;
62 var params = void 0;
63 var pretty = void 0;
64 var formatErrorFn = void 0;
65 var extensionsFn = void 0;
66 var showGraphiQL = void 0;
67 var query = void 0;
68
69 var documentAST = void 0;
70 var variables = void 0;
71 var operationName = void 0;
72
73 // Promises are used as a mechanism for capturing any thrown errors during
74 // the asynchronous process below.
75
76 // Parse the Request to get GraphQL request parameters.
77 return getGraphQLParams(request).then(function (graphQLParams) {
78 params = graphQLParams;
79 // Then, resolve the Options to get OptionsData.
80 return resolveOptions(params);
81 }, function (error) {
82 // When we failed to parse the GraphQL parameters, we still need to get
83 // the options object, so make an options call to resolve just that.
84 var dummyParams = {
85 query: null,
86 variables: null,
87 operationName: null,
88 raw: null
89 };
90 return resolveOptions(dummyParams).then(function () {
91 return Promise.reject(error);
92 });
93 }).then(function (optionsData) {
94 // Assert that schema is required.
95 if (!optionsData.schema) {
96 throw new Error('GraphQL middleware options must contain a schema.');
97 }
98
99 // Collect information from the options data object.
100 var schema = optionsData.schema;
101 var rootValue = optionsData.rootValue;
102 var fieldResolver = optionsData.fieldResolver;
103 var graphiql = optionsData.graphiql;
104
105 context = optionsData.context || request;
106
107 var validationRules = _graphql.specifiedRules;
108 if (optionsData.validationRules) {
109 validationRules = validationRules.concat(optionsData.validationRules);
110 }
111
112 // GraphQL HTTP only supports GET and POST methods.
113 if (request.method !== 'GET' && request.method !== 'POST') {
114 response.setHeader('Allow', 'GET, POST');
115 throw (0, _httpErrors2.default)(405, 'GraphQL only supports GET and POST requests.');
116 }
117
118 // Get GraphQL params from the request and POST body data.
119 query = params.query;
120 variables = params.variables;
121 operationName = params.operationName;
122 showGraphiQL = graphiql && canDisplayGraphiQL(request, params);
123
124 // If there is no query, but GraphiQL will be displayed, do not produce
125 // a result, otherwise return a 400: Bad Request.
126 if (!query) {
127 if (showGraphiQL) {
128 return null;
129 }
130 throw (0, _httpErrors2.default)(400, 'Must provide query string.');
131 }
132
133 // Validate Schema
134 var schemaValidationErrors = (0, _graphql.validateSchema)(schema);
135 if (schemaValidationErrors.length > 0) {
136 // Return 500: Internal Server Error if invalid schema.
137 response.statusCode = 500;
138 return { errors: schemaValidationErrors };
139 }
140
141 // GraphQL source.
142 var source = new _graphql.Source(query, 'GraphQL request');
143
144 // Parse source to AST, reporting any syntax error.
145 try {
146 documentAST = (0, _graphql.parse)(source);
147 } catch (syntaxError) {
148 // Return 400: Bad Request if any syntax errors errors exist.
149 response.statusCode = 400;
150 return { errors: [syntaxError] };
151 }
152
153 // Validate AST, reporting any errors.
154 var validationErrors = (0, _graphql.validate)(schema, documentAST, validationRules);
155 if (validationErrors.length > 0) {
156 // Return 400: Bad Request if any validation errors exist.
157 response.statusCode = 400;
158 return { errors: validationErrors };
159 }
160
161 // Only query operations are allowed on GET requests.
162 if (request.method === 'GET') {
163 // Determine if this GET request will perform a non-query.
164 var operationAST = (0, _graphql.getOperationAST)(documentAST, operationName);
165 if (operationAST && operationAST.operation !== 'query') {
166 // If GraphiQL can be shown, do not perform this query, but
167 // provide it to GraphiQL so that the requester may perform it
168 // themselves if desired.
169 if (showGraphiQL) {
170 return null;
171 }
172
173 // Otherwise, report a 405: Method Not Allowed error.
174 response.setHeader('Allow', 'POST');
175 throw (0, _httpErrors2.default)(405, 'Can only perform a ' + operationAST.operation + ' operation ' + 'from a POST request.');
176 }
177 }
178 // Perform the execution, reporting any errors creating the context.
179 try {
180 return (0, _graphql.execute)(schema, documentAST, rootValue, context, variables, operationName, fieldResolver);
181 } catch (contextError) {
182 // Return 400: Bad Request if any execution context errors exist.
183 response.statusCode = 400;
184 return { errors: [contextError] };
185 }
186 }).then(function (result) {
187 // Collect and apply any metadata extensions if a function was provided.
188 // http://facebook.github.io/graphql/#sec-Response-Format
189 if (result && extensionsFn) {
190 return Promise.resolve(extensionsFn({
191 document: documentAST,
192 variables: variables,
193 operationName: operationName,
194 result: result,
195 context: context
196 })).then(function (extensions) {
197 if (extensions && (typeof extensions === 'undefined' ? 'undefined' : _typeof(extensions)) === 'object') {
198 result.extensions = extensions;
199 }
200 return result;
201 });
202 }
203 return result;
204 }).catch(function (error) {
205 // If an error was caught, report the httpError status, or 500.
206 response.statusCode = error.status || 500;
207 return { errors: [error] };
208 }).then(function (result) {
209 // If no data was included in the result, that indicates a runtime query
210 // error, indicate as such with a generic status code.
211 // Note: Information about the error itself will still be contained in
212 // the resulting JSON payload.
213 // http://facebook.github.io/graphql/#sec-Data
214 if (response.statusCode === 200 && result && !result.data) {
215 response.statusCode = 500;
216 }
217 // Format any encountered errors.
218 if (result && result.errors) {
219 result.errors = result.errors.map(formatErrorFn || _graphql.formatError);
220 }
221
222 // If allowed to show GraphiQL, present it instead of JSON.
223 if (showGraphiQL) {
224 var payload = (0, _renderGraphiQL.renderGraphiQL)({
225 query: query,
226 variables: variables,
227 operationName: operationName,
228 result: result
229 });
230 return sendResponse(response, 'text/html', payload);
231 }
232
233 // At this point, result is guaranteed to exist, as the only scenario
234 // where it will not is when showGraphiQL is true.
235 if (!result) {
236 throw (0, _httpErrors2.default)(500, 'Internal Error');
237 }
238
239 // If "pretty" JSON isn't requested, and the server provides a
240 // response.json method (express), use that directly.
241 // Otherwise use the simplified sendResponse method.
242 if (!pretty && typeof response.json === 'function') {
243 response.json(result);
244 } else {
245 var _payload = JSON.stringify(result, null, pretty ? 2 : 0);
246 sendResponse(response, 'application/json', _payload);
247 }
248 });
249
250 function resolveOptions(requestParams) {
251 return Promise.resolve(typeof options === 'function' ? options(request, response, requestParams) : options).then(function (optionsData) {
252 // Assert that optionsData is in fact an Object.
253 if (!optionsData || (typeof optionsData === 'undefined' ? 'undefined' : _typeof(optionsData)) !== 'object') {
254 throw new Error('GraphQL middleware option function must return an options object ' + 'or a promise which will be resolved to an options object.');
255 }
256
257 formatErrorFn = optionsData.formatError;
258 extensionsFn = optionsData.extensions;
259 pretty = optionsData.pretty;
260 return optionsData;
261 });
262 }
263 };
264}
265
266/**
267 * Provided a "Request" provided by express or connect (typically a node style
268 * HTTPClientRequest), Promise the GraphQL request parameters.
269 */
270module.exports.getGraphQLParams = getGraphQLParams;
271function getGraphQLParams(request) {
272 return (0, _parseBody.parseBody)(request).then(function (bodyData) {
273 var urlData = request.url && _url2.default.parse(request.url, true).query || {};
274 return parseGraphQLParams(urlData, bodyData);
275 });
276}
277
278/**
279 * Helper function to get the GraphQL params from the request.
280 */
281function parseGraphQLParams(urlData, bodyData) {
282 // GraphQL Query string.
283 var query = urlData.query || bodyData.query;
284 if (typeof query !== 'string') {
285 query = null;
286 }
287
288 // Parse the variables if needed.
289 var variables = urlData.variables || bodyData.variables;
290 if (variables && typeof variables === 'string') {
291 try {
292 variables = JSON.parse(variables);
293 } catch (error) {
294 throw (0, _httpErrors2.default)(400, 'Variables are invalid JSON.');
295 }
296 } else if ((typeof variables === 'undefined' ? 'undefined' : _typeof(variables)) !== 'object') {
297 variables = null;
298 }
299
300 // Name of GraphQL operation to execute.
301 var operationName = urlData.operationName || bodyData.operationName;
302 if (typeof operationName !== 'string') {
303 operationName = null;
304 }
305
306 var raw = urlData.raw !== undefined || bodyData.raw !== undefined;
307
308 return { query: query, variables: variables, operationName: operationName, raw: raw };
309}
310
311/**
312 * Helper function to determine if GraphiQL can be displayed.
313 */
314function canDisplayGraphiQL(request, params) {
315 // If `raw` exists, GraphiQL mode is not enabled.
316 // Allowed to show GraphiQL if not requested as raw and this request
317 // prefers HTML over JSON.
318 return !params.raw && (0, _accepts2.default)(request).types(['json', 'html']) === 'html';
319}
320
321/**
322 * Helper function for sending a response using only the core Node server APIs.
323 */
324function sendResponse(response, type, data) {
325 var chunk = new Buffer(data, 'utf8');
326 response.setHeader('Content-Type', type + '; charset=utf-8');
327 response.setHeader('Content-Length', String(chunk.length));
328 response.end(chunk);
329}
\No newline at end of file