How do I avoid redundant data lookups when resolving multiple nested graphql queries? - rest

Is there a known solution or feature in GraphQL or Apollo for batching expensive, redundant, data lookups across multiple query and field resolvers?
Say we have Person & Pet types, and queries to fetch them:
type Query {
pets: [Pet]
people: [Person]
}
type Pet {
name: String
owner: Person
}
type Person {
name: String
}
And we have a web client that needs to get all of them to show on a page:
query PetsAndPeople {
pets {
name
owner {
name
}
}
people {
name
}
}
To resolve this query, our backend needs to make REST API calls to an external service, which happens to apply rate-limiting, or has a $ cost per query. The endpoints look like this:
GET /pets
[
{
"name": "Mittens",
"ownerId": 1
},
{
"name": "Rover",
"ownerId": 1
}
{
"name": "Spot",
"ownerId": 2
}
]
GET /person/1
{
"name": "John Doe"
}
GET /persons
[
{
"name": "John Doe"
},
{
"name": "Jane Smith"
}
]
The resolvers look like this:
{
Query: {
pets: () => fetchJSON(`${API}/pets`)
}
people: () => fetchJSON(`${API}/persons`)
},
Pet: {
name: (pet) => pet.name
person: (pet) => fetchJSON(`${API}/person/${pet.ownerId}`)
}
Person: {
name: (person) => person.name
}
}
So lets say the graphql response for the web client looks like this:
{
"pets": [
{
"name": "Mittens",
"owner": {
"name": "John Doe"
}
},
{
"name": "Rover",
"owner": {
"name": "John Doe"
}
},
{
"name": "Spot",
"owner": {
"name": "Jane Smith"
}
}
],
"people": [
{
"name": "John Doe"
},
{
"name": "Jane Smith"
}
]
}
To produce this, we had to make 4-5 REST calls on the graphql side to the external API:
GET /people to resolve all of the people's names
GET /pets to resolve all of the pets' names
GET /person/1 to resolve Mittens's owner's name
GET /person/1 to resolve Rover's owner's name (maybe apollo caches this, but I'm not sure)
GET /person/2 to resolve Spot's owner's name
Since there are restrictions on the external API (such as rate limits or $/query), I would ideally want only want to make 2 queries (GET /people and GET /pets), and then resolve the pet's owner's names based on the results of the GET /people instead of making the redundant GET /person/3 calls.
I'm wondering if there is a known solution to this problem (such as manually handling all the queries in a single top-level function somehow?)? Am I setting up my resolvers or schemas incorrectly? Is there some other Graphql or Apollo feature that I'm missing?
This is a bit of a contrived example but I have a real use case that is similar with even more nested resolvers (and so exponentially more queries being made redundantly).

Related

Github GrapQL API returns only the last StatusContext for each context

I'm cross-posting the question from here.
I’m interested in knowing whether it’s possible to fetch all the statuses for all the contexts for a given reference using the GQL API.
The query that I’m currently doing is the following:
{
repository(owner: "owner", name: "name") {
pullRequests(headRefName: "head-ref", last: 1) {
nodes {
id
commits(first: 10) {
nodes {
commit {
oid
status {
contexts {
context
createdAt
id
description
state
}
}
}
}
}
}
}
}
}
This query returns a single status for each status context, and those are the last ones for each:
{
"data": {
"repository": {
"pullRequests": {
"nodes": [
{
"id": "some-id",
"commits": {
"nodes": [
{
"commit": {
"oid": "some-oid",
"status": {
"contexts": [
{
"context": "context-1",
"createdAt": "2021-07-06T21:28:26Z",
"id": "***",
"description": "Your tests passed!",
"state": "SUCCESS"
},
{
"context": "context-2",
"createdAt": "2021-07-06T21:25:26Z",
"id": "***",
"description": "Your tests passed!",
"state": "SUCCESS"
},
]
}
}
}
]
}
}
]
}
}
}
}
On the other hand, if I use the REST API with this query:
curl -i -u se7entyse7en:$(cat token) https://api.github.com/repos/owner/name/commits/some-oid/statuses
where some-oid is the corresponding retrieved with the GQL API, the output contains ALL the statuses. In particular, I can see all the statuses of context-1 and context-2 that happened before those that are returned by the GQL API.
It seems a limitation of the GQL schema given that StatusContext is a node instead of being a list of nodes. Basically, I expect StatusContext to be of type [Status!]! where Status represents a single status for the given context.
Am I missing something? Is this something expected to be changed in the future? Is the REST API the only option?
Thanks.
I opened a support ticket and this is the expected behavior indeed, there are no plans for changing it. The only solution is to use the REST API.
The link to the community forum is this one.

GraphQL query result for object that does not exist

I have a GraphQL query that calls a REST service to get the return object. The query contains an Id parameter that is then passed to the service. However, the REST service can respond with http status 404 Not Found if an object with that Id does not exist. That seems like the right response.
How do you model a Not Found response in GraphQL?
Is there a way to inform the GQL caller that something does not exist?
Update
Some options I am considering:
Return null
Change the GrqlhQL Query to return a list of objects and return empty list of nothing is found
Return some kind of error object with an error code
but it is unclear if there is a recommended practice in GQL API design.
You might treat it as an error and handle it accordingly.
I recommend you to check the GraphQL spec, the paragraph about error handling.
I hope it contains exactly what you are looking for.
Basically, you should return whatever you could, and inform a client about potential problems in the "errors" field.
The example from the documentation:
Request:
{
hero(episode: $episode) {
name
heroFriends: friends {
id
name
}
}
}
Response:
{
"errors": [
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [ { "line": 6, "column": 7 } ],
"path": [ "hero", "heroFriends", 1, "name" ]
}
],
"data": {
"hero": {
"name": "R2-D2",
"heroFriends": [
{
"id": "1000",
"name": "Luke Skywalker"
},
{
"id": "1002",
"name": null
},
{
"id": "1003",
"name": "Leia Organa"
}
]
}
}
}

