graphql-server.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. // modified from vue-cli-plugin-apollo/graphql-server
  2. // added a return value for the server() call
  3. const http = require('http')
  4. const { chalk } = require('@vue/cli-shared-utils')
  5. const express = require('express')
  6. const { ApolloServer, gql } = require('apollo-server-express')
  7. const { PubSub } = require('graphql-subscriptions')
  8. const merge = require('deepmerge')
  9. function defaultValue (provided, value) {
  10. return provided == null ? value : provided
  11. }
  12. function autoCall (fn, ...context) {
  13. if (typeof fn === 'function') {
  14. return fn(...context)
  15. }
  16. return fn
  17. }
  18. module.exports = (options, cb = null) => {
  19. // Default options
  20. options = merge({
  21. integratedEngine: false
  22. }, options)
  23. // Express app
  24. const app = express()
  25. // Customize those files
  26. let typeDefs = load(options.paths.typeDefs)
  27. const resolvers = load(options.paths.resolvers)
  28. const context = load(options.paths.context)
  29. const schemaDirectives = load(options.paths.directives)
  30. let pubsub
  31. try {
  32. pubsub = load(options.paths.pubsub)
  33. } catch (e) {
  34. if (process.env.NODE_ENV !== 'production' && !options.quiet) {
  35. console.log(chalk.yellow('Using default PubSub implementation for subscriptions.'))
  36. console.log(chalk.grey('You should provide a different implementation in production (for example with Redis) by exporting it in \'apollo-server/pubsub.js\'.'))
  37. }
  38. }
  39. let dataSources
  40. try {
  41. dataSources = load(options.paths.dataSources)
  42. } catch (e) {}
  43. // GraphQL API Server
  44. // Realtime subscriptions
  45. if (!pubsub) pubsub = new PubSub()
  46. // Customize server
  47. try {
  48. const serverModule = load(options.paths.server)
  49. serverModule(app)
  50. } catch (e) {
  51. // No file found
  52. }
  53. // Apollo server options
  54. typeDefs = processSchema(typeDefs)
  55. let apolloServerOptions = {
  56. typeDefs,
  57. resolvers,
  58. schemaDirectives,
  59. dataSources,
  60. tracing: true,
  61. cacheControl: true,
  62. engine: !options.integratedEngine,
  63. // Resolvers context from POST
  64. context: async ({ req, connection }) => {
  65. let contextData
  66. try {
  67. if (connection) {
  68. contextData = await autoCall(context, { connection })
  69. } else {
  70. contextData = await autoCall(context, { req })
  71. }
  72. } catch (e) {
  73. console.error(e)
  74. throw e
  75. }
  76. contextData = Object.assign({}, contextData, { pubsub })
  77. return contextData
  78. },
  79. // Resolvers context from WebSocket
  80. subscriptions: {
  81. path: options.subscriptionsPath,
  82. onConnect: async (connection, websocket) => {
  83. let contextData = {}
  84. try {
  85. contextData = await autoCall(context, {
  86. connection,
  87. websocket
  88. })
  89. contextData = Object.assign({}, contextData, { pubsub })
  90. } catch (e) {
  91. console.error(e)
  92. throw e
  93. }
  94. return contextData
  95. }
  96. }
  97. }
  98. // Automatic mocking
  99. if (options.enableMocks) {
  100. // Customize this file
  101. apolloServerOptions.mocks = load(options.paths.mocks)
  102. apolloServerOptions.mockEntireSchema = false
  103. if (!options.quiet) {
  104. if (process.env.NODE_ENV === 'production') {
  105. console.warn('Automatic mocking is enabled, consider disabling it with the \'enableMocks\' option.')
  106. } else {
  107. console.log('✔️ Automatic mocking is enabled')
  108. }
  109. }
  110. }
  111. // Apollo Engine
  112. if (options.enableEngine && options.integratedEngine) {
  113. if (options.engineKey) {
  114. apolloServerOptions.engine = {
  115. apiKey: options.engineKey,
  116. schemaTag: options.schemaTag,
  117. ...options.engineOptions || {}
  118. }
  119. console.log('✔️ Apollo Engine is enabled')
  120. } else if (!options.quiet) {
  121. console.log(chalk.yellow('Apollo Engine key not found.') + `To enable Engine, set the ${chalk.cyan('VUE_APP_APOLLO_ENGINE_KEY')} env variable.`)
  122. console.log('Create a key at https://engine.apollographql.com/')
  123. console.log('You may see `Error: Must provide document` errors (query persisting tries).')
  124. }
  125. } else {
  126. apolloServerOptions.engine = false
  127. }
  128. // Final options
  129. apolloServerOptions = merge(apolloServerOptions, defaultValue(options.serverOptions, {}))
  130. // Apollo Server
  131. const server = new ApolloServer(apolloServerOptions)
  132. // Express middleware
  133. server.applyMiddleware({
  134. app,
  135. path: options.graphqlPath,
  136. cors: options.cors
  137. // gui: {
  138. // endpoint: graphqlPath,
  139. // subscriptionEndpoint: graphqlSubscriptionsPath,
  140. // },
  141. })
  142. // Start server
  143. const httpServer = http.createServer(app)
  144. httpServer.setTimeout(options.timeout)
  145. server.installSubscriptionHandlers(httpServer)
  146. httpServer.listen({
  147. host: options.host || 'localhost',
  148. port: options.port
  149. }, () => {
  150. if (!options.quiet) {
  151. console.log(`✔️ GraphQL Server is running on ${chalk.cyan(`http://localhost:${options.port}${options.graphqlPath}`)}`)
  152. if (process.env.NODE_ENV !== 'production' && !process.env.VUE_CLI_API_MODE) {
  153. console.log(`✔️ Type ${chalk.cyan('rs')} to restart the server`)
  154. }
  155. }
  156. cb && cb()
  157. })
  158. // added in order to let vue cli to deal with the http upgrade request
  159. return {
  160. apolloServer: server,
  161. httpServer
  162. }
  163. }
  164. function load (file) {
  165. const module = require(file)
  166. if (module.default) {
  167. return module.default
  168. }
  169. return module
  170. }
  171. function processSchema (typeDefs) {
  172. if (Array.isArray(typeDefs)) {
  173. return typeDefs.map(processSchema)
  174. }
  175. if (typeof typeDefs === 'string') {
  176. // Convert schema to AST
  177. typeDefs = gql(typeDefs)
  178. }
  179. // Remove upload scalar (it's already included in Apollo Server)
  180. removeFromSchema(typeDefs, 'ScalarTypeDefinition', 'Upload')
  181. return typeDefs
  182. }
  183. function removeFromSchema (document, kind, name) {
  184. const definitions = document.definitions
  185. const index = definitions.findIndex(
  186. def => def.kind === kind && def.name.kind === 'Name' && def.name.value === name
  187. )
  188. if (index !== -1) {
  189. definitions.splice(index, 1)
  190. }
  191. }