Schema Stitching two remote Prisma/GraphQL schemas - node.js

I'm attempting to create a microservice based application that uses two remote Prisma/GraphQL schemas that run in Docker and a gateway that introspects them using schema stitching.
Prisma/GraphQL Schemas:
// Profile Schema service - http:localhost:3000/profile
type Profile {
id: ID!
user_id: ID!
firstName: String!
...
}
type Query {
findProfileById(id: ID!): Profile
findProfileByUserID(user_id: ID!): Profile
}
// User Schema service - http:localhost:5000/user
type User {
id: ID!
profileID: ID!
email: String!
...
}
type Query {
findUserById(id: ID!): User
findUserByProfileID(profileID: ID!): Profile
}
Now in the Gateway server I am able to introspect and mergeSchemas using graphql-tools successfully and I have added extended types to allow for relationships between the two types
// LinkTypeDefs
extend type Profile {
user: User
}
extend type User {
userProfile: Profile
}
I followed Apollo GraphQL documentation for schema stitching with remote schemas and this is the resolver I have for the now merged schemas
app.use('/gateway', bodyParser.json(), graphqlExpress({ schema: mergeSchemas({
schemas: [
profileSchema,
userSchema,
linkTypeDefs
],
resolvers: mergeInfo => ({
User: {
userProfile: {
fragment: `fragment UserFragment on User { id }`,
resolve(user, args, context, info) {
return delegateToSchema({
schema: profileSchema,
operation: 'query',
fieldName: 'findProfileByUserId',
args: {
user_id: user.id
},
context,
info
},
);
},
},
},
Profile: {
user: {
fragment: `fragment ProfileFragment on Profile { id }`,
resolve(profile, args, context, info) {
return delegateToSchema({
schema: authSchema,
operation: 'query',
fieldName: 'findUserByProfileId',
args: {
profileID: profile.id
},
context,
info
})
}
}
}
}),
})
}));
The issue I am having is everytime I query User or Profile for their new extended fields it always returns null. I have made sure that I have created User object with an existing profileId, likewise with Profile object with an existing userId. This is a sample of the query results
I have gone through the docs for about a week now and nothing seems to be working. From my undertstanding everything is plugged in properly. Hopefully someone can help. I have a feeling it has something to do with the fragments. I can provide a screenshot of the user and profile objects for more clarification if need be. Thanks.

I ended up resolving the issue by changing delegateToSchema() to info.mergeInfo.delegate(). That seemed to do the trick.
I have not tried the newly updated info.mergeInfo.delegateToSchema yet. Will update this post if the updated version works but for now this does the job for me.
Hopefully this can help someone in the future!

Related

How to make NextAuth update the User on a role change?

I'm using NextAuth with the Prisma adapter and AWS Cognito and it works perfectly, but my problem is that my User model doesn't get updated if I change the groups on Cognito. This is how I configured NextAuth:
// I copied the original and changed some of the fields
export type CognitoProfile = {
email: string;
sub: string;
preferred_username: string;
"cognito:groups": string[];
};
const CognitoProvider = (
options: OAuthUserConfig<CognitoProfile>
): OAuthConfig<CognitoProfile> => {
return {
id: "cognito",
name: "Cognito",
type: "oauth",
wellKnown: `${options.issuer}/.well-known/openid-configuration`,
idToken: true,
profile: (profile) => {
return {
id: profile.sub,
name: profile.preferred_username,
email: profile.email,
image: "",
roles: profile["cognito:groups"],
};
},
options,
};
};
export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session: ({ session, user }) => {
console.log(`User: ${JSON.stringify(user)}`);
if (session.user) {
session.user.id = user.id;
}
return session;
},
},
adapter: PrismaAdapter(prisma),
providers: [
CognitoProvider({
clientId: process.env.COGNITO_CLIENT_ID!,
clientSecret: process.env.COGNITO_CLIENT_SECRET!,
issuer: process.env.COGNITO_ISSUER,
}),
],
};
This works perfectly when a new user logs in (their groups are saved properly).
The problem is that the database is not updated when I log out and log back in after I add/remove group(s) to a Cognito user. This problem is not Cognito-specific it would be the same with things like Keycloak.
I checked the NextAuth docs, but I didn't find a solution for this. What's the recommended way of keeping the User model up to date? I don't want to reinvent the wheel 😅

Cannot query field signup on type Query

Started messing around with GraphQL, but I'm stuck with this error. Not sure if it's a problem in the schema definition or in the query.
const express_graphql = require('express-graphql')
const { buildSchema } = require('graphql')
const users = require('../users/translator')
const schema = buildSchema(`
type User {
id: ID
email: String
role: String
}
type Query {
user(id: ID!): User
users: [User]
token(email: String!, password: String!): String!
}
type Mutation {
signup(email: String!, password: String!, role: String!): ID
}`
)
const resolvers = {
users: users.getAll,
user: users.getById,
token: users.login,
signup: users.create,
}
module.exports = app => {
// GraphQL route
app.use('/graphql', express_graphql({
schema,
rootValue: resolvers,
graphiql: true,
}))
}
app is an express.js server while const users holds the logic. I'm able to fetch users and tokens, but when I try to POST a mutation
{
signup(email: "my#email.com", password: "321321", role: "admin")
}
I get the error Cannot query field "signup" on type "Query". By looking at the GraphiQL suggestions after reading the schema from the server, it looks like the signup mutation doesn't even get exported:
Some tutorials say I should export resolvers using
const resolvers = {
query: {
users: users.getAll,
user: users.getById,
token: users.login,
},
mutation: {
signup: users.create,
}
}
But it doesn't work either. Any hints?
You need to specify the operation type (query, mutation or subscription) like this:
mutation {
signup(email: "my#email.com", password: "321321", role: "admin")
}
If the operation type is omitted, the operation is assumed to be a query. This is called "query shorthand notation", but only works if your operation is unnamed and does not include any variable definitions.
It's good practice to always include the operation type regardless.

