# GraphQL WS

GraphQL Websocket allows you to use the subscription feature of GraphQL using the Websocket transport protocol. This module is based on the graphql-ws (opens new window) package. It pre-configures the socket server and GraphQL server to work together.

# Feature

  • Support multiple GraphQL server
  • Enable subscription feature of GraphQL

# Installation

This module need to be used with @tsed/apollo module. So, you must install it before (see here).

    import {Configuration} from "@tsed/common";
    import "@tsed/platform-express";
    import "@tsed/apollo";
    import "@tsed/graphql-ws";
    import {join} from "path";
    
    @Configuration({
      apollo: {
        server1: {
          // GraphQL server configuration
          path: "/",
          playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio
          plugins: [], // Apollo plugins
    
          wsServerOptions: {
            // See options descriptions on
          },
          wsUseServerOptions: {
            // See options descriptions on GraphQL WS
          }
    
          // Give custom server instance
          // server?: (config: Config) => ApolloServer;
    
          // ApolloServer options
          // ...
          // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html
        }
      },
      graphqlWs: {
        // global options
        wsServerOptions: {
          // See options descriptions on
        },
        wsUseServerOptions: {
          // See options descriptions on
        }
      }
    })
    export class Server {}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40

    # Register plugins

    You can register plugins with the plugins property. The plugins are executed in the order of declaration.

    import {Configuration} from "@tsed/common";
    import "@tsed/platform-express";
    import "@tsed/apollo";
    import {join} from "path";
    
    @Configuration({
      apollo: {
        server1: {
          plugins: [] // Apollo plugins
        }
      }
    })
    export class Server {}
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    But if you need to register and access to the injector, you can use the $alterApolloServerPlugins hook. For example, you can register the graphql-ws necessary to support the subscription feature of GraphQL like this:

    import {Constant, Inject, InjectorService, Module} from "@tsed/di";
    import {useServer} from "graphql-ws/lib/use/ws";
    import Http from "http";
    import Https from "https";
    import {WebSocketServer} from "ws";
    import {GraphQLWSOptions} from "./GraphQLWSOptions";
    
    @Module()
    export class GraphQLWSModule {
      @Constant("graphqlWs", {})
      private settings: GraphQLWSOptions;
    
      @Inject(Http.Server)
      private httpServer: Http.Server | null;
    
      @Inject(Https.Server)
      private httpsServer: Https.Server | null;
    
      @Inject()
      private injector: InjectorService;
    
      createWSServer(settings: GraphQLWSOptions) {
        const wsServer = new WebSocketServer({
          ...(this.settings.wsServerOptions || {}),
          ...settings.wsServerOptions,
          server: this.httpsServer || this.httpServer!,
          path: settings.path
        });
    
        return useServer(
          {
            ...(this.settings.wsUseServerOptions || {}),
            ...settings.wsUseServerOptions,
            schema: settings.schema
          },
          wsServer
        );
      }
    
      async $alterApolloServerPlugins(plugins: any[], settings: GraphQLWSOptions) {
        const wsServer = await this.createWSServer(settings);
    
        this.injector.logger.info(`Create GraphQL WS server on: ${settings.path}`);
    
        return plugins.concat({
          serverWillStart() {
            return {
              async drainServer() {
                await wsServer.dispose();
              }
            };
          }
        } as any);
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    Note

    Ts.ED provide a @tsed/graphql-ws package to support the subscription feature of GraphQL. See here (opens new window) for more details.

    # Nexus

    # Installation

      Now, we can configure the Ts.ED server by importing @tsed/apollo in your Server:

      import {Configuration} from "@tsed/common";
      import "@tsed/platform-express";
      import "@tsed/apollo";
      import {schema} from "./schema";
      import {join} from "path";
      
      @Configuration({
        apollo: {
          server1: {
            // GraphQL server configuration
            path: "/",
            playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio
            schema,
            plugins: [] // Apollo plugins
      
            // Give custom server instance
            // server?: (config: Config) => ApolloServer;
      
            // ApolloServer options
            // ...
            // See options descriptions on https://www.apollographql.com/docs/apollo-server/api/apollo-server.html
          }
        }
      })
      export class Server {}
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25

      Then create schema/index.ts:

      import {makeSchema} from "nexus";
      import {join} from "path";
      
      export const schema = makeSchema({
        types: [], // 1
        outputs: {
          typegen: join(process.cwd(), "..", "..", "nexus-typegen.ts"), // 2
          schema: join(process.cwd(), "..", "..", "schema.graphql") // 3
        }
      });
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      # TypeGraphQL

      # Installation

      To begin, install the @tsed/typegraphql package:

        Now, we can configure the Ts.ED server by importing @tsed/typegraphql in your Server:

          # Types

          We want to get the equivalent of this type described in SDL:

          type Recipe {
            id: ID!
            title: String!
            description: String
            creationDate: Date!
            ingredients: [String!]!
          }
          
          1
          2
          3
          4
          5
          6
          7

          So we create the Recipe class with all properties and types:

          class Recipe {
            id: string;
            title: string;
            description?: string;
            creationDate: Date;
            ingredients: string[];
          }
          
          1
          2
          3
          4
          5
          6
          7

          Then we decorate the class and its properties with decorators:

          import {Field, ID, ObjectType} from "type-graphql";
          
          @ObjectType()
          export class Recipe {
            @Field((type) => ID)
            id: string;
          
            @Field()
            title: string;
          
            @Field({nullable: true})
            description?: string;
          
            @Field()
            creationDate: Date;
          
            @Field((type) => [String])
            ingredients: string[];
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19

          The detailed rules for when to use nullable, array and others are described in fields and types docs (opens new window).

          # Resolvers

          After that we want to create typical crud queries and mutation. To do that we create the resolver (controller) class that will have injected RecipeService in the constructor:

          import {Inject} from "@tsed/di";
          import {ResolverController} from "@tsed/typegraphql";
          import {Arg, Args, Query} from "type-graphql";
          import {RecipeNotFoundError} from "../errors/RecipeNotFoundError";
          import {RecipesService} from "../services/RecipesService";
          import {Recipe} from "../types/Recipe";
          import {RecipesArgs} from "../types/RecipesArgs";
          
          @ResolverController(Recipe)
          export class RecipeResolver {
            @Inject()
            private recipesService: RecipesService;
          
            @Query((returns) => Recipe)
            async recipe(@Arg("id") id: string) {
              const recipe = await this.recipesService.findById(id);
              if (recipe === undefined) {
                throw new RecipeNotFoundError(id);
              }
          
              return recipe;
            }
          
            @Query((returns) => [Recipe])
            recipes(@Args() {skip, take}: RecipesArgs) {
              return this.recipesService.findAll({skip, take});
            }
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          23
          24
          25
          26
          27
          28

          # Multiple GraphQL server

          If you register multiple GraphQL servers, you must specify the server id in the @ResolverController decorator.

          @ResolverController(Recipe, {id: "server1"})
          
          1

          Another solution is to not use @ResolverController (use @Resolver from TypeGraphQL), and declare explicitly the resolver in the server configuration:

          @Configuration({
            graphql: {
              server1: {
                resolvers: {
                  RecipeResolver
                }
              },
              server2: {
                resolvers: {
                  OtherResolver
                }
              }
            }
          })
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14

          # Data Source

          Data source is one of the Apollo server features which can be used as option for your Resolver or Query. Ts.ED provides a DataSourceService decorator to declare a DataSource which will be injected to the Apollo server context.

          import {DataSource} from "@tsed/typegraphql";
          import {RESTDataSource} from "apollo-datasource-rest";
          import {User} from "../models/User";
          @DataSource()
          export class UserDataSource extends RESTDataSource {
            constructor() {
              super();
              this.baseURL = "https://myapi.com/api/users";
            }
          
            getUserById(id: string): Promise<User> {
              return this.get(`/${id}`);
            }
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14

          Then you can retrieve your data source through the context in your resolver like that:

          import {ResolverController} from "@tsed/typegraphql";
          import {Arg, Authorized, Ctx, Query} from "type-graphql";
          import {UserDataSource} from "../datasources/UserDataSource";
          import {User} from "../models/User";
          
          @ResolverController(User)
          export class UserResolver {
            @Authorized()
            @Query(() => User)
            public async user(@Arg("userId") userId: string, @Ctx("dataSources") dataSources: any): Promise<User> {
              const userDataSource: UserDataSource = dataSources.userDataSource;
          
              return userDataSource.getUserById(userId);
            }
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15

          # Get Server instance

          ApolloService (or TypeGraphQLService) lets you to retrieve an instance of ApolloServer.

          import {AfterRoutesInit} from "@tsed/common";
          import {Inject, Injectable} from "@tsed/di";
          import {ApolloService} from "@tsed/apollo";
          import {ApolloServer} from "apollo-server-express";
          
          @Injectable()
          export class UsersService implements AfterRoutesInit {
            @Inject()
            private ApolloService: ApolloService;
            // or private typeGraphQLService: TypeGraphQLService;
          
            private server: ApolloServer;
          
            $afterRoutesInit() {
              this.server = this.apolloService.get("server1")!;
            }
          }
          
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17

          For more information about ApolloServer, look at its documentation here (opens new window);

          # Testing

          Here is an example to create a test server based on TypeGraphQL and run a query:

          TIP

          The unit example is also available to test any Apollo Server!

            # Author

              # Maintainers

                Last Updated: 4/26/2024, 5:13:34 PM

                Other topics