How do I query subcollections firestore ionic 4

Hello I want to make a query to a sub-collection in firestore I have the following structure
"groups": {
"g1":
{
"name": "Group 1",
"users": {
"u1": {
"id": "user1"
},
"u2": {
"id": "user2"
}
}
},
"g2":
{
"name": "Group 2",
"users": {
"u1": {
"id": "user1"
}
}
}
}
"users": {
"user1": {
"firstName": "Lorem",
"lastName": "Lorem"
},
"user2": {
"firstName": "Lorem2",
"lastName": "Lorem2"
}
}
and I want to make a query that looking for user1 brings me the groups that belong that user in the example would bring me g1 and g2 but if I look for user2 should I only bring g1 can you create a composite index between the group and the user? I am developing it in ionic 4 I don't know if the data is well structured
Thank you very much in what you can help me
You can use an array_contains operation on field users. All that requires is that you know the complete, exact element that the array should contains. If you know all of that, you can check with::
groupsRef.where("users", "array-contains", {
u1": {
"id": "user1"
}
})
If you only know "user1", you will need to have an array that contains only "user1". So for example:
"userids": ["user1", "user2"]
Then you can query with:
itiesRef.where("regions", "array-contains", "user1")

How to create a jsonpath to instagram business account in a batch request with the Graph API?

