Search code examples
expresswebsocketnuxt.jsapollosubscription

Apollo Server as Nuxt serverMiddleware


I've managed to have a express + Apollo Backend as a serverMiddleware in Nuxtjs. Everything works fine(auth, cache, datasources, queries, mutations) but now I'm trying to get subscriptions(websockets) running and its giving me a hard time.

I tried this example https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware but even letting the httpServer listening didn't work.

This is my API file which I require through the nuxt.config.js with '~/api/index' :

module.exports = async () => {
  const app = require('express')()
  const server = await require("./apollo")() // apollo-server-express w/ typeDefs and resolvers

  // apply Apollo to Express
  server.applyMiddleware({ app });
  console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);

  const httpServer = http.createServer(app);
  server.installSubscriptionHandlers(httpServer);
  console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);

  return {
    path: '/api',
    handler: httpServer
  }
}

Now my playground is giving me this error: "Could not connect to websocket endpoint ws://192.168.150.98:3000/api/graphql. Please check if the endpoint url is correct."

TypeDefs:

type Subscription {
  postAdded: Post
}
type Post {
  author: String
  comment: String
}
type Query {
  posts: [Post]
}
type Mutation {
  addPost(author: String, comment: String): Post
}

Resolvers:

Query: {
  posts(root, args, context) {
    return Posts;
  }
}
Mutation: {
  addPost(root, args, context) {
    pubsub.publish(POST_ADDED, { postAdded: args });
    return Posts.add(args);
  }
},
Subscription: {
  postAdded: {
    // Additional event labels can be passed to asyncIterator creation
    subscribe: () => pubsub.asyncIterator([POST_ADDED]),
  },
}

First question here, thank u in advance! :)


Solution

  • I found a hacky way to achieve it, import the code as a nuxt module:

    import http from 'http'
    
    export default function () {
      this.nuxt.hook('render:before', async () => {
        const server = require("./apollo")()
        
        // apply Apollo to Express
        server.applyMiddleware({ app: this.nuxt.renderer.app });
        console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
        
        const httpServer = http.createServer(this.nuxt.renderer.app);
        
        // apply SubscriptionHandlers to httpServer
        server.installSubscriptionHandlers(httpServer);
        console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
    
        // overwrite nuxt.server.listen()
        this.nuxt.server.listen = (port, host) => new Promise(resolve => httpServer.listen(port || 3000, host || 'localhost', resolve))
        
        // close this httpServer on 'close' event
        this.nuxt.hook('close', () => new Promise(httpServer.close))
      })
    }
    

    Tho I'm now using a probably more stable way, using nuxt programmatically! With hapi instead of express, since express is giving me trouble compiling and not showing the loading-screen(progress of building). Just use npx create-nuxt-app and create an app with a hapi server backend.

    The code with hapi would look like this:

    const consola = require('consola')
    const Hapi = require('@hapi/hapi')
    const HapiNuxt = require('@nuxtjs/hapi')
    
    async function start () {
      const server = require('./apollo/index')()
      const app = new Hapi.Server({
        host: process.env.HOST || '127.0.0.1',
        port: process.env.PORT || 3000
      })
    
      await app.register({
        plugin: HapiNuxt
      })
      
      app.route(await require('./routes')())
      
      await server.applyMiddleware({
        app,
        path: '/graphql'
      });
      console.log(`🚀 ApolloServer ready at ${server.graphqlPath}`);
      await server.installSubscriptionHandlers(app.listener)
      console.log(`🚀 ApolloSubscriptions ready at ${server.subscriptionsPath}`);
    
      await app.start()
    
      consola.ready({
        message: `Server running at: ${app.info.uri}`,
        badge: true
      })
    }
    process.on('unhandledRejection', error => consola.error(error))
    start().catch(error => console.log(error))
    

    Maybe i can help somebody