Using composeResolvers with an array of resolvers - graphql

Is it possible to pass an array of resolvers into composeResolvers from "#graphql-tools/resolvers-composition". If I do something like composeResolvers(authorsResolvers, resolversComposition); it works however if I try to pass in all my resolvers, composeResolvers(resolvers, resolversComposition);, it doesn't work.
const rootResolver = {};
const resolvers = [rootResolver, authorsResolvers, booksResolvers];
const isAuthenticated =
() =>
(next: any) =>
async (root: any, args: any, context: any, info: any) => {
if (!context.currentUser) {
throw new Error("You are not authorized");
}
return next(root, args, context, info);
};
const resolversComposition = {
"*.*": [isAuthenticated()],
};
const composedResolvers = composeResolvers(resolvers, resolversComposition);
export default composedResolvers;

No, composeResolvers expects an object as the first argument.
If all valid resolver objects, you could do something like this:
const resolvers = {...rootResolver, ...authorsResolvers, ...booksResolvers};
But make sure to do the necessary adjustments in case they have overlapping attributes, otherwise the last one will override the former.

Related

How to get a type of a single resolver function?

I wondering if it possible to get a type of single resolver function generated by graphql-codegen?
I also use graphql-modules with graphql-modules-preset. All of these libraries provide me a list of automatically generated types, but unfortunately I can't find a type of a single resolver function.
I would expected something like this:
const createRepository: CreateRepositoryMutationResolver = (parent, args, context, info) => {
// Code goes here
}
where all arguments of the function (parent, args, context and info) are strongly typed.
Instead, I could only find this way of providing types
const createRepository: ResolverFn<{}, MutationCreateRepositoryInput, GraphQLContext, GraphQLInfo> = (parent, args, context, info) => {
// Code goes here
}
I would like to skip this step where I need to fill in generics of ResolverFn
Any ideas?
P.S. If I declare all resolvers in single file, then types work as expected.
const resolvers: RepositoryModule.Resolvers = {
Mutations: {
createRepository: (parent, args, context, info) => {
// all types of all arguments work as expected
},
removeRepository: (parent, args, context, info) => {
// all types of all arguments work as expected
}
}
}
But I want to move each resolver into separate file
I did a little research on the types that were generated by graphql-codegen and found that all properties of Resolvers interface as well as all of its children are optional. It means that I can re-use this interface, but specify only resolvers that I need in the separate files
First file
// create-repository.resolver.ts
import { RepositoryModule } from '../../generated-types/module-types'
const resolvers: RepositoryModule.Resolvers = {
Mutation: {
createRepository: async (parent, args, context, info) => {
// code goes here
}
}
}
export default resolvers
second file
// create-repository.resolver.ts
import { RepositoryModule } from '../../generated-types/module-types'
const resolvers: RepositoryModule.Resolvers = {
Mutation: {
removeRepository: async (parent, args, context, info) => {
// code goes here
}
}
}
export default resolvers
and after, in a module, I can join all resolvers by loadFileSync
import { loadFilesSync } from '#graphql-tools/load-files'
import { createModule } from 'graphql-modules'
import { join } from 'path'
const resolvers = loadFilesSync(join(__dirname, './**/*.resolver.ts'), { useRequire: true })
export const repositoryModule = createModule({
id: 'repositoryModule-module',
dirname: __dirname,
resolvers: resolvers,
...
})
I'm not sure if this is the correct way to use this interface, but it works for me

How can I excute code after the entire request for GraphQL has finished in NestJS?

I'm trying to set up Sentry transactions with something like this:
(A globally registered interceptor)
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const transaction = Sentry.startTransaction({
op: 'gql',
name: 'GraphQLTransaction'
});
this.setTransaction(context, transaction); // adds a `transaction` property to the context
return next.handle().pipe(
tap((...args) => {
transaction.finish();
}),
);
}
and then inside a FieldMiddleware I track spans with something like this:
(A globally registered field middleware)
export const checkRoleMiddleware: FieldMiddleware = async (
ctx: MiddlewareContext,
next: NextFn,
) => {
try {
const { info, context: gqlCtx } = ctx;
const transaction: Transaction = gqlCtx.transaction;
const span = transaction.startChild({
op: 'resolver',
description: `${info.parentType.name}.${info.fieldName}`,
});
const result = await next();
span.finish();
return result;
} catch (e) {
// log error to console, since for some reason errors are silenced in field middlewares
console.error(e);
Sentry.captureException(e);
return next();
}
};
However, it seems that transaction.finished() inside the tap() operator gets called before fields are resolved.
Is there another operator that I should be using?

Migrating to 3.5.0 with apollo-server-express

