Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Subscriptions client examples? #6

Closed
joefru opened this issue Jan 14, 2018 · 17 comments
Closed

Subscriptions client examples? #6

joefru opened this issue Jan 14, 2018 · 17 comments
Assignees
Labels

Comments

@joefru
Copy link

joefru commented Jan 14, 2018

Very excited about NestJS. Thinking of becoming a sponsor if it proves out for my new project.

I need to get GraphQL subscriptions working. For starters, I've implemented the example from docs, and now I'm trying to connect GraphiQL with something like this:

  consumer
    .apply(graphiqlExpress({
      endpointURL: "/graphql", 
      subscriptionsEndpoint: `ws://localhost:${process.env.PORT || 3000}/subscriptions`
    }))
    .forRoutes({path: "/graphiql", method: RequestMethod.GET})
    .apply(graphqlExpress(req => ({schema, rootValue: req})))
    .forRoutes({path: "/graphql", method: RequestMethod.ALL});

I'm getting ERR_CONNECTION_REFUSED in browser console. I feel like I'm missing the connection between GraphQL Subscriptions and WebSockets, but I can't seem to piece it together from the docs.

Are there any working e2e examples out there?

@kamilmysliwiec
Copy link
Member

Hi @joefru,
I'm glad that you enjoy the project & and happy about the sponsor idea 🙂
Have you created the subscriptions endpoint in the same way as explained here? https://dev-blog.apollodata.com/tutorial-graphql-subscriptions-server-side-e51c32dc2951 (WebSocket Transport for Subscriptions)

@joefru
Copy link
Author

joefru commented Jan 14, 2018

I did happen to read that article. I'm wondering how to include the following code in 'the nest way'...

// Wrap the Express server
const ws = createServer(server);
ws.listen(PORT, () => {
  console.log(`GraphQL Server is now running on http://localhost:${PORT}`);
  // Set up the WebSocket for handling GraphQL subscriptions
  new SubscriptionServer({
    execute,
    subscribe,
    schema
  }, {
    server: ws,
    path: '/subscriptions',
  });
});

I tried something like this:

@WebSocketGateway({ port: process.env.PORT || 3000, namespace: "subscriptions" })
export class SubscriptionGateway implements OnGatewayInit {

	constructor(private graphQLFactory: GraphQLFactory) {}
	
	afterInit(server: any) {
		const schema = this.createSchema();

		new SubscriptionServer({
			execute,
			subscribe,
			schema
		}, {
			server: server,
			path: "/subscriptions",
		});
	}

	createSchema() {
		const typeDefs = this.graphQLFactory.mergeTypesByPaths("./**/*.graphql");

		return this.graphQLFactory.createSchema({ typeDefs });
	}
}

But that yields the following build-time error: Error: listen EADDRINUSE :::3000. When I switch to port 3001, it compiles but then I get the following error in browser console:

WebSocket connection to 'ws://localhost:3001/subscriptions' failed: Connection closed before receiving a handshake response

@kamilmysliwiec
Copy link
Member

Hi @joefru,
I think you should avoid using @Gateway() here, let's make use of an async component instead. Register a SUBSCRIPTION_SERVER somewhere, for example inside the AppModule:

components: [
    {
      provide: SUBSCRIPTION_SERVER,
      useFactory: async () => {
        const server = createServer();
        return await new Promise((resolve) => server.listen(PORT, resolve));
      },
    }
  ]

Then inject this component into module class:

export class ApplicationModule implements NestModule {
  constructor(
    private readonly graphQLFactory: GraphQLFactory,
    @Inject(SUBSCRIPTION_SERVER) private readonly ws,
  ) {}

  /// rest of code
}

Afterwards, create instance of SubscriptionServer:

  configure(consumer: MiddlewaresConsumer) {
    const schema = this.createSchema();
    this.initSubscriptionServer(schema);

    consumer
      .apply(graphiqlExpress({
        endpointURL: "/graphql", 
        subscriptionsEndpoint: `ws://localhost:${process.env.PORT || 3000}/subscriptions`
      }))
      .forRoutes({path: "/graphiql", method: RequestMethod.GET})
      .apply(graphqlExpress(req => ({schema, rootValue: req})))
      .forRoutes({path: "/graphql", method: RequestMethod.ALL});
  }

  initSubscriptionServer(schema) {
    new SubscriptionServer({ execute, subscribe, schema}, {
	server: this.ws,
	path: "/subscriptions",
    });
  }

