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
Related
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.
I'm writing a graphql project with express. I had defined User object like this:
const User = new GraphQLObjectType({
name: 'User',
fields: () => ({
name: {
type: new GraphQLNonNull(GraphQLString),
},
friends: {
type: HistoricalPerformanceEvaluation,
async resolve(user) {
return db.users.findFriendsFor(user.id);
},
},
}),
});
However, i'd like to use graphql syntax to create the schema and define it like this:
type User {
name: String
friends: [User]!
}
Where should i write the resolver now? I'm not using Apollo.
You can use addResolveFunctionsToSchema function from 'graphql-tools' to create schema with resolver functions.
import { addResolveFunctionsToSchema } from 'graphql-tools';
Using the reference graphql-js library, you can pass a root object to the GraphQL execution. Any top-level queries and mutations are looked up on this object, and further nested query fields are looked up on the objects returned from that, and so on.
The example on the graphql-js front page includes this object:
// The root provides a resolver function for each API endpoint
var root = {
hello: () => {
return 'Hello world!';
},
};
That object is then passed as a parameter to the graphql entry point.
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!
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
How could I check if user has permission to see or query something? I have no idea how to do this.
In args? How would that even work?
In resolve()? See if user has permission and somehow
eliminate/change some of the args?
Example:
If user is "visitor", he can only see public posts, "admin" can see everything.
const userRole = 'admin'; // Let's say this could be "admin" or "visitor"
const Query = new GraphQLObjectType({
name: 'Query',
fields: () => {
return {
posts: {
type: new GraphQLList(Post),
args: {
id: {
type: GraphQLString
},
title: {
type: GraphQLString
},
content: {
type: GraphQLString
},
status: {
type: GraphQLInt // 0 means "private", 1 means "public"
},
},
// MongoDB / Mongoose magic happens here
resolve(root, args) {
return PostModel.find(args).exec()
}
}
}
}
})
Update - Mongoose model looks something like this:
import mongoose from 'mongoose'
const postSchema = new mongoose.Schema({
title: {
type: String
},
content: {
type: String
},
author: {
type: mongoose.Schema.Types.ObjectId, // From user model/collection
ref: 'User'
},
date: {
type: Date,
default: Date.now
},
status: {
type: Number,
default: 0 // 0 -> "private", 1 -> "public"
},
})
export default mongoose.model('Post', postSchema)
You can check a user's permission in the resolve function or in the model layer. Here are the steps you have to take:
Authenticate the user before executing the query. This is up to your server and usually happens outside of graphql, for example by looking at the cookie that was sent along with the request. See this Medium post for more details on how to do this using Passport.js.
Add the authenticated user object or user id to the context. In express-graphql you can do it via the context argument:
app.use('/graphql', (req, res) => {
graphqlHTTP({ schema: Schema, context: { user: req.user } })(req, res);
}
Use the context inside the resolve function like this:
resolve(parent, args, context){
if(!context.user.isAdmin){
args.isPublic = true;
}
return PostModel.find(args).exec();
}
You can do authorization checks directly in resolve functions, but if you have a model layer, I strongly recommend implementing it there by passing the user object to the model layer. That way your code will be more modular, easier to reuse and you don't have to worry about forgetting some checks in a resolver somewhere.
For more background on authorization, check out this post (also written by myself):
Auth in GraphQL - part 2
One approach that has helped us solve authorization at our company is to think about resolvers as a composition of middleware. The above example is great but it will become unruly at scale especially as your authorization mechanisms get more advanced.
An example of a resolver as a composition of middleware might look something like this:
type ResolverMiddlewareFn =
(fn: GraphQLFieldResolver) => GraphQLFieldResolver;
A ResolverMiddlewareFn is a function that takes a GraphQLFieldResolver and and returns a GraphQLFieldResolver.
To compose our resolver middleware functions we will use (you guessed it) the compose function! Here is an example of compose implemented in javascript, but you can also find compose functions in ramda and other functional libraries. Compose lets us combine simple functions to make more complicated functions.
Going back to the GraphQL permissions problem lets look at a simple example.
Say that we want to log the resolver, authorize the user, and then run the meat and potatoes. Compose lets us combine these three pieces such that we can easily test and re-use them across our application.
const traceResolve =
(fn: GraphQLFieldResolver) =>
async (obj: any, args: any, context: any, info: any) => {
const start = new Date().getTime();
const result = await fn(obj, args, context, info);
const end = new Date().getTime();
console.log(`Resolver took ${end - start} ms`);
return result;
};
const isAdminAuthorized =
(fn: GraphQLFieldResolver) =>
async (obj: any, args: any, context: any, info: any) => {
if (!context.user.isAdmin) {
throw new Error('User lacks admin authorization.');
}
return await fn(obj, args, context, info);
}
const getPost = (obj: any, args: any, context: any, info: any) => {
return PostModel.find(args).exec();
}
const getUser = (obj: any, args: any, context: any, info: any) => {
return UserModel.find(args).exec();
}
// You can then define field resolve functions like this:
postResolver: compose(traceResolve, isAdminAuthorized)(getPost)
// And then others like this:
userResolver: compose(traceResolve, isAdminAuthorized)(getUser)