cgatian
cgatian

Reputation: 22994

NestJS GraphQL DataSources

WARNING THE CODE BELOW IS INCORRECT DATASOURCES NEED TO BE CREATED PER REQUEST.

DO NOT USE THE CODE BELOW

Im attempting to use a apollo-rest-datasource with NestJS. The downside Im seeing is the DataSources do not participate in NestJS' DI system.

I was able to work around this by having NestJS instantiate the singleton datasources and then using GraphQLModule.forRootAsync inject these instances into the dataSources property of Apollo Server.

 GraphQLModule.forRootAsync({
      imports: [
        DataSourcesModule
      ],
      useFactory: (...args: DataSource[]) => {
        return {
          typePaths: ['./**/*.graphql'],
          context: ({req}: {req: Request}) => ({ token: req.headers.authorization }),
          playground: true,
          dataSources: () => {
            let dataInstances = {} as any;
            args.forEach(arg => {
              const dataSource = arg as any;
              dataInstances[dataSource.constructor.name] = arg;
            });
            return dataInstances;
          },
        };
      },
      inject: [...dataSources]

I now get DI working in my DataSource, and can use DI within the resolvers to include my DataSource instances (instead of accessing from the GraphQL context). While this works, it just feels wrong.

Is there a better approach for NestJS' DI and Apollo GraphQL context?

Upvotes: 4

Views: 4365

Answers (2)

A_A
A_A

Reputation: 340

I was able to solve this issue by using the @Context decorator on each of my resolvers methods in order to grab the data sources. The full answer with an example here.

Upvotes: -1

Jesse Carter
Jesse Carter

Reputation: 21207

RESTDataSource looks like it's just a regular class. You should be able to simply apply the @Injectable() decorator and treat them as a regular Nest service. This would allow you to inject dependencies into them as well as to inject the DataSources into your Resolvers without needing to bootstrap things in the GraphQLModule like you've shown.

const { RESTDataSource } = require('apollo-datasource-rest');
import { Injectable } from '@nestjs/common';

@Injectable()
class MoviesAPI extends RESTDataSource {
  // Inject whatever Nest dependencies you want
  constructor(private readonly someDependency: SomeDependency) {
    super();
    this.baseURL = 'https://movies-api.example.com/';
  }

  async getMovie(id) {
    return this.get(`movies/${id}`);
  }

  async getMostViewedMovies(limit = 10) {
    const data = await this.get('movies', {
      per_page: limit,
      order_by: 'most_viewed',
    });
    return data.results;
  }
}

@Injectable()
class ResolverClass {
   // Inject your datasources
   constructor(private readonly moviesApi: MoviesAPI) { }
}

You'll just have to make sure that put your DataSource classes into the providers of the appropriate Nest module and optionally export them if they need to be consumed from other modules as well.

Update: Since the dataSources need to also be passed into ApolloServer you could potentially do this in a more Nest-y way by introducing your own decorator to apply to each DataSource and then using reflection to "discover" all the sources that exist in your application. This isn't something that's currently documented well but you can take a look at examples from some of the Nest source code for how to accomplish this. For reference here's the code that discovers all of the @Resolver decorated classes for the GraphQL module.

It basically boils down to using the ModulesContainer and MetadataScanner to find all providers that exist in the application and then filtering to find which ones have applied your custom decorator. (eg. @DataSource()).

I don't think that what you have now is necessarily a big issue but if you were to implement it this way you wouldn't have to worry about remembering to add new dataSources each time.

Upvotes: 4

Related Questions