My goal is to create a batch request with dependent calls as documented here:
https://developers.facebook.com/docs/graph-api/making-multiple-requests#operations
You can reference the results of a previous operation using JSONPath in form post parameters in addition to query string parameters.
I can't get the right JSONPath to make it work when there are multiple elements in the data array that have an instagram_business_account.id (iba_id)
The two calls that I want to make are
/me/accounts?fields=instagram_business_account
/17841400714813297?fields=business_discovery.username(thomasguntenaar){media_count}
my batch looks like
[
{"method":"GET","name":"get-ig", "relative_url":"me/accounts?fields=instagram_business_account"},
{"method":"GET", "relative_url":"{result=get-ig:$.data..instagram_business_account.id}?fields=business_discovery.username(thomasguntenaar){media_count}}"}
]
in the second query you are supposed to put the JSONPath to the instagram business account id
after result=
I get this error back
{
"code": 404,
"body": "{
\"error\": {
\"message\": \"(#803) Some of the aliases you requested do not exist: 17841400714813297,17841403388404550,17841401383243593\",
\"type\": \"OAuthException\",
\"code\": 803,
\"fbtrace_id\": \"FV8qA+oA7fp\"
}
}"
}
Facebooks json response after the first call is
{
"data": [
{
"id": "466912700123917"
},
{
"id": "502655553273897"
},
{
"instagram_business_account": {
"id": "17841400714813297"
},
"id": "503124266815195"
},
{
"instagram_business_account": {
"id": "17841403388404550"
},
"id": "510613645695833"
},
{
"instagram_business_account": {
"id": "17841401383243593"
},
"id": "2061834074114937"
}
],
"paging": {
"cursors": {
"before": "NDY2OTEyNzAwMTIzOTE3",
"after": "MjA2MTgzNDA3NDExNDkzNwZDZD"
}
}
}
When you query the second request like this
?ids=17841400714813297,17841403388404550,17841401383243593&fields=business_discovery.username(thomasguntenaar){username,media_count}
the response looks like this
{
"17841400714813297": {
"business_discovery": {
"username": "thomasguntenaar",
"media_count": 76,
"id": "17841400714813297"
},
"id": "17841400714813297"
},
"17841403388404550": {
"business_discovery": {
"username": "thomasguntenaar",
"media_count": 76,
"id": "17841400714813297"
},
"id": "17841403388404550"
},
"17841401383243593": {
"business_discovery": {
"username": "thomasguntenaar",
"media_count": 76,
"id": "17841400714813297"
},
"id": "17841401383243593"
}
}
(#803) Some of the aliases you requested do not exist: 17841400714813297,17841403388404550,17841401383243593
Apparently the API thinks this was supposed to be one id, and doesn’t realize it is supposed to be three separate ones.
The API has a syntax to request data for more than one object in one request - instead of /{id}?fields=foo, you can make a request of the form ?ids={1,2,3}&fields=foo, to request this data for the objects with ids 1, 2 and 3 in one go. The resulting data structure will contain a sub-structure for each of those ids.
The same structure should work in batch requests as well, when parts (here, the IG account ids returned by the previous query) are dynamically inserted.

REST API - non-static (changing) resource for single url - how to design?

Imagine I have an API for a school. One of the resources is Department, which has various resources on it, such as a collection of Professors, and a "Head Professor".
Department looks like this:
{
"_links": {
"self": "http://myapi.com/department/math"
}
"name": "Math Department",
"headProfessor": {
"_links": {
"self": "http://myapi.com/professor/id/2",
"headProfessor": "http://myapi.com/headprofessor/department/math"
},
"name": "George Patton",
"id": "2"
}
"professors": {
"_links": {
"self": "http://myapi.com/professors/department/math"
},
"_collectionData": [
{
"_links": {
"self": "http://myapi.com/professor/id/1"
},
"name": "John Doe",
"id": "1"
},
{
"_links": {
"self": "http://myapi.com/professor/id/2"
},
"name": "George Patton",
"id": "2"
},
{
"_links": {
"self": "http://myapi.com/professor/id/3"
},
"name": "Paul Simon",
"id": "3"
}
]
}
}
My question is regarding "headProfessor" and the links. What is the canonical link for the "head professor"? Is it http://myapi.com/professor/id/1 or is it http://myapi.com/headprofessor/department/math? Should I have both in there? Or is only one necessary? Is there a better way to represent the "head" or the "top" of something, basically a url whose resource could change because it represents a relationship and not a static resource?
NOTE
Yes, I do prefer the resource designator first in the url as it gives the resource designator the same location in every url. But my question is not about that. That's just a matter of taste and style.
First of all Department has professors and not vice-versa so your APIs should be designed like this
http://myapi.com/departments -> GET all departments
http://myapi.com/departments/{departmentId}/professors ---> POST to add a professor to a department , body of POST has the rank of professor has HOD, or staff
http://myapi.com/departments/{departmentId}/professors ---> GET should get all professors of that department
http://myapi.com/departments/{departmentId}/professors?rank=hod ---> Should give you the HOD
http://myapi.com/departments/{departmentId}/professors/{professorId} ---> PUT to change rank of Professor
http://myapi.com/departments/{departmentId}/professors/{professorId} ---> DELETE to remove professor from Department if he retires or moves to another college,etc.