index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.graphqlHTTP = graphqlHTTP;
  6. exports.getGraphQLParams = getGraphQLParams;
  7. var _accepts = _interopRequireDefault(require("accepts"));
  8. var _httpErrors = _interopRequireDefault(require("http-errors"));
  9. var _graphql = require("graphql");
  10. var _parseBody = require("./parseBody");
  11. var _renderGraphiQL = require("./renderGraphiQL");
  12. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  13. /**
  14. * Middleware for express; takes an options object or function as input to
  15. * configure behavior, and returns an express middleware.
  16. */
  17. function graphqlHTTP(options) {
  18. if (!options) {
  19. throw new Error('GraphQL middleware requires options.');
  20. }
  21. return async function graphqlMiddleware(request, response) {
  22. var _result$errors;
  23. // Higher scoped variables are referred to at various stages in the asynchronous state machine below.
  24. let params;
  25. let showGraphiQL = false;
  26. let graphiqlOptions;
  27. let formatErrorFn = _graphql.formatError;
  28. let pretty = false;
  29. let result;
  30. try {
  31. var _optionsData$validati, _optionsData$graphiql, _optionsData$context, _optionsData$customPa, _optionsData$customEx, _optionsData$customVa, _optionsData$pretty2, _ref2, _optionsData$customFo2;
  32. // Parse the Request to get GraphQL request parameters.
  33. try {
  34. params = await getGraphQLParams(request);
  35. } catch (error) {
  36. var _optionsData$pretty, _ref, _optionsData$customFo;
  37. // When we failed to parse the GraphQL parameters, we still need to get
  38. // the options object, so make an options call to resolve just that.
  39. const optionsData = await resolveOptions();
  40. pretty = (_optionsData$pretty = optionsData.pretty) !== null && _optionsData$pretty !== void 0 ? _optionsData$pretty : false;
  41. formatErrorFn = (_ref = (_optionsData$customFo = optionsData.customFormatErrorFn) !== null && _optionsData$customFo !== void 0 ? _optionsData$customFo : optionsData.formatError) !== null && _ref !== void 0 ? _ref : formatErrorFn;
  42. throw error;
  43. } // Then, resolve the Options to get OptionsData.
  44. const optionsData = await resolveOptions(params); // Collect information from the options data object.
  45. const schema = optionsData.schema;
  46. const rootValue = optionsData.rootValue;
  47. const validationRules = (_optionsData$validati = optionsData.validationRules) !== null && _optionsData$validati !== void 0 ? _optionsData$validati : [];
  48. const fieldResolver = optionsData.fieldResolver;
  49. const typeResolver = optionsData.typeResolver;
  50. const graphiql = (_optionsData$graphiql = optionsData.graphiql) !== null && _optionsData$graphiql !== void 0 ? _optionsData$graphiql : false;
  51. const extensionsFn = optionsData.extensions;
  52. const context = (_optionsData$context = optionsData.context) !== null && _optionsData$context !== void 0 ? _optionsData$context : request;
  53. const parseFn = (_optionsData$customPa = optionsData.customParseFn) !== null && _optionsData$customPa !== void 0 ? _optionsData$customPa : _graphql.parse;
  54. const executeFn = (_optionsData$customEx = optionsData.customExecuteFn) !== null && _optionsData$customEx !== void 0 ? _optionsData$customEx : _graphql.execute;
  55. const validateFn = (_optionsData$customVa = optionsData.customValidateFn) !== null && _optionsData$customVa !== void 0 ? _optionsData$customVa : _graphql.validate;
  56. pretty = (_optionsData$pretty2 = optionsData.pretty) !== null && _optionsData$pretty2 !== void 0 ? _optionsData$pretty2 : false;
  57. formatErrorFn = (_ref2 = (_optionsData$customFo2 = optionsData.customFormatErrorFn) !== null && _optionsData$customFo2 !== void 0 ? _optionsData$customFo2 : optionsData.formatError) !== null && _ref2 !== void 0 ? _ref2 : formatErrorFn; // Assert that schema is required.
  58. if (schema == null) {
  59. throw (0, _httpErrors.default)(500, 'GraphQL middleware options must contain a schema.');
  60. } // GraphQL HTTP only supports GET and POST methods.
  61. if (request.method !== 'GET' && request.method !== 'POST') {
  62. throw (0, _httpErrors.default)(405, 'GraphQL only supports GET and POST requests.', {
  63. headers: {
  64. Allow: 'GET, POST'
  65. }
  66. });
  67. } // Get GraphQL params from the request and POST body data.
  68. const {
  69. query,
  70. variables,
  71. operationName
  72. } = params;
  73. showGraphiQL = canDisplayGraphiQL(request, params) && graphiql !== false;
  74. if (typeof graphiql !== 'boolean') {
  75. graphiqlOptions = graphiql;
  76. } // If there is no query, but GraphiQL will be displayed, do not produce
  77. // a result, otherwise return a 400: Bad Request.
  78. if (query == null) {
  79. if (showGraphiQL) {
  80. return respondWithGraphiQL(response, graphiqlOptions);
  81. }
  82. throw (0, _httpErrors.default)(400, 'Must provide query string.');
  83. } // Validate Schema
  84. const schemaValidationErrors = (0, _graphql.validateSchema)(schema);
  85. if (schemaValidationErrors.length > 0) {
  86. // Return 500: Internal Server Error if invalid schema.
  87. throw (0, _httpErrors.default)(500, 'GraphQL schema validation error.', {
  88. graphqlErrors: schemaValidationErrors
  89. });
  90. } // Parse source to AST, reporting any syntax error.
  91. let documentAST;
  92. try {
  93. documentAST = parseFn(new _graphql.Source(query, 'GraphQL request'));
  94. } catch (syntaxError) {
  95. // Return 400: Bad Request if any syntax errors errors exist.
  96. throw (0, _httpErrors.default)(400, 'GraphQL syntax error.', {
  97. graphqlErrors: [syntaxError]
  98. });
  99. } // Validate AST, reporting any errors.
  100. const validationErrors = validateFn(schema, documentAST, [..._graphql.specifiedRules, ...validationRules]);
  101. if (validationErrors.length > 0) {
  102. // Return 400: Bad Request if any validation errors exist.
  103. throw (0, _httpErrors.default)(400, 'GraphQL validation error.', {
  104. graphqlErrors: validationErrors
  105. });
  106. } // Only query operations are allowed on GET requests.
  107. if (request.method === 'GET') {
  108. // Determine if this GET request will perform a non-query.
  109. const operationAST = (0, _graphql.getOperationAST)(documentAST, operationName);
  110. if (operationAST && operationAST.operation !== 'query') {
  111. // If GraphiQL can be shown, do not perform this query, but
  112. // provide it to GraphiQL so that the requester may perform it
  113. // themselves if desired.
  114. if (showGraphiQL) {
  115. return respondWithGraphiQL(response, graphiqlOptions, params);
  116. } // Otherwise, report a 405: Method Not Allowed error.
  117. throw (0, _httpErrors.default)(405, `Can only perform a ${operationAST.operation} operation from a POST request.`, {
  118. headers: {
  119. Allow: 'POST'
  120. }
  121. });
  122. }
  123. } // Perform the execution, reporting any errors creating the context.
  124. try {
  125. result = await executeFn({
  126. schema,
  127. document: documentAST,
  128. rootValue,
  129. contextValue: context,
  130. variableValues: variables,
  131. operationName,
  132. fieldResolver,
  133. typeResolver
  134. });
  135. } catch (contextError) {
  136. // Return 400: Bad Request if any execution context errors exist.
  137. throw (0, _httpErrors.default)(400, 'GraphQL execution context error.', {
  138. graphqlErrors: [contextError]
  139. });
  140. } // Collect and apply any metadata extensions if a function was provided.
  141. // https://graphql.github.io/graphql-spec/#sec-Response-Format
  142. if (extensionsFn) {
  143. const extensions = await extensionsFn({
  144. document: documentAST,
  145. variables,
  146. operationName,
  147. result,
  148. context
  149. });
  150. if (extensions != null) {
  151. result = { ...result,
  152. extensions
  153. };
  154. }
  155. }
  156. } catch (error) {
  157. var _error$status, _error$graphqlErrors;
  158. // If an error was caught, report the httpError status, or 500.
  159. response.statusCode = (_error$status = error.status) !== null && _error$status !== void 0 ? _error$status : 500;
  160. if (error.headers != null) {
  161. for (const [key, value] of Object.entries(error.headers)) {
  162. response.setHeader(key, value);
  163. }
  164. }
  165. result = {
  166. data: undefined,
  167. errors: (_error$graphqlErrors = error.graphqlErrors) !== null && _error$graphqlErrors !== void 0 ? _error$graphqlErrors : [error]
  168. };
  169. } // If no data was included in the result, that indicates a runtime query
  170. // error, indicate as such with a generic status code.
  171. // Note: Information about the error itself will still be contained in
  172. // the resulting JSON payload.
  173. // https://graphql.github.io/graphql-spec/#sec-Data
  174. if (response.statusCode === 200 && result.data == null) {
  175. response.statusCode = 500;
  176. } // Format any encountered errors.
  177. const formattedResult = { ...result,
  178. errors: (_result$errors = result.errors) === null || _result$errors === void 0 ? void 0 : _result$errors.map(formatErrorFn)
  179. }; // If allowed to show GraphiQL, present it instead of JSON.
  180. if (showGraphiQL) {
  181. return respondWithGraphiQL(response, graphiqlOptions, params, formattedResult);
  182. } // If "pretty" JSON isn't requested, and the server provides a
  183. // response.json method (express), use that directly.
  184. // Otherwise use the simplified sendResponse method.
  185. if (!pretty && typeof response.json === 'function') {
  186. response.json(formattedResult);
  187. } else {
  188. const payload = JSON.stringify(formattedResult, null, pretty ? 2 : 0);
  189. sendResponse(response, 'application/json', payload);
  190. }
  191. async function resolveOptions(requestParams) {
  192. const optionsResult = await Promise.resolve(typeof options === 'function' ? options(request, response, requestParams) : options); // Assert that optionsData is in fact an Object.
  193. if (optionsResult == null || typeof optionsResult !== 'object') {
  194. throw new Error('GraphQL middleware option function must return an options object or a promise which will be resolved to an options object.');
  195. }
  196. if (optionsResult.formatError) {
  197. // eslint-disable-next-line no-console
  198. console.warn('`formatError` is deprecated and replaced by `customFormatErrorFn`. It will be removed in version 1.0.0.');
  199. }
  200. return optionsResult;
  201. }
  202. };
  203. }
  204. function respondWithGraphiQL(response, options, params, result) {
  205. const data = {
  206. query: params === null || params === void 0 ? void 0 : params.query,
  207. variables: params === null || params === void 0 ? void 0 : params.variables,
  208. operationName: params === null || params === void 0 ? void 0 : params.operationName,
  209. result
  210. };
  211. const payload = (0, _renderGraphiQL.renderGraphiQL)(data, options);
  212. return sendResponse(response, 'text/html', payload);
  213. }
  214. /**
  215. * Provided a "Request" provided by express or connect (typically a node style
  216. * HTTPClientRequest), Promise the GraphQL request parameters.
  217. */
  218. async function getGraphQLParams(request) {
  219. var _urlData$get, _urlData$get2;
  220. const urlData = new URLSearchParams(request.url.split('?')[1]);
  221. const bodyData = await (0, _parseBody.parseBody)(request); // GraphQL Query string.
  222. let query = (_urlData$get = urlData.get('query')) !== null && _urlData$get !== void 0 ? _urlData$get : bodyData.query;
  223. if (typeof query !== 'string') {
  224. query = null;
  225. } // Parse the variables if needed.
  226. let variables = (_urlData$get2 = urlData.get('variables')) !== null && _urlData$get2 !== void 0 ? _urlData$get2 : bodyData.variables;
  227. if (typeof variables === 'string') {
  228. try {
  229. variables = JSON.parse(variables);
  230. } catch (error) {
  231. throw (0, _httpErrors.default)(400, 'Variables are invalid JSON.');
  232. }
  233. } else if (typeof variables !== 'object') {
  234. variables = null;
  235. } // Name of GraphQL operation to execute.
  236. let operationName = urlData.get('operationName') || bodyData.operationName;
  237. if (typeof operationName !== 'string') {
  238. operationName = null;
  239. }
  240. const raw = urlData.get('raw') != null || bodyData.raw !== undefined;
  241. return {
  242. query,
  243. variables,
  244. operationName,
  245. raw
  246. };
  247. }
  248. /**
  249. * Helper function to determine if GraphiQL can be displayed.
  250. */
  251. function canDisplayGraphiQL(request, params) {
  252. // If `raw` false, GraphiQL mode is not enabled.
  253. // Allowed to show GraphiQL if not requested as raw and this request prefers HTML over JSON.
  254. return !params.raw && (0, _accepts.default)(request).types(['json', 'html']) === 'html';
  255. }
  256. /**
  257. * Helper function for sending a response using only the core Node server APIs.
  258. */
  259. function sendResponse(response, type, data) {
  260. const chunk = Buffer.from(data, 'utf8');
  261. response.setHeader('Content-Type', type + '; charset=utf-8');
  262. response.setHeader('Content-Length', String(chunk.length));
  263. response.end(chunk);
  264. }