I have a query where my required fields inside the query may change. User is allowed to generate the query on fly and user can select the fields in the query. Is there a way to pass the required fields into query based on user's selection from a dropdown. Eg: in below query id, traveller, visaApplication can be replaced by anyother fields. So my queries has to dynamic.
{
Travels{
id
traveller {
nationality
firstName
}
visaApplication {
nationality
city
}
}
}
Fields can be added dynamically using string interpolation:
const otherFields = `
traveller {
nationality
firstName
}
`
const query = gql`
{
Travels {
id
${otherFields}
}
}
`
You can also utilize the #skip or #include directives, combined with variables, to control whether to skip/include particular fields for a specific request while utilizing a single query:
const query = gql`
query (
$includeTraveller: Boolean!
$includeVisa: Boolean!
) {
Travels {
id
traveller #include(if: $includeTraveller) {
nationality
firstName
}
visaApplication #include(if: $includeVisa) {
nationality
city
}
}
}
`
Related
Say I want to query an object tree like this:
query UsersWithOrgs {
users {
nodes {
firstName
lastName
orgMemberships {
nodes {
joinedAt
organization {
name
foundedAt
location {
city
country
}
}
}
}
}
}
}
Now say I want to filter for only the users where the org they joinedAt <= 6 months ago, or only the users where the org they joined is in England.
query UsersWhereJoinedRecently {
users {
nodes {
firstName
lastName
orgMemberships(joinedAt: { lte: "2022/03/12" }) {
nodes {
joinedAt
organization {
name
foundedAt
location {
city
country
}
}
}
}
}
}
}
query UsersWhereOrgInEngland {
users {
nodes {
firstName
lastName
orgMemberships {
nodes {
joinedAt
organization {
name
foundedAt
location(country: "England") {
city
country
}
}
}
}
}
}
}
Is this the "GraphQL way" of defining filters on nested objects? Or should the filters be hoisted up somehow?
Based on the definitions above, I would instead expect UsersWhereJoinedRecently to return all users in the app, but orgMemberships would be filtered to only include those after the joinedAt date. Likewise, I would expect UsersWhereOrgInEngland to return all users with all their orgs, but only return the location for the org if the country is "England". That is not at all what I want, I want only the users where if that deeply nested condition is false, not to return the user. What is the appropriate way people handle this situation?
To me there are two passes in the overall query:
Joins and Wheres.
Projection.
First, we must fetch all users where the deeply nested condition is true. Then given those top-level IDs for the user, we fetch the projected schema.
So first:
// userIds =
select id from users
inner join memberships on memberships.userId = users.id
where memberships.joinedAt <= "2022/03/12"
Then:
select firstName, lastName from users where id in userIds
select joinedAt, orgId from orgMemberships where userId in userIds and joinedAt <= "2022/03/12"
... select from each of the other tables.
... then build into tree and return to client.
Same with the other query.
So because of these two seemingly completely separate phases, it makes me think that all filtering should be passed in at the top level in some fashion in GraphQL. Like:
query UsersWhereJoinedRecently {
users(membershipJoinedAt: { lte: "2022/03/12" }) {
nodes {
firstName
lastName
orgMemberships {
nodes {
joinedAt
organization {
name
foundedAt
location {
city
country
}
}
}
}
}
}
}
Is that correct, is that the recommended practice? Or what is recommended practice in these sorts of "filter by nested objects" scenarios?
For example, I have two queries:
query FirstOne {
city {
id
name
}
}
and
query SecondOne {
person {
id
name
}
}
Can I do something like this?
query CombinedQuery {
FirstOne
SecondOne
}
I want to reduce query duplications because if I can't create a query like CombinedQuery I will need to create it in this way:
query CombinedQuery {
city {
id
name
}
person {
id
name
}
}
From my perspective - this query is bad.
Let's say that I want to get a person's age using this query:
{
getUser(id: "09d14db4-be1a-49d4-a0bd-6b46cc1ceabb") {
full_name
age
}
}
I resolve my getUser query like this (I use node.js, type-graphql and knex):
async getUser(getUserArgs: GetUserArgs, fields: UserFields[]): Promise<User> {
// Return ONLY ASKED FIELDS
const response = await knex.select(this.getKnexFields(fields)).from(USER).whereRaw('id = ?', [getUserArgs.id]);
// returns { full_name: 'John Smith' }
return response[0];
}
The problem is that then I can't calculate age field, because I did not get born_at (datetime field stored in a db) in the first place:
#FieldResolver()
age(#Root() user: User, #Info() info: GraphQLResolveInfo): number {
console.log(user); // { full_name: 'John Smith' } no born_at field - no age, so error
// calculate age from born_at
return DateTime.fromJSDate(user.born_at).diff(DateTime.fromJSDate(new Date()), ['years']).years;
}
Is there some fancy graphql-build-in way / convention to predict that born_at will be needed instead of doing it manually through info / context?
You should always return full entity data from the query-level resolvers, so they are available for field resolvers.
The other solution is to manually maintain a list of required fields for field resolvers, so your "fields to knex" layer can always include them additionally".
Further improvements might be to can a list of additional columns based on the requested fields (thus the field resolvers that will be triggered).
Here is the problem. I can get member by ID and my query looks like below:
{
member(memberId:[1,2]) {
firstName
lastName
contacts {
}
}
}
Now I need to add few more query to get member by name and email like below
{
member(email:["abc#xy.com","adc#xy.com"]) {
firstName
lastName
contacts {
}
}
}
{
member(name:["abc","adc"]) {
firstName
lastName
contacts {
}
}
}
How do I design my graphQL query and schema? Should my query have just 1 field with multiple optional arguments? like below
Field("member", ListType(Member),
arguments = ids :: email :: name,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Or should I have multiple query with different field under a schema. like below
Field("memberById", ListType(Member),
arguments = Id :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Field("memberByEmail", ListType(Member),
arguments = email :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Field("memberByName", ListType(Member),
arguments = name :: Nil,
resolve = (ctx) => {
val id : Seq[Int] = ctx.arg("memberId")
ctx.ctx.getMemberDetails(id)
})
Thank you in advance. let me know in case you need more details.
You should think about advantanges and disadvantages of both solutions.
If you will prepare separate fields, you will get a lot of boilerplate.
On the other hand you can set all possible inputs as OptionalInputType, it makes schema field only. Disadvantage of this solutions is that Sangria cannot validate a field that at least one argument should be required, so you have to cover this case with proper response or whatever.
The third option is to make generic solution at Schema level. You can create a query with two arguments filterName and filterValues, first would be EnumType for Id, Email, Name, the second would be a list of strings.
Such solution avoid disadvantages of both previous solutions, it has required fields and it doesn't need spreading fields in schema for every filter. Additionally if you want to add any additional function you have only edit FilterName enum and a resolver function to cover this.
Finally you schema will looks like this:
enum FilterName {
ID
EMAIL
NAME
}
type Query {
member(filterName: FilterName!, filterValues: [String]!): Member!
}
I've got a working query that looks like this:
const GETONEASSOCIATE_QUERY = gql`
query getOneAssociate($_id: String!) {
getOneAssociate(_id: $_id) {
_id
first_name
last_name
city
state
userIDinRelatedTable
}
} `;
Now I'd like to be able to look up an associate by userIDinRelatedTable. Do I have to write a whole new graphQL query, or, is there a way to set up a query so that I can specify what fields to be used for the lookup -- something like:
enter code herequery getOneAssociate($_args: args) {
Thanks in advance to all for any thoughts/advice/info!
I'm guessing in your scheme you have a userIDinRelatedTable field on Associate which resolves to RelatedTable.
So to get the fields returned in your query you can
const GETONEASSOCIATE_QUERY = gql`
query getOneAssociate($_id: String!) {
getOneAssociate(_id: $_id) {
_id
first_name
last_name
city
state
userIDinRelatedTable {
id
field1
field2
}
}
} `;