Reputation: 22994
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
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
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