UNPKG

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