starting with graphql-problems with queries - graphql

Hi I have just started with graphql so bear with me and be very explicit.
I have a database in MySQL with a list of departments with the attributes of id, name and array of users. I have explicited that in the schema in this way
type Department {
id: ID!
name: String!
user: [User]
}
then I have created a couple of queries, ie. allDepartments and getDepartment in this way
type Query {
departments: [Department!]!
getDept(id: ID!): Department!
}
the resolver functions are those ones
Query: {
departments: async (parent, args, context) => {
return context.prisma.department.findMany();
},
getDept: async (parent, args, context) => {
return context.prisma.department.findUnique({
where: { id: args.id },
});
},
},
however
when I query the alldepartments, I get the list but the user array is empty (whereas there is at least one person each)
with getDept I manage to get the department only if I hardcode the id in the where obj. how can I implement a query where I can pass the id as argument and have the findunique to catch that particular one? can I use the name of the department instead of the id?

the args.id needs to be parsed as int
findUnique is not the right method since it has to be used with fields that are marked as "unique" in the schema

Related

GraphQL query with multiple nested resolvers and mapping fields to arguments

From GraphQL Client's perspective, how do I perform a query with multiple nested resolvers where the fields from the parent are passed as arguments to the child resolver?
Here is a minimal example:
GraphQL Schema:
type Author {
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
releaseDate: String!
}
type Query {
// Returns a list of Authors ordered by name, 'first' indicates how many entries to return
getAllAuthors(first: Int!): [Author]!
// Returns a list of Books ordered by releaseDate, 'first' indicates how many entries to return
getBooksByAuthorId(first: Int! authorId: ID!): [Book]!
}
Is it possible to write a query to get all authors and their last released book? Something around the lines:
query GetAuthorsWithLastBook($first: Int!) {
getAllAuthors(first: $first) {
authorId: id
name
lastBook: getBooksByAuthor(1, authorId) {
title
}
}
}
In the example above, I attempted to alias getAllAuthors.id as authorId and pass the alias down as argument to getBooksByAuthor(...) but that didn't work.
The key aspect of the problem is that I don't know the authorIds beforehand. I could fetch the authors first and build a query to fetch their last book but that will result in multiple queries and that is something I would like to avoid.
Update
A Java Kickstarter example is available here: https://www.graphql-java-kickstart.com/tools/schema-definition/
yes, on the graphql definition, you need to add lastBook in the Author
type Author {
id: ID!
name: String!
lastBook: [Book]
}
Next up u need to write the resolver for the lastBook
const resolvers = {
Query: {
Author {
lastBook: (parent, args) {
const userId = parent.id;
return getBooksByAuthor(userId, 1);
},
}
}
};

How to make self resolving array of object types with Prisma and GraphQL