That's all. Remember to kill server (ws instance) using OnModuleDestroy hook.

@joefru
Copy link
Author

joefru commented Jan 16, 2018

First of all, thanks for the help and quick responses. Unfortunately, I still think something is missing. I followed your directions using the nest graphql-apollo example for simplicity's sake, and I get these warnings:

(node:71978) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 3): TypeError: Cannot convert undefined or null to object
(node:71978) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

When trying to access GraphiQL on localhost, the browser churns for several minutes, and eventually I get ERR_CONNECTION_REFUSED. Any thoughts?

@kamilmysliwiec
Copy link
Member

Will try to reproduce this locally

@kamilmysliwiec
Copy link
Member

Could you provide a little bit more code? What actually have you changed? Did you add a snippet that I shared with your or sth more?

@joefru
Copy link
Author

joefru commented Jan 16, 2018

I cloned the graphql-apollo example and contained all changes within app.module.ts:

import {
  Inject,
  Module,
  MiddlewaresConsumer,
  NestModule,
  RequestMethod,
} from '@nestjs/common';
import { createServer } from 'http';
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
import { GraphQLModule, GraphQLFactory } from '@nestjs/graphql';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import { execute, subscribe } from 'graphql';
import { CatsModule } from './cats/cats.module';

@Module({
  components: [
    {
      provide: "SUBSCRIPTION_SERVER",
      useFactory: async () => {
        const server = createServer();

        return new Promise((resolve) => server.listen(process.env.PORT || 3000, resolve));
      },
    }
  ],
  imports: [CatsModule, GraphQLModule],
})
export class ApplicationModule implements NestModule {
  constructor(private readonly graphQLFactory: GraphQLFactory, @Inject("SUBSCRIPTION_SERVER") private readonly ws) {}

  configure(consumer: MiddlewaresConsumer) {
    const schema = this.createSchema();

    this.initSubscriptionServer(schema);

    consumer
      .apply(graphiqlExpress({
        endpointURL: '/graphql', 
        subscriptionsEndpoint: `ws://localhost:${process.env.PORT || 3000}/subs`
      }))
      .forRoutes({ path: '/graphiql', method: RequestMethod.GET })
      .apply(graphqlExpress(req => ({ schema, rootValue: req })))
      .forRoutes({ path: '/graphql', method: RequestMethod.ALL });
  }

  createSchema() {
    const typeDefs = this.graphQLFactory.mergeTypesByPaths('./**/*.graphql');
    const schema = this.graphQLFactory.createSchema({ typeDefs });
    return this.graphQLFactory.createSchema({ typeDefs });
  }

  initSubscriptionServer(schema) {
    const ss = new SubscriptionServer({execute, subscribe, schema}, {
      server: this.ws,
      path: "/subs",
    });
  }

  onModuleDestroy() {
    this.ws.close();
  }
}

@kamilmysliwiec
Copy link
Member

kamilmysliwiec commented Jan 18, 2018

@joefru I'm sorry.. It was my mistake 🙁 Let's use code below instead:

{
      provide: 'SUBSCRIPTION_SERVER',
      useFactory: () => {
        const server = createServer();
        return new Promise(resolve => server.listen(process.env.PORT || 3000, () => resolve(server)));
      },
},

@joefru
Copy link
Author

joefru commented Jan 19, 2018

Making progress... That change let's me successfully create the subscription server. I still can't get a subscription to listen. I have this:

cats.resolvers.ts

import { Component, UseGuards } from '@nestjs/common';
import { Query, Mutation, Resolver, Subscription } from '@nestjs/graphql';

import { Cat } from './interfaces/cat.interface';
import { CatsService } from './cats.service';
import { CatsGuard } from './cats.guard';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

