Return custom field based on other not requested field? - graphql

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).

Related

How to query relational data in ascending order in strapi?

I have this query that works
async find(ctx) {
let { _start, _limit } = ctx.request.query;
console.log(ctx.request.query)
_limit ? 0 : (_limit = 10);
const entities = await strapi.services["course-series"].find({});
return entities.map((entity) => {
// Do I sort them here or in the url query (and how)
entity.courses = entity.courses.slice(_start, _limit);
return sanitizeEntity(entity, { model: strapi.models["course-series"] });
});
}
The idea is that I can load 10 courses from each series at first and then get the next 10...
I just realized that the first 10 I am getting are not the recent ones.
As I commented // Do I sort them here or in the url query (and how)
What version of Strapi do you use?
What does this line do strapi.services["course-series"].find({})? How did you build this find method in the service? What does it do? Does it accept params?
Personally I'd do something like that (assuming you're working with Strapi version > 4:
const entities = await strapi.entityService.findMany('api::course-series.course-series', {
fields: [/* list the course-series fields you want to populate */],
populate: {
courses: {
fields: [/* list the course fields you want to populate */],
sort: 'createdAt:desc', // You can use id, publishedAt or updatedAt here, depends on your sorting prefrences
offset: _start,
limit: _limit // I must admit I haven't tested `offset` and `limit` on the populated related field
}
}
})
// ...the rest of your code, if needed
Read more about Entity Service API here.
Doing it the way you did it, you will always first retrieve the full list of courses for each course-series, and then run costly operations like mapping (the lesser of 2 evils) and above all sorting.

How to find path to a field in a graphql query

I am very new to graphql. I have a following graphql query for an example:
query pets {
breed(some arguments)
{
name
items
{
owner(some arguments)
{
items
{
ID
ownerSnumber
country
address
school
nationality
gender
activity
}
}
name
phoneNumber
sin
}
}
}
Is it possible to parse a gql query and get the path of a field in the query?
For example I would like to get the path of 'ID'. For example from the above query, is it possible to get the path where the ID is: owner.items.ID
With https://graphql.org/graphql-js/ it exposes a fourth argument called resolve info. This field contains more information about the field.
Have a look at GraphQLObjectType config parameter type definition:
With a good start from the earlier answer, relying on the ResolveInfo you could do something like a recursive check going from child to parent:
export const getFieldPath = (path: Path): string => {
if (!path.prev) return `${path.key}`
return `${getFieldPath(path.prev)}.${path.key}`
}
And later in your resolver you could use it like:
const myFieldResolver = (parent, args, ctx, info) => {
const pathOfThisResolversField = getFieldPath(info.path)
// use your pathOfThisResolversField
return yourFieldResolvedData
};
Worth noting though, the solution above will include every node all the way to the query root, rather than just the ones you mentioned owner.items.ID

Dynamically pass required fields into GraphQL query

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
}
}
}
`

GraphQL-java add variables to query

I am new to GraphQL. I know this is a basic question but hope someone could help me to add variables to my query as I tried many times and failed :(
In my query, below schema is used:
type Query {
ContinentInfo(id: ID): Continent
}
type Continent {
id : ID
name: String
countries: [Country]
}
type Country {
id : ID
name : String
population: Float
}
Below query is executed successfully:
{
ContinentInfo(id: "continent01") {
name
countries {
name
population
}
}
}
Then I want to add more conditions in the query, for example add a variable "populationMoreThan" to filter the result. so the query may look like:
{
ContinentInfo(id: "continent01") {
name
countries(populationMoreThan: $populationMoreThan) {
name
population
}
}
}
but it always failed when I tried to add this variable in the schema and in the query.
Could anyone provide me an example of adding variable in my case?
Also, it looks I need to pass the parameter value into the query? Now I'm using graphql.GraphQL.execute(queryString) to pass the query string. How to pass the variable value here?
Finally found a way to filter the result.
Update the schema with:
type Continent {
id : ID
name: String
countries(populationMoreThan: Float = 0): [Country]
}
And query with:
{
ContinentInfo(id: "continent01") {
name
countries(populationMoreThan: 1.0) {
name
population
}
}
}

clean way to get same field by different key

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!
}

Resources