'use strict'; const { isEmpty, mergeWith, isArray } = require('lodash/fp'); const { execute, subscribe } = require('graphql'); const { SubscriptionServer } = require('subscriptions-transport-ws'); const { ApolloServer } = require('apollo-server-koa'); const { ApolloServerPluginLandingPageDisabled, ApolloServerPluginLandingPageGraphQLPlayground, } = require('apollo-server-core'); const depthLimit = require('graphql-depth-limit'); const { graphqlUploadKoa } = require('graphql-upload'); const formatGraphqlError = require('./format-graphql-error'); const merge = mergeWith((a, b) => { if (isArray(a) && isArray(b)) { return a.concat(b); } }); module.exports = async ({ strapi }) => { // Generate the GraphQL schema for the content API const schema = strapi .plugin('graphql') .service('content-api') .buildSchema(); if (isEmpty(schema)) { strapi.log.warn('The GraphQL schema has not been generated because it is empty'); return; } const { config } = strapi.plugin('graphql'); const path = config('endpoint'); const defaultServerConfig = { // Schema schema, // Initialize loaders for this request. context: ({ ctx }) => ({ state: ctx.state, koaContext: ctx, }), // Validation validationRules: [depthLimit(config('depthLimit'))], // Errors formatError: formatGraphqlError, // Misc cors: false, uploads: false, bodyParserConfig: true, plugins: [ process.env.NODE_ENV === 'production' ? ApolloServerPluginLandingPageDisabled() : ApolloServerPluginLandingPageGraphQLPlayground(), ], }; const serverConfig = merge(defaultServerConfig, config('apolloServer')); // Handle subscriptions if (config('subscriptions')) { const subscriptionServer = SubscriptionServer.create( { schema, execute, subscribe }, { server: strapi.server.httpServer, path } ); serverConfig.plugins.push({ async serverWillStart() { return { async drainServer() { subscriptionServer.close(); }, }; }, }); } // Create a new Apollo server const server = new ApolloServer(serverConfig); // Register the upload middleware useUploadMiddleware(strapi, path); try { // Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware() await server.start(); } catch (e) { strapi.log.error('Failed to start the Apollo server', e.message); } // Link the Apollo server & the Strapi app strapi.server.routes([ { method: 'ALL', path, handler: [ (ctx, next) => { ctx.state.route = { info: { // Indicate it's a content API route type: 'content-api', }, }; return strapi.auth.authenticate(ctx, next); }, // Apollo Server server.getMiddleware({ path }), ], config: { auth: false, }, }, ]); // Register destroy behavior // We're doing it here instead of exposing a destroy method to the strapi-server.js // file since we need to have access to the ApolloServer instance strapi.plugin('graphql').destroy = async () => { await server.stop(); }; }; /** * Register the upload middleware powered by graphql-upload in Strapi * @param {object} strapi * @param {string} path */ const useUploadMiddleware = (strapi, path) => { const uploadMiddleware = graphqlUploadKoa(); strapi.server.app.use((ctx, next) => { if (ctx.path === path) { return uploadMiddleware(ctx, next); } return next(); }); };