How a GraphQL request is resolved - serlo/documentation GitHub Wiki
After you have read the introduction to api.serlo.org and an overview to it's architecture we have everything we need to understand how a GraphQL request is resolved. Let's take the following request:
query {
uuid(
alias: {
instance: en
path: "/community/41297/multiplying-decimal-fractions"
}
) {
... on Article {
currentRevision {
title
content
}
}
}
}
Resolving the request step by step
1. Resolve uuid
Since we have a query request we start at the resolver function uuid()
of Query
. So the following function is called:
const resolvers = {
Query: {
async uuid(_parent, args, { dataSources }) {
}
}
}
Since we have just started to resolve the GraphQL request the passed parent object for _parent
is {}
(the underscore in the variable name says that we do not want to use this variable). args
hold the arguments given to uuid
in the query. In our example args
will be { alias: { instance: "en", path: "/community/41297/multiplying-decimal-fractions" } }
. dataSources
gives access to our data sources.
You can find the actual code for uuid()
at https://github.com/serlo/api.serlo.org/blob/main/packages/server/src/schema/uuid/abstract-uuid/resolvers.ts In our case it will look into the path /community/41297/multiplying-decimal-fractions
and sees that it already contains the uuid 41297
of the requested article. This id is used to request the article model object with dataSources.serlo.getUuid({ id: 41297 })
.
Now the serlo
data source will make the following request to the database layer:
curl -X POST \
-H "Content-Type: application/json" \
--data '{ \
"type": "UuidQuery", \
"payload": { "id": 41297 } \
}' https://<IP of database layer>/
The database layer will respond with the model object of the article which is returned as a result of uuid()
:
{
"__typename": "Article",
"id": 41297,
"currentRevisionId": 44855,
...
}
__resolveType()
2. Resolve type via In the GraphQL schema we have defined that the uuid
endpoint has the type AbstractUuid
:
type Query {
uuid(alias: ..., id: ...): AbstractUuid
}
AbstractUuid
is an interface and thus Apollo Server does not know the concrete GraphQL type of the returned object. To determine the concrete type Apollo Server passes the returned object { __typename: "Article", id: 41297, ... }
of uuid()
to the function AbstractUuid.__resolveType()
:
export const resolvers = {
AbstractUuid: {
__resolveType(uuid) {
return uuid.__typename
},
}
}
As you can see __resolveType()
returns the property __typename
of the passed object. In this case we have the object { __typename: "Article", id: 41297, ... }
from the last step as the argument uuid
and thus the string "Article"
is returned. Now Apollo Server knows that { __typename: "Article", id: 41297, ... }
shall represent an Article
an can proceed with the GraphQL request.
currentRevision
3. Resolve Apollo Server now knows that { __typename: "Article", id: 41297, currentRevisionId: 44855, ... }
represent an article. Since we have defined a resolver function Article.currentRevision()
Apollo Server knows that this property shall be calculated dynamically. Thus it passes the object to this resolver function as parent
:
const resolvers = {
Article: {
async currentRevision(parent, _args, { dataSources }) {
if (parent.currentRevisionId === null) return null
return await dataSources.serlo.getUuid({ id: parent.currentRevisionId })
}
}
}
Since parent.currentRevisionId === 44855
another request is made to the database layer to resolve the article revision 44855
:
curl -X POST \
-H "Content-Type: application/json" \
--data '{ \
"type": "UuidQuery", \
"payload": { "id": 44855 } \
}' https://<IP of database layer>/
The response will be something like:
{
"__typename": "ArticleRevision",
"id": 44855,
"title": "Multiplying decimal fractions",
"content": ""[[{\"col\":24,\"content\":\"## Procedure\"}],[{\"col\":16,\"content\":\"1. Ignore ..."
...
}
This is returned as the result of currentRevision()
.
title
and content
4. Resolve Since we have already stated in GraphQL that the property currentRevision
of Article
is of type ArticleRevision
Apollo server already knows the returned object has the concrete type ArticleRevision
. Thus the properties title
and content
can be resolved.
Since there are no resolver functions ArticleRevision.title()
nor ArticleRevision.content()
and the model object { __typename: "ArticleRevision", id: 44855, title: "Multiplying decimal fractions", ... }
already have the properties title
and content
these values are returned.
Now Apollo Server has collected all necessary data and can return as a result the JSON data:
{
"data": {
"uuid": {
"currentRevision": {
"title": "Multiplying decimal fractions",
"content": "[[{\"col\":24,\"content\":\"## Procedure\"}],[{\"col\":16,\"content\":\"1. Ignore ..."
}
}
}
}
Summary
While resolving the query the following happens:
position in query | state of Apollo Server | resolver function call | result of resolver function
------------------------------------------------------------------------------------------------------------------
query { | model object: {} | Query.uuid( | {
→ uuid(alias: ...) { | type: "Query" | parent = {}, | __typename: "Article",
... on Article { | | args = { | id: 41297,
currentRevision { | | alias: { | currentRevisionId: 44855,
title | | instance: "en", | ...
content | | path: "/communit...", | }
} | | } |
} | | }, |
} | | context |
} | | ) |
------------------------------------------------------------------------------------------------------------------
query { | model object: { | AbstractUuid.__resolveType( | "Article"
uuid(alias: ...) { | __typename: "Article", | parent = { |
→ ... on Article { | id: 41297, | __typename: "Article", |
currentRevision { | currentRevisionId: 44855, | id: 41297, |
title | } | currentRevisionId: 44855, |
content | type: "AbstractUuid" | } |
} | | ) |
} | | |
} | | |
} | | |
------------------------------------------------------------------------------------------------------------------
query { | model object: { | Article.currentRevision( | {
uuid(alias: ...) { | __typename: "Article", | parent = { | __typename: "ArticleRevision",
... on Article { | id: 41297, | __typename: "Article", | id: 44855,
→ currentRevision { | currentRevisionId: 44855, | id: 41297, | title: "Multipl...",
title | } | currentRevisionId: 44855, | content: "..."
content | type: "Article" | } | ...
} | | args = {}, | }
} | | context |
} | | ) |
} | | |
------------------------------------------------------------------------------------------------------------------
query { | model object: { | no resolver function call | "...",
uuid(alias: ...) { | __typename: | necessary since property |
... on Article { | "ArticleRevision", | be retrieved from model |
currentRevision { | title: "Multipl...", | object |
title | content: "..." | |
→ content | ... | |
} | } | |
} | | |
} | | |
} | | |
------------------------------------------------------------------------------------------------------------------