Maybe the title is not accurate but I really don't know how to describe it anymore. I went through multiple documentations and descriptions but still couldn't figure it out.
I want to implement a basic social media like followers/following query on my type User. I am using MySQL and for that I made a separate table called Follow as it's a many-to-many connection.
Here is a pseudo-ish representation of my tables in the database without the unnecessary columns:
Table - User
user_id primary key Int
Table - Follow
follow_er foreign_key -> User(user_id) Int
follow_ed foreign_key -> User(user_id) Int
A user could "act" as a follow_er so I can get the followed people
And a user could be follow_ed, so I can get the followers.
My prisma schema look like this:
model User {
user_id Int #id #default(autoincrement())
following Follow[] #relation("follower")
followed Follow[] #relation("followed")
}
model Follow {
follow_er Int
follower User #relation("follower", fields: [follow_er], references: [user_id])
follow_ed Int
followed User #relation("followed", fields: [follow_ed], references: [user_id])
##id([follow_er, follow_ed])
##map("follow")
}
By implementing this I can get the followers and following object attached to the root query of the user:
const resolvers = {
Query: {
user: async (parent, arg, ctx) => {
const data = await ctx.user.findUnique({
where: {
user_id: arg.id
},
include: {
following: true,
followed:true
}
})
return data
}....
Here is my GraphQL schema I tried to make:
type Query{
user(id: Int!): User
}
type User{
id: ID
following: [User]
followed: [User]
}
So I can get something like:
query {
user(id: $id) {
id
following {
id
}
followed{
id
}
}
}
}
But I couldn't make it work as even if I get the the array of objects of {follow-connections}:
[
{
follow_er:1,
follow_ed:2
},
{
follow_er:1,
follow_ed:3
},
{
follow_er:3,
follow_ed:1
},
]
I can't iterate through the array. As far as I know, I have to pass either the follow_er or follow_ed, which is a user_id to get a User object.
What am I missing? Maybe I try to solve it from a wrong direction. If anybody could help me with this, or just tell me some keywords or concepts I have to look for it would be cool. Thanks!
I would suggest creating self-relations for this structure in the following format:
model User {
id Int #id #default(autoincrement())
name String?
followedBy User[] #relation("UserFollows", references: [id])
following User[] #relation("UserFollows", references: [id])
}
And then querying as follows:
await prisma.user.findUnique({
where: { id: 1 },
include: { followedBy: true, following: true },
})
So you will get a response like this:

GraphQL nested query returns null

I am trying to use a GraphQL nested query (I am 80% sure this is a nested query?) to get information on the listing and the chef (author) of the listing. I can get the listing info just fine, but I am unable to get the chef info.
I was under the impression that the default resolver (user) would fire when getListing(args) returned without a valid User object for the chef. But the default resolver does not appear to be firing.
How do I properly get the nested information?
For example, my query is:
query getListing($listingID: String!) {
getListing(listingID: $listingID) {
name
chef {
firstName
}
}
}
The query returns:
{
"data": {
"getListing": {
"name": "Test",
"chef": {
"firstName": null
}
}
}
}
The function getListing(args) queries the DB and returns:
{
name: 'Test',
chef: 'testUsername',
listingID: 'testListingID'
}
My Schema is:
type Listing {
uuid: String!
name: String!
chef: User!
}
type User {
username: String
firstName: String
}
type Query {
getUser(jwt: String!): User
getListing(listingID: String): Listing
}
And my resolvers are:
const resolvers = {
Query: {
getListing: async (parent, args, context, info) => {
console.log('GET_LISTING');
return getListing(args);
},
getUser: async (parent, args, context, info) => {
console.log('GET_USER');
return getUser(args);
},
},
User: async (parent, args) => {
console.log('USER RESOLVER');
return getUser(args);
},
};
Other Info:
I am using Apollo Server running on AWS Lambda integrating with DynamoDB on the backend.
Resolvers exist only at the field level. You can't resolve a type (i.e. User). You can only resolve a field that has that type (i.e. chef).
const resolvers = {
// ...
Listing: {
chef: (parent, args) => {
return getUser()
},
},
}
It's unclear what sort of parameters getUser accepts, so you'll need to modify the above example accordingly. You won't use args unless you actually specify arguments for the field being resolved in your schema. It looks like the returning listing has a chef property that's the name of the user, so you can access that value with parent.chef.

How to implement mutations with optional arguments in GraphQL?

I am learning about graphql, and went through the https://www.howtographql.com/graphql-js/3-a-simple-mutation/ tutorial, and was interested in what the implementation of the updateLink mutation as follows would look like.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLink(id: ID!, url: String, description: String): Link
}
The reason I am asking this is that every other mutation implementation I have seen uses only NON-optional parameters. I am curious if there is a community-agreed-upon pattern for extracting and applying only the provided non-null arguments(url, description) from the given context and applying them to relevant the database record.
I have considered checking if each variable is null as follows, but this approach looks way messier than I would expect compared to the rest of the 'magic' and simplicity that Graphql provides.
updateLink(root, args, context) {
if (args.url == null && args.description == null){
return null
} else if (args.url == null) {
return context.prisma.updateLink({
id: args.id,
description: args.description
})
} else {
return context.prisma.updateLink({
id: args.id,
url: args.url
})
}
}
Please let me know if you found a cleaner way to extract and apply the optional arguments(url, description).
Another consideration I had was to make two separate update mutations as follows.
type Query {
# Fetch a single link by its `id`
link(id: ID!): Link
}
type Mutation {
# Update a link
updateLinkURL(id: ID!, url: String!): Link
updateLinkDescription(id: ID!, description: String!): Link
}
The thinking here was with limited arguments and a declarative mutation name, one could force the arguments to be Non-Null. The main issue here is that one can have many update methods for tables with many columns, this would also start to look messy.
FYI I am using prisma as my ORM.
const resolvers = {
Query: {
info: () => `This is the API of a Hackernews Clone`,
feed: () => links,
link: (parent, args) => {
// console.log(args)
return links.find((link) => link.id === args.id)
}
},
Link: {
id: (parent) => parent.id,
description: (parent) => parent.description,
url: (parent) => parent.url,
},
}

Enumerating all fields from a GraphQL query

Given a GraphQL schema and resolvers for Apollo Server, and a GraphQL query, is there a way to create a collection of all requested fields (in an Object or a Map) in the resolver function?
For a simple query, it's easy to recreate this collection from the info argument of the resolver.
Given a schema:
type User {
id: Int!
username: String!
roles: [Role!]!
}
type Role {
id: Int!
name: String!
description: String
}
schema {
query: Query
}
type Query {
getUser(id: Int!): User!
}
and a resolver:
Query: {
getUser: (root, args, context, info) => {
console.log(infoParser(info))
return db.Users.findOne({ id: args.id })
}
}
with a simple recursive infoParser function like this:
function infoParser (info) {
const fields = {}
info.fieldNodes.forEach(node => {
parseSelectionSet(node.selectionSet.selections, fields)
})
return fields
}
function parseSelectionSet (selections, fields) {
selections.forEach(selection => {
const name = selection.name.value
fields[name] = selection.selectionSet
? parseSelectionSet(selection.selectionSet.selections, {})
: true
})
return fields
}
The following query results in this log:
{
getUser(id: 1) {
id
username
roles {
name
}
}
}
=> { id: true, username: true, roles: { name: true } }
Things get pretty ugly pretty soon, for example when you use fragments in the query:
fragment UserInfo on User {
id
username
roles {
name
}
}
{
getUser(id: 1) {
...UserInfo
username
roles {
description
}
}
}
GraphQL engine correctly ignores duplicates, (deeply) merges etc. queried fields on execution, but it is not reflected in the info argument. When you add unions and inline fragments it just gets hairier.
Is there a way to construct a collection of all fields requested in a query, taking in account advanced querying capabilities of GraphQL?
Info about the info argument can be found on the Apollo docs site and in the graphql-js Github repo.
I know it has been a while but in case anyone ends up here, there is an npm package called graphql-list-fields by Jake Pusareti that does this. It handles fragments and skip and include directives.
you can also check the code here.

Resources