I'm trying to update the version of one of my projects from node 12.x.x to node.14.x.x.
Now I saw the documentation of apollo to migrate to the v3 but it's not clear at all someone know where to start? Is it gonna be easier to make all the apollo part systems from scratch?
Current file that should be updated:
const { ApolloServer } = require('apollo-server-express');
const { makeExecutableSchema } = require('graphql-tools');
const { applyMiddleware } = require('graphql-middleware');
const { gql } = require('apollo-server-express');
const { resolvers, typeDefs } = require('graphql-scalars');
const { types } = require('./typedefs/types');
const { inputs } = require('./typedefs/inputs');
const { queries } = require('./typedefs/queries');
const { mutations } = require('./typedefs/mutations');
const customResolvers = require('./resolvers');
const { permissions } = require('./permissions/permissions');
const graphqlApis = gql`
${types}
${queries}
${inputs}
${mutations}
`;
const schema = makeExecutableSchema({
typeDefs: [...typeDefs, graphqlApis],
resolvers: {
...customResolvers,
...resolvers,
},
});
const server = new ApolloServer({
context: ({ req }) => {
const headers = req.headers || '';
return headers;
},
schema: applyMiddleware(schema, permissions),
});
module.exports = server;
Current error when trying to run: Error: You must 'await server.start()' before calling 'server.applyMiddleware()'
I would simply love to get some solutions/help/insight with it.
As mentioned in the docs, you need to surround your code with an Async function
https://www.apollographql.com/docs/apollo-server/migration/
(async function () {
// ....
})();
Or Like so: ...
define your typeDefs & resolvers first
const typeDefs = "" // your typedefs here
const resolvers = "" // your resolvers here
async function startApolloServer(typeDefs, resolvers) {
// Apollo Express 3.5.x code goes here
// ....
await server.start();
// promise resolve...
}
startApolloServer(typeDefs, resolvers)

DIalogflow-Fullfilment GraphQL

anyone here implemented Dialog flow fullfilment on graphql server? How do you handle it? Do you handle fulfillment as a mutation or you implement it as a separate rest endpoint?
I am able to expose my local server using ngrok but I am not sure how to go about setting up the fulfillment. I had separated my DF code from GraphQL code such that the DF module only exposes the methods that handle event and text queries to Dialog flow:
// df/index.js
module.exports={
text: ()=>{
self=module.exports
// ...
return self.getResult()
},
event: ()=>{
self=module.exports
// ...
return self.getResult()
},
getResult:()=>{
//...
return{
query,
response,
cards,
quickReply
}
}
}
Then this is passed through the graphQL context and exposed to the bot.resolver.js module where respective mutations for handling text and events are defined as shown
// schema/resolvers/bot.resolver.js
module.exports={
// Mutation
Mutation:{
sendText: (parent,args,context) => {
const {df}=context;
const response = df.text(args);
return response;
},
sendEvent: (parent,args,context) => {
const {df}=context;
const response = df.event(args);
return response;
},
},
};
The corresponding graphQL types are defined in bot.type.js as shown:
const { gql } = require('apollo-server-express');
module.exports=gql`
type Course {
id:ID
header:String!
image:String!
description:String
price:String!
}
type Option {
value:String!
payload:String
link:String
}
type QuickReply {
text:String!
options:[Option!]!
}
type Bot {
query:String!,
response:String!
cards:[Course!]
quickReply:QuickReply
}
type Mutation {
sendText(text: String!, userId:String!, parameters:String): Bot!
sendEvent(name: String!, userId:String!, parameters:String): Bot!
}
`;
Please advise where I can write the code below that sets up dialog flow fulfillment
dialogflow-fulfillment setup code
😊Surprisingly it was as simple as writing it as a middleware on my graphQl api.
// import the required dependencies
const express = require('express');
const bodyParser = require('body-parser')
const cors = require('cors');
const { ApolloServer, } = require('apollo-server-express');
// do not forget your graphQl schema definition
const schema = require('./schema');
// we shall also need to import the data source.
// I will assume an array for our data source defined as below
const models ={
Course:[
{id:1, name:'Chatbots',}
{id:2, name:'React',}
{id:3, name:'Express',}
{id:4, name:'GraphQl',}
],
Book:[
{id:1, title:'Fundermentals in Chatbots',courseId:1},
{id:2, title:'Express for Newbies',courseId:3},
{id:3, title:'Advanced AI on Bots',courseId:1},
{id:4, title:'GraphQl Simplified',courseId:4},
]
}
// Import the WebhookClient class
const { WebhookClient } = require('dialogflow-fulfillment');
// Do the graphQl gymnastics ... I find Apollo server 2 just on point.
const server = new ApolloServer(schema);
const path='/'
const port = process.env.PORT || 4000
const app = express(); // we will merge express with the Apollo server 2
// do the express gymnastics ...
app.use(path,cors(),bodyParser.json(),)
// **IT'S HERE THAT WE DEFINE DIALOG FLOW'S WEB-HOOK AS A MIDDLEWARE**
app.use('/webhook', async (request,response,next)=>{
const agent = new WebhookClient({ request, response });
const {parameters}=request.body.queryResult;
const course =parameters['course'];
// ... do the database logic here ...
// eg get the title of available text books for the particular course
// I will assume
const {id} = await models.Course.find(({name})=>name ===course)
const books = await model.Book.filter(({courseId})=>courseId===id)
const booksTitleArray = books.map(({title})=>title)
let titleList = booksTitle.Array.toString();
titleList.replace(',', ' , ') // put space btn comas
titleList.replace(/\,(?=[^,]*$)/, "and")
let intentMap = new Map();
const recommendBooks courses=>{
agent.add(`For ${course}, We use the following books: ${titleList}`);
};
intentMap.set('course.recommended.books',recommendBooks);
agent.handleRequest(intentMap);
next();
})
server.applyMiddleware({ app, path });
app.listen(port,()=>{
console.log( `Apollo Server Running on http://localhost:${port}`)
})
I feel like writing an article on this because I tried looking for help almost everywhere in vain. Incase I get the time to do so, I will provide it in the comments.😏😉🤔🤭
Guys, we should not forget the ngrok magic if we are testing from localhost 😁

