I am building a Graphql Schema and I was wandering what is the best practice of returning single vs collection items of a type. Let's say we want to retrieve users,
One option (if possible somehow) would be to have a query like this where the ID is optional, if ID is passed we return a single item, if not a collection of all users
query {
user (id: 1234) {
name
}
}
// return a single [User]
query {
user (id: null) {
name
}
}
// return a collection [User,User,User,...]
Another option would be to have user and users
query {
user (id: 1234) {
name
}
}
// return a single User
query {
users {
name
}
}
// return a collection [User,User,User,...]
I was wondering what is the best practice, or if you can pin-point me some resources related to that to read.
I am using the singular and plurals nouns to name the query field that return a single object and a list of object respectively. I think this naming style is very natural to most of the developers.
So to return a single user, it is :
type Query {
user(id:Int!) : User
}
It always return a single user. Just make the id input parameter as mandatory such that it cannot accept NULL.
And to return a list of user , normally it is:
type Query {
users : [User]
}
But in case it can have many users , most probably you need to consider something like pagination that allows developers to get the user page by page. For the offset -based pagination , I am doing something like below :
type Query {
users(offset:Int limit:Int) : UserPage
}
type UserPage {
data : [User]
pageInfo : PageInfo
}
type PageInfo {
# When paginating forwards, are there more items?
hasNextPage : Boolean!
# When paginating backwards, are there more items?
hasPreviousPage: Boolean!
# Total number of records in all page
total : Long
}
Depending on the requirements , you can consider to add an orderBy or a filter input parameter to the users query field to provide more options to the developers to get the result set that they are interested.
If you want to return the user list in the cursor-based pagination style, you can take a look on Relay Specification.
Related
For example, if there are two types User and Item
type User {
items: [Item!]!
}
type Item {
id: ID!
name: String!
price: Int!
}
If one user has PARTNER role.
I want to prevent it from being called only in the form of the query below.
query Query1 {
user {
items {
name
}
}
}
If user call another query, I want to indicate that user doesn't have permission.
query Query2 {
user {
items {
id
name
}
}
}
In short. if (Query1 != Query2) throw new Error;
Your question is a bit hard to follow but a couple things:
A GraphQL server is stateless - you cannot (and really should not) have a query behave differently based on a previous query. (If there's a mutation in between sure but not two queries back to back)
access management is normally implemented in your resolvers. You can have the resolver for the item id check to see if the user making the query has the right to see that or not and return an error if they don't have access.
Note that it can be bad practice to hide the id of objects from queries as these are used as keys for caching on the client.
I'm trying to run a GraphQL query in the AWS AppSync console:
query MyQuery {
getUserInfoById(id: "1234566789") {
account {
id // need this value for getAvailableCourses
}
}
getAvailableCourses(accountId: "", pageNumber: 0) {
data {
id
name
type
}
}
}
Basically I need the value account.id in getUserInfoById for getAvailableCourses. I'm obviously new to GraphQL. How would I go about this?
To the best of my knowledge, there can be two ways you can do this.
You can handle this in your frontend by getting user's id
from the session info and pass it to the other query.
You can also merge these two queries and make it one. You will also have to change the respective fields. Then attach a resolver with AvailableCourses and use $ctx.source.id in the resolver to get further details. Schema would look something like this
type Account {
id : ID!
availableCourses: AvailableCourses
..
}
type AvailableCourses {
name: String!
type: String!
..
}
type Query {
getUserInfoById(id: ID!): Account
}
Using the returned fields as inputs for a second query into your datasource is precisely what field resolvers are for. I can't say for sure since I don't know your schema or access patterns but it looks like you need to make available courses a sub field of the user.
Imagine the condition that I have a query called "users" that returns all the users and these users can be associated with one or more companies, so I have a type UserCompanies (I need it because it saves some more information beyond the relation). I'm using Prisma and I need to force a filter that returns only users that are of the same company as the requester.
I get the information of the company from JWT and need to inject this to the query before sending it to Prisma.
So, query should be like that:
query allUsers {
users {
name
id
status
email
userCompanies{
id
role
}
}
}
and on server side, I should transform it to: (user where is ok, just changing args)
query allUsers {
users(where: {
userCompanies_some: {
companyId: "companyId-from-jwt"
}
}) {
name
id
status
email
userCompanies(where: {
companyId: "companyId-from-jwt"
}){
id
role
}
}
}
I'm seeing a few resolutions to this, but I don't know if it is the best way:
1 - Using addFragmentToInfo, does the job to put conditions on the query, but if the query has a usercompanies already set, it gives me a conflict. Otherwise, it works fine.
2 - I can use an alias for the query, but after DB result I will need to edit all the results in array to overwrite the result.
3 - don't use info on Prisma and filter in js.
4 - Edit info(4th parameter) of type GraphqlResolveInfo
Imagine the following query:
query {
user {
id
}
SomeOtherStuff(id: <--- I want to pass the id obtained from user) {
id
}
}
How do you pass a parameter obtained from one query to another ?
In GraphQL, fields at each "level" of the request are executed and resolved in parallel. In your example, user and SomeOtherStuff are both fields of the same type (the root Query type) -- so they will be resolved at the same time. That means each query essentially is not aware of the other or what the other resolved to.
You would have to handle this kind of scenario client side. In other words, request the user first, parse the response for the id and then make the second request.
Edit: In Apollo, you would utilize compose for this purpose:
const userQuery = gql`query User { user { id } }`;
const stuffQuery = gql`query SomeOtherStuff($id: ID) { someOtherStuff(id: $id){ stuff } }`;
export default compose(
graphql(userQuery, { name: 'userData' })
graphql(stuffQuery, { name: 'stuffData', options: ({userData:{id}={}}) => ({variables: {id}}) }),
)(YourComponent)
I agree with #DanielRearden. You should make type-resolvers so you can go infinitely deep into the graph. I made a simple server example here that shows deep relationships. Because all the noun-fields are references, it goes infinitely deep for any query.
With that server, you can run a query like this, for example:
{
hero {
name
friends {
name
friends {
name
friends {
name
friends: {
name
}
}
}
}
}
}
So, in your example, structure it like this:
query {
user {
id
otherStuff {
id
}
}
}
I was looking for same scenario and landed on this question. You can get it work other way around. It all depends how you have written your graphql resolver and you need to make sure that your database relations are intact. I have got it working like this.
Imagine the following query:
query {
user {
id
}
SomeOtherStuff(id: <--- I want to pass the id obtained from user) {
id
}
}
How do you pass a parameter obtained from one query to another ?
In GraphQL, fields at each "level" of the request are executed and resolved in parallel. In your example, user and SomeOtherStuff are both fields of the same type (the root Query type) -- so they will be resolved at the same time. That means each query essentially is not aware of the other or what the other resolved to.
You would have to handle this kind of scenario client side. In other words, request the user first, parse the response for the id and then make the second request.
Edit: In Apollo, you would utilize compose for this purpose:
const userQuery = gql`query User { user { id } }`;
const stuffQuery = gql`query SomeOtherStuff($id: ID) { someOtherStuff(id: $id){ stuff } }`;
export default compose(
graphql(userQuery, { name: 'userData' })
graphql(stuffQuery, { name: 'stuffData', options: ({userData:{id}={}}) => ({variables: {id}}) }),
)(YourComponent)
I agree with #DanielRearden. You should make type-resolvers so you can go infinitely deep into the graph. I made a simple server example here that shows deep relationships. Because all the noun-fields are references, it goes infinitely deep for any query.
With that server, you can run a query like this, for example:
{
hero {
name
friends {
name
friends {
name
friends {
name
friends: {
name
}
}
}
}
}
}
So, in your example, structure it like this:
query {
user {
id
otherStuff {
id
}
}
}
I was looking for same scenario and landed on this question. You can get it work other way around. It all depends how you have written your graphql resolver and you need to make sure that your database relations are intact. I have got it working like this.