graphql-modules not calling __resolveType

I'm not entirely sure about the purpose / action of __resolveType function on an interface / union, but I suppose it's adding a __typename field with the resolved type. However, I cannot seem to get this working with graphql-modules. Here as subset, but sufficient subset of my schema.
const typeDefs = gql`
interface Node {
id: ID!
}
type User implements Node {
id: ID!
email: String!
password: String
}
type Group implements Node {
id: ID!
name: String!
}
type Query {
node(id: ID!): Node
}`;
And my resolver down below.
const resolvers = {
Query: {
node: () => {
return { id: 'abc', email: 'a#b.c', password: 'test' };
}
},
Node: {
__resolveType: () => {
console.log('__resolveType');
return 'User';
}
}
}
Which together combine to a module.
const Module = new GraphQLModule({
resolvers,
typeDefs,
});
Used with Apollo server like.
const server = new ApolloServer({
modules: [Module],
})
// Launch etc...
But when querying for node the __resolveType doesn't get logged and i get the following error.
Abstract type Node must resolve to an Object type at runtime for field Query.node with { id: "abc", email: "a#b.c" password: "test" }, received "undefined". Either the Node type should provide a "resolveType" function or each possible type should provide an "isTypeOf" function."
What am I doing wrong, and how would I solve this problem?
Quick note: adding __typename: 'User' in the returned object in Query.node seems to work, but doesn't seem like the ideal solution
I'm having the same issue, can confirm that it appears __resolveType is never called when using graphql-modules. I'm going to raise an issue in the repo and reference this SO. Will post back any answer I get.
It has already been reported in the repo issues and the fix was to pass the toplevel module schema to apollo-server, not as modules
({
schema: Appmodule.schema,
context: session => session
})
see issue here. https://github.com/Urigo/graphql-modules/issues/619
Can confirm this is working

GraphQL Expected Iterable, but did not find one for field while using find

Currently I am using Apollo/GraphQL/Node.js/Sequelize to build my backend server, and my server code looked like below, in there I can use req.user to get the current login user
app.use(
'/graphql',
bodyParser.json(),
graphqlExpress(req => ({
schema,
context: {
models,
user: req.user,
},
})),
);
Now I have two models User and Recipe, and the association rule is Recipe belongs to User, so in the Recipe schema I can use the UserId to know which user create this schema, the Recipe schema is
type Recipe {
id: Int!
authorName: String!
authorFbPage: String #virtual
perfumeName: String!
message: String
UserId: Int
}
type Query {
allRecipe: [Recipe]
meRecipe: [Recipe]
AvailableWatchRecipe: [Recipe]
}
My problem is in the meRecipe part, this Query supposed to be able to show the recipes created by login user, the resolver code is
meRecipe: async (parent, args, { models, user }) => {
if (user) {
console.log(user.id);
console.log(user.username);
return models.Recipe.find({ where: { UserId: user.id } })
.then((result) => { return result });
}
return null;
},
You can see I also use the console.log to check whether I can get the current user information, it actually can, so I am really confused why when I run this Query in the GraphQL server, it always shows "message": "Expected Iterable, but did not find one for field Query.meRecipe.
I have checked these resources:
https://github.com/brysgo/graphql-bookshelf/issues/10
and
GraphQL Expected Iterable, but did not find one for field xxx.yyy
but none of them fit my case, can anyone give me some advice, thanks!
Instead of using :
models.Recipe.find
Use
models.Recipe.findAll // this will return single result in array

GraphQL not loading children lists

I had started out writing verbose GraphQL and switched to graphql-tools and makeExecutableSchema and with the changes it will load queries for user(id: "N"), users, group(id: "N") and groups, however, the nested lists just return "id": null. I feel like I have to have a small mistake somewhere but am not seeing it:
const { makeExecutableSchema } = require('graphql-tools')
const db = require('./db')
const typeDefs = `
type User {
id: String
first_name: String!
last_name: String!
email: String!
friends: [User!]
groups: [Group!]
}
type Group {
id: String
name: String!
}
type Query {
users: [User!]!
user(id: String!): User
groups: [Group!]!
group(id: String!): Group
}
`
const resolvers = {
Query: {
users: db.readAllUsers,
user: (root, args, { loaders }) => loaders.user.load(args.id),
groups: db.readAllGroups,
group: (root, args, { loaders }) => loaders.group.load(args.id)
}
}
module.exports = makeExecutableSchema({ typeDefs, resolvers })
Any help would be greatly appreciated!
Edit: to clarify, here's what the data source looks like
You've defined resolvers for your queries, while are just fields on your Query type. However, you don't have any resolvers for the group field on the User type, so GraphQL falls back to using the default resolver for that field. Since the property field on the Object User resolves to is just an array of ids, GraphQL doesn't know how to make sense of it -- after all, you told it groups would be an array of objects.
You'll need to add a resolver for the groups field and transform that array into a Promise that will resolve to an array of Group objects:
const resolvers = {
Query: {
users: db.readAllUsers,
user: (root, args, { loaders }) => loaders.user.load(args.id),
groups: db.readAllGroups,
group: (root, args, { loaders }) => loaders.group.load(args.id)
},
User: {
groups: ({ groups }, args, { loaders }) => {
return Promise.all(groups.map(id => loaders.group.load(id)))
}
}
}

Resources