Apollo GraphQL server: filter (or sort) by a field that is resolved separately

I might be facing a design limitation of Apollo GraphQL server and I'd like to ask if there is a workaround.
My schema contains type Thing, that has field flag. I'd like to be able to filter things by the value of flag, but there is appears to be impossible if this field is resolved separately. The same problem would arise if I wanted to sort things. Here’s an example:
type Thing {
id: String!
flag Boolean!
}
type Query {
things(onlyWhereFlagIsTrue: Boolean): [Thing!]!
}
const resolvers = {
Thing: {
flag: async ({id}) => {
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// ↓ this does not work, because flag is still undefined
result = _.filter(result, ['flag', true]);
}
return result;
}
}
}
Is there any way of filtering things after all the async fields are resolved? I know I can call getFlagForThing(id) inside things resolver, but won't that be just repeating myself? The logic behind resolving flag can be a bit more complex than just calling one function.
UPD: This is the best solution I could find so far. Pretty ugly and hard to scale to other fields:
const resolvers = {
Thing: {
flag: async ({id, flag}) => {
// need to check if flag has already been resolved
// to avoid calling getThingsWithoutFlags() twice
if (!_.isUndefined(flag)) {
return flag;
}
const value = await getFlagForThing(id);
return value;
}
},
Query: {
async things(obj, {onlyWhereFlagIsTrue = false}) {
let result = await getThingsWithoutFlags();
if (onlyWhereFlagIsTrue) {
// asynchroniously resolving flags when needed
const promises = _.map(result, ({id}) =>
getFlagForThing(id)
);
const flags = await Promise.all(promises);
for (let i = 0; i < flags.length; i += 1) {
result[i].flag = flags[i];
}
// ↓ this line works now
result = _.filter(result, ['flag', true]);
}
return result;
}
},
};
I think that the issue here is not really a limitation of Apollo server, and more to do with the fact that you have a primitive field with a resolver. Generally, it's best to use resolvers for fields only when that field is going to return a separate type:
Thing {
id: ID!
flag: Boolean!
otherThings: OtherThing
}
Query {
things(onlyWhereFlag: Boolean): [Thing!]!
}
In this example, it would be fine to have a separate resolver for otherThings, but if a field is a primitive, then I would just resolve that field along with Thing.
Using your original schema:
const filterByKeyValuePair = ([key, value]) => obj => obj[key] === value;
const resolvers = {
Query: {
async things(parent, { onlyWhereFlag }) {
const things = await Promise.all(
(await getThings()).map(
thing =>
new Promise(async resolve =>
resolve({
...thing,
flag: await getFlagForThing(thing)
})
)
)
);
if (onlyWhereFlag) {
return things.filter(filterByKeyValuePair(['flag', true]));
} else {
return things;
}
}
}
};
What if flag wasn't a primitive? Well, if you want to filter by it, then you would have a couple of different options. These options really depend on how you are fetching the "flag" data. I'd be happy to elaborate if you can provide more details about your schema and data models.

Resources