@Resolver('Cat')
export class CatsResolvers {
  constructor(private readonly catsService: CatsService) {}

  @Query()
  @UseGuards(CatsGuard)
  async getCats() {
    return await this.catsService.findAll();
  }

  @Query('cat')
  async findOneById(obj, args, context, info): Promise<Cat> {
    const { id } = args;
    return await this.catsService.findOneById(+id);
  }

  @Mutation('createCat')
  async create(obj, args, context, info): Promise<Cat> {
    const cat = await this.catsService.create(args.name);

    pubsub.publish('catCreated', {catCreated: cat});

    return cat;
  }

  @Subscription('catCreated')
  catCreated() {
    return {
      subscribe: () => pubsub.asyncIterator('catCreated'),
    };
  }
}

cats.types.graphql

type Query {
  getCats: [Cat]
  cat(id: ID!): Cat
  catByHumanId(id: ID!): Cat
}

type Mutation {
  createCat(name: String): Cat
}

type Subscription {
  catCreated: Cat
}

type Cat {
  id: Int
  name: String
  age: Int
  humanId: Int
}

When I run the following subscription in GraphiQL:

subscription catname {
  catCreated {
    name
  }
}

It displays [object Object] in the results window. It looks like it quickly flashes Your subscription data will appear here after server publication! and then immediately changes to [object Object]. Similar behavior was described in this thread. Unfortunately, the suggestions in that discussion have not led me to a solution.

Ultimately, I'd love to see the graphql-apollo example be extended to include a simple working subscription like catCreated that I can run from GraphiQL.

@kamilmysliwiec
Copy link
Member

I updated example with the subscription server:
https://github.com/nestjs/nest/tree/master/examples/12-graphql-apollo

Subscribe in one window:

subscription {
  catCreated {
  	name
  }
}

Mutate in the different one:

mutation {
  createCat(name: "Nest") {
    name
  }
}

Everythin works fine 🙂

@joefru
Copy link
Author

joefru commented Jan 22, 2018

Downloaded the latest example but I still get [object Object] in the results window when I run the subscription query.

Also, what I'd really like to achieve is to have the subscription server listen on the same port as the express http server. That way, I can deploy a single application to Heroku, which only allows a single port per deployment.

@kamilmysliwiec
Copy link
Member

I have tested this example and it works fine for me, there's no [object Object], that's weird. What kind of mutation are you executing from the graphiql? It has nothing to do with @nestjs/graphql module, but rather with this issue that you mentioned about earlier (apollographql/subscriptions-transport-ws#236 (comment))

@andrew-hamilton-dev
Copy link

andrew-hamilton-dev commented Jan 31, 2018

I was having the same issue with [Object Object] as @joefru but I was able to resolve it by playing around with the package versions in the dependency versions. Below are the versions i ended up with to have it working. Might help you out @joefru.

"dependencies": {
    "@nestjs/common": "^4.5.9",
    "@nestjs/core": "^4.5.10",
    "@nestjs/graphql": "^2.0.0",
    "apollo-server-express": "^1.3.2",
    "graphql": "0.11.7",
    "graphql-subscriptions": "^0.5.6",
    "graphql-tools": "^2.19.0",
    "reflect-metadata": "^0.1.12",
    "rxjs": "^5.5.6",
    "subscriptions-transport-ws": "^0.9.5",
    "typescript": "^2.6.2",
    "ws": "^4.0.0"
  },
  "devDependencies": {
    "@types/node": "^9.4.0",
    "ts-node": "^4.1.0"
  }

@nestjs nestjs deleted a comment from Stradivario Mar 19, 2018
@Stradivario
Copy link

@kamilmysliwiec i really don't understand why you delete the comment...

@Stradivario
Copy link

Its a Community right ? and you can share your Subscription Methods to Others ? What is your point ? and problem ?

@Stradivario
Copy link

I am out will ban nest community for sure

@lock
Copy link

lock bot commented Apr 25, 2020

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@lock lock bot locked as resolved and limited conversation to collaborators Apr 25, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

4 participants