Apollo server accessing all datasources within resolvers before API request is sent - graphql

In a graphql query with multiple resolvers, I'm looking for a way to count how many times datasources are called before the first datasource API request is sent. The project that I am working on requires me to either allow or stop all the requests if the number of the datasources called within resolvers in a graphql query exceeds a certain number.
I am using an instance of the RESTDataSource to make API calls and each one of the resolvers call one or more datasources from the RESTDataSource class. I've been looking into this and far as I know, the RESTDataSource class doesn't have a method that shows me all the datasources requested because it is only called by the resolver and per request.
My problem is, I'm not finding a place where I can have access to all the datasources that will be called before the request is sent. I found that in the Apollo server instantiation, the only thing that I have access to are the resolvers, and not the datasources within each resolver, and as far as I know, not before the request is made so I can't stop it if the number of datasources calls exceed a certain threshold. I was hoping I could access that in the willSendRequest method inside the RESTDataSource class since from what I know, this is the only method that intercepts the request before being sent, but I don't think it's possible.
I'm pretty new to Apollo and I've been reading about this but didn't find a solution. I'd really appreciate any help.
Here's a simplified snippet of my code (not the original code):
resolvers.ts
export const resolvers: Resolvers = {
Query: {
getCompanies: (_, __, { dataSources }) => {
return dataSources.companyDatasource.getCompanies();
},
getCompany: (_, { name }, { dataSources }) => {
return dataSources.companyDatasource.getCompanyByName(name);
},
getCompanyCEOs: async (_, { name }, { dataSources }) => {
const company = await dataSources.companyDatasource.getCompanyByName(name);
return dataSources.companyDatasource.getCEOs(company.id);
},
....
company.datasource.ts
export default class CompanyDatasource extends RESTDataSource {
async willSendRequest(request) {
// some logic
}
async getCompanies() {
return this.get(`some_api_url`);
}
async getCompanyByName(name) {
return this.get(`some_api_url?companyName=name`);
}
//other external API endpoints
...
}
main.ts
const server = new ApolloServer({
typeDefs: schema,
schema,
resolvers,
dataSources,
cache: 'bounded',
});
await server.start();
Edit: I'm limiting the number of unique datasource API calls because the API I'm hitting has a limit. I tried instantiating a counter in the RESTDataSource class and using it in the willSendRequest to count how many datasource calls there are, but the problem is this is counting request by request and has no access to all the API requests that are coming from the resolver. For instance, if the getCompanies API can be called only once and I have 2 upcoming requests, I'll have to let one of them pass and only stop the second, because at that point I don't know there's a second request coming. My team has agreed to stop both requests in case the number of upcoming requests exceeds the available limit for the endpoint (this is specified in our database), so this is why I need to know beforehand how many API requests are there before even allowing the first request.

Related

GraphQL : share the context among all the resolvers

I am implementing Authorization in GraphQL. I am storing authorization status in context and want to share it with every resolver. I know that the Apollo server has a context which is shared across all the resolvers i.e :
const server = new ApolloServer({
schema,
resolvers,
context: async ({ event, context, appId, userId }) => {
....
return { event, context, appId, userId };
}
})
But my problem is , I want to update the context in different files and other resolver should always have the updated context.
is there any way we can create the context in such way that the changes made in the context object is always reflected to other resolvers??

Resolve batched query in single api call on apollo graphql server

I am currently using batching of similar calls on apollo graphql client.
So it sends single call to apollo-server if same graphql query is fired but with different parameters.
But on graphql server side I want to optimize such that there also a single call goes to resolve all graphql queries at once.
Graphql server makes call to api server but for each single batched call, not to resolve all queries at once.
I have used data-loader to use batching but it sends single request only.
genUserInfoDataLoader() {
return new DataLoader(async (arr) => {
logger.info(`---> UserInfoDataLoader Making calls for:${JSON.stringify(arr)}`);
const leagueId = arr[0].split(':')[1];
const UserIds = arr.map(a => a.split(':')[0]);
const userInfoMap = await this.post('endpoint/user-info ', {
userIds: UserIds.join(','),
tourId,
});
return UserIds
.map(
userId => (userInfoMap[userId] ? userInfoMap[userId] : [])
);
});
}

Log Query/Mutation actions to database for Auditing

My goal is to run some kind of webhook, cloud function or say I want to perform some kind of action after each query success or mutation success in graphql.
Means I want to log each and every action performed by users (kind of history of when what was created and updated).
How can this be implemented using some kind of middleware between graphql and DB (say mongo for now)?
Means that middleware should be responsible to run the logging action each time a query or mutation is called from front-end.
Tech stack being used is- Node, express, graphQl, Redis etc.
Any suggestions would really be appreciated.
Thanks
The solution I came up with was calling a function manually each time a query or mutate.
If you're using Apollo, you can utilize the formatResponse and formatError options for logging, as outlined in the docs.
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: error => {
console.log(error);
return error;
},
formatResponse: response => {
console.log(response);
return response;
},
});
Using an extension can allow you to hook into different phases of the GraphQL request and allow more granular logging. A simple example:
const _ = require('lodash')
const { GraphQLExtension } = require('graphql-extensions')
module.exports = class LoggingExtension extends GraphQLExtension {
requestDidStart(options) {
logger.info('Operation: ' + options.operationName)
}
willSendResponse(o) {
const errors = _.get(o, 'graphqlResponse.errors', [])
for (const error of errors) {
logger.error(error)
}
}
}
There's a more involved example here. You can then add your extension like this:
const server = new ApolloServer({
typeDefs,
resolvers,
extensions: [() => new YourExtension()]
});
If you're using express-graphql to serve your endpoint, your options are a bit more limited. There's still a formatError option, but no formatResponse. There is a way to pass in an extensions array as well, but the API is different from Apollo's. You can take a look at the repo for more info.

