hopefully someone can help me with this little problem, I just cannot figure it out right now.
Problem Statement:
I want to access 'context' for the sake of authentication in my DataLoader
. This DataLoader
is defined in a seperate path /loaders
. In my resolvers.js
file I can access my context nicely with dataSources.userAPI.getAllUsers()
.
But how to access it anywhere else in my serverside application, like f.e. in my /loaders
folder?
I just cant get it how to get access to my context object to then pass the token to the DataLoader
to then load the data from my API and then pass this data to my resolvers.js
file.
Every help is highly appreciated, I don't know how to solve this simple thing .. Thanks!
Here comes the code:
index.js
const express = require('express');
const connectDB = require('./config/db');
const path = require('path');
var app = express();
const cors = require('cors')
const axios = require('axios')
// apollo graphql
const { ApolloServer } = require('apollo-server-express');
const DataLoader = require('dataloader')
const { userDataLoader } = require('./loaders/index')
// Connect Database
connectDB();
// gql import
const typeDefs = require('./schema');
const resolvers = require('./resolvers')
// apis
const UserAPI = require('./datasources/user')
// datasources
const dataSources = () => ({
userAPI: new UserAPI(),
});
// context
const context = ({ req, res }) => ({
token: req.headers.authorization || null,
loaders: {
userLoader: userDataLoader,
},
res
})
// init server
const server = new ApolloServer({
typeDefs,
resolvers,
dataSources,
context
});
// middleware
app.use(express.json());
// cors
var corsOptions = {
credentials: true
}
app.use(cors(corsOptions))
// serve middleware
server.applyMiddleware({
app
});
// run server
app.listen({ port: 4000 }, () =>
console.log(`Server ready at http://localhost:4000${server.graphqlPath}`)
);
module.exports = {
dataSources,
context,
typeDefs,
resolvers,
loaders,
ApolloServer,
UserAPI,
server,
};
loaders/index.js
const userDataLoader = require('./user')
module.exports = {
userDataLoader
}
loaders/user.js
const UserAPI = require('../datasources/users')
// init loader
const userDataLoader = new DataLoader(keys => batchUser(keys))
// batch
const batchUsers = async (keys) => {
// this part is not working!
// How to access the UserAPI methods in my DataLoader?
// Or lets say: How to access context from here,
// so I can add auth for the server I am requesting data from?
const userAPI = new UserAPI()
const users = userAPI.getAllUsers()
.then(res => {
return res.data
})
return keys.map(userId => users.find(user=> user._id === userId))
}
module.exports = userDataLoader
resolvers.js
// here is just my api call to get the data from my
// dataloader with userLoader.load() and this works perfectly
// if I just make API calls with axios in my loaders/user
// here just a little snippet from the resolver file
....
users: async (parent, args, { loaders }) => {
const { userLoader } = loaders
if (!parent.users) {
return null;
}
return await userLoader.load(parent.user)
},
....
datasources/user.js
const { RESTDataSource } = require('apollo-datasource-rest');
class UserAPI extends RESTDataSource {
constructor() {
super()
this.baseURL = 'http://mybaseurl.com/api'
}
willSendRequest(request) {
request.headers.set('Authorization',
this.context.token
);
}
async getUserById(id) {
return this.get(`/users/${id}`)
}
async getAllUsers() {
const data = await this.get('/users');
return data;
}
}
module.exports = UserAPI;
You can use apollo-link-rest to call REST API inside your GraphQL queries.
Resolver is a collection of functions that generate response for a GraphQL query. In simple terms, a resolver acts as a GraphQL query handler. Every resolver function in a GraphQL schema accepts four positional arguments as given below − fieldName:(root, args, context, info) => { result }
I recommend to create a function that creates data loaders and provides the needed state inside of a closure and not use data sources at all:
module.exports.createDataloaders = function createDataLoaders(options) {
const batchUsers = ids => {
const users = await fetch('/users/', { headers: { Authorization: options.auth } });
// ...
}
return {
userLoader: new Dataloader(batchUsers);
};
}
// now in index.js
// context
const context = ({ req, res }) => ({
token: req.headers.authorization || null,
loaders: createDataloaders({ auth: req.headers.authorization || null }),
res
})
// init server
const server = new ApolloServer({
typeDefs,
resolvers,
context
});
Or consider flipping your layers around:
const { RESTDataSource } = require('apollo-datasource-rest');
class UserAPI extends RESTDataSource {
constructor() {
super()
this.baseURL = 'http://mybaseurl.com/api'
this.dataloader = new Dataloader(ids => {
// use this.get here
});
}
willSendRequest(request) {
request.headers.set('Authorization',
this.context.token
);
}
async getUserById(id) {
return this.dataloader.load(id);
}
async getAllUsers() {
const data = await this.get('/users');
return data;
}
}
module.exports = UserAPI;
But Datasources are not designed to be used with Dataloader as explained here in the Apollo Docs itself. So if you want to keep the sources maybe get rid of the loaders alltogether.
Basically what they are saying is that GraphQL APIs that wrap rest APIs benefit more from (global) request caching than from pre request data loaders. Caching across requests can lead to authorization issues though, so be careful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With