Skip to content

Apollo ​

Unlock microservices potential with Apollo GraphQL. Seamlessly integrate APIs, manage data, and enhance performance. Explore Apollo's innovative solutions. Ts.ED provides a module to create multiple Apollo server and bind it with Ts.ED server (Express or Koa).

Feature ​

WARNING

Since v7.70.0, Ts.ED use Apollo Server v4. If you are using Apollo Server v3, you can use the @tsed/apollo@7.69.0 package.

  • Create Apollo Server and bind it with Ts.ED,
  • Create multiple servers,
  • Support TypeGraphQL, Nexus or standalone Apollo server (or whatever).
  • Support subscription with GraphQL WS

Installation ​

bash
npm install --save @tsed/apollo graphql @apollo/server
npm install --save-dev apollo-server-testing
bash
npm install --save @tsed/apollo graphql @apollo/server @as-integration/koa
npm install --save-dev apollo-server-testing
typescript
import {Configuration} from "@tsed/di";
import "@tsed/platform-express";
import "@tsed/apollo";
import {join} from "node:path";

@Configuration({
  apollo: {
    server1: {
      // GraphQL server configuration
      path: "/",
      playground: true, // enable playground GraphQL IDE. Set false to use Apollo Studio
      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 {}

Register plugins ​

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

typescript
import {Configuration} from "@tsed/di";
import "@tsed/platform-express";
import "@tsed/apollo";
import {join} from "node:path";

@Configuration({
  apollo: {
    server1: {
      plugins: [] // Apollo plugins
    }
  }
})
export class Server {}

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:

typescript
import {Constant, Inject, InjectorService, Module} from "@tsed/di";
import {useServer} from "graphql-ws/lib/use/ws";
import Http from "node:http";
import Https from "node: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;

  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);
  }

  protected 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
    );
  }
}

Note

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

Get Server instance ​

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

ts
import {AfterRoutesInit} from "@tsed/platform-http";
import {Inject, Injectable} from "@tsed/di";
import {ApolloService} from "@tsed/apollo";
import {ApolloServer} from "@apollo/server";

@Injectable()
export class UsersService implements AfterRoutesInit {
  @Inject()
  private ApolloService: ApolloService;
  // or private typeGraphQLService: TypeGraphQLService;

  private server: ApolloServer;

  $afterRoutesInit() {
    this.server = this.apolloService.get("server1")!;
  }
}

For more information about ApolloServer, look at its documentation here;

DataSources ​

Apollo Server provides a mechanism to fetch data from a REST API or a database. This mechanism is called DataSources. Ts.ED allows you to register your DataSources using the @DataSource decorator.

typescript
import {DataSource} from "@tsed/apollo";
import {RESTDataSource} from "@apollo/datasource-rest";

@DataSource()
export class MyDataSource extends RESTDataSource {
  @InjectContext()
  protected $ctx: PlatformContext;

  @Constant("envs.MY_API_URL", "http://localhost:8001")
  private baseURL: string;

  getMyData(id: string) {
    return this.get(`/rest/calendars/${id}`, {
      headers: {
        Authorization: `Bearer ${this.$ctx.request.get("authorization")}`
      }
    });
  }
}

Alter Apollo Context 7.70.0+ ​

You can alter the Apollo context by using the $alterApolloContext hook. This hook is called each the GraphQL request is handled.

For example, you can call a service to get the user scope from the request token.

typescript
import type {AlterApolloContext, ApolloContext} from "@tsed/apollo";
import {PlatformContext} from "@tsed/platform-http";
import {Injectable} from "@tsed/di";
import {AuthService} from "../auth/AuthService";

export interface CustomApolloContext extends ApolloContext {
  scope: string;
  token: string | undefined;
}

@Injectable()
export class MyModule implements AlterApolloContext {
  @Inject()
  protected authService: AuthService;

  async $alterApolloContext(context: ApolloContext, $ctx: PlatformContext): CustomApolloContext {
    const token = $ctx.request.get("authorization");

    return {
      ...context,
      token,
      scope: await this.authService.getScope(token)
    };
  }
}

Now, your context will be updated with the authScope property, and you can access it in your DataSources or resolvers.

typescript
import {DataSource, InjectApolloContext, ApolloContext, InjectApolloContext} from "@tsed/apollo";
import {Constant, Opts} from "@tsed/di";
import {RESTDataSource} from "@apollo/datasource-rest";

@DataSource()
export class MyDataSource extends RESTDataSource {
  @InjectContext()
  protected $ctx: PlatformContext;

  @Constant("envs.MY_BACK_URL", "http://localhost:8001")
  protected baseURL: string;

  @InjectApolloContext()
  protected context: CustomApolloContext;

  constructor(server: ApolloServer, logger: Logger) {
    super({
      logger,
      cache: server.cache
    });
  }

  willSendRequest(path, request) {
    request.headers["authorization"] = this.context.token;
  }

  getMyData(id: string) {
    return this.get(`/rest/calendars/${id}`);
  }
}

Here another example using apollo-datasource-http:

ts
import {DataSource, InjectApolloContext, ApolloContext, InjectApolloContext} from "@tsed/apollo";
import {ApolloServer} from "@apollo/server";
import {Opts, Configuration} from "@tsed/di";

@DataSource()
class MoviesAPI extends HTTPDataSource {
  constructor(@Opts {token}: CustomApolloContext, server: ApolloServer, logger: Logger, @Configuration() configuration: Configuration) {
    // the necessary arguments for HTTPDataSource
    super(configuration.get("envs.MOVIES_API_URL"), {
      logger
    });

    // We need to call the initialize method in our data source's
    // constructor, passing in our cache and contextValue.
    this.initialize({cache: server.cache, context: token});
  }

  getMovie(id: string) {
    return this.get(`movies/${encodeURIComponent(id)}`);
  }
}

WARNING

Injecting ApolloServer on constructor is only possible inside a DataSource class. If you need to inject ApolloServer in another class, you can use the ApolloService to retrieve the server instance.

Author ​

Maintainers ​

Released under the MIT License.