Context cleanup with apollo graphql server

I can create a context function in ApolloServer that will be executed before each request.
How can I have a cleanup function (after request execution)?
not sure if this is still relevant but I found this medium article addressing this exact question.
You can use this snippet for when you want code to run after all the resolvers have run:
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => {
...
},
plugins: [
{
requestDidStart: () => ({
willSendResponse: response => {
// this will run after every request
if (response.context.db) {
response.context.db.close();
}
}
})
}
]
});
I looked in the docs and this plugin system is nowhere to be found. I haven't found anything to discourage use like this but it does not seem to be an official solution.
I tried it in my project and it does seem to work.
Not sure why you would like to have context cleanup function? The context is set before as the middleware for each request therefore if you would like to have context set differently for another request you just inject the logic to the middleware. Maybe i just do not understand your use case for this. It would be helpful if you can clarify why you would like to apply it? The context is set per request, therefore you can inject enmpty object for some requests based on the req. If you need to clear up information from req object after graphql middleware you can do for example another middleware where you will set req.user (if you have authenticated user there) to null.

Flatten Apollo GraphQL Response (Data Field)

A Client needs a specific JSON structure which I wanted to provide by an GraphQL Response.
Unfortunately I have to get rid of the top level "data" field and flatten the response for that client.
Is there a way to do this by a resolver?
From:
{
"data" : {
"myKey":
{...}
}
}
To:
{
"myKey":
{...}
}
Thanks!
It's technically possible by utilizing the formatResponse option passed in to ApolloServer's constructor:
const formatResponse = ({ data, errors }) => data
const server = new ApolloServer({ typeDefs, resolvers, formatResponse })
or to do that for a specific query (for example, status), you can do:
const formatResponse = res => {
if (res.data && res.data.status) return res.data
return res
}
However, I would highly advise against this sort of approach for two main reasons. One, it breaks the spec, which is going to make your API incompatible with most client libraries out there designed for explicitly working with GraphQL APIs. Two, it leaves you with either having to inject your errors (validation or otherwise) into your actual data somewhere, or leaving them out altogether.
It's hard to imagine a scenario where pulling the data out of the response shouldn't be done by the client application -- and if you're having a hard time with that on a particular framework, that sounds like a good follow up SO question!

Resources