Composing and running GraphQL queries
As we have elaborated in other articles about GraphQL , the specification describes the query language, the schema definition language, and the runtime to resolve GraphQL operations which can be of type query, mutation, or subscription. This article focuses on GraphQL queries and describes variations in composing them so that GraphQL servers can fulfill them. Let's get started!
The GraphQL query is one of the core operations of GraphQL. With a single endpoint, App developers can retrieve data from a GraphQL server using queries. Hence, a query is the equivalent of an HTTP GET request in a REST API. By convention, GraphQL queries are operations to fetch data from a server without any side effects.
The GraphQL schema defines the query's structure, such as its arguments, fields, and return value. When defining a schema, the API developer declares queries as fields of the root type Query. A GraphQL server validates the query against the GraphQL schema before executing the attached query resolvers and projects the retrieved data onto the defined query fields. Query resolvers will run in parallel to ensure the best possible performance.
Let's start with a simple query for retrieving a welcome message from the server. The corresponding GraphQL schema with a Welcome object type and its query field welcome could look as follows:
type Welcome {
id: ID
message: String
}
type Query {
welcome(id: ID!): Welcome
}
schema {
query: Query
}
To execute the welcome query, a developer can enter the following syntax into, e.g., the GraphiQL explorer:
{
welcome(id: "1") {
id
message
}
}
In the above example, we used the shorthand syntax without the keyword query and without defining an operation name. If no operation type is specified, it's assumed to be a query by default. To make queries less ambiguous, however, the client should set the operation name when running in production, e.g.:
query welcome_message {
welcome(id: "1") {
id
message
}
}
The de facto standard for serving GraphQL over HTTP looks as follows.
{
"query": "...",
"operationName": "...",
"variables": {
"key": "value"
}
}
The operationName property is required if multiple query operations are present in the query. Batching of query operations is one of many advantages that GraphQL has over classical REST APIs. Important to note is that each query operation must have a unique name for the GraphQL server to execute them successfully. To achieve that, GraphQL provides a concept called aliasing.
Aliases are a great concept allowing clients to organize query results. Let's have a look at an example:
query welcome_with_aliases {
john: welcome(id: "1") {
...welcomeFields
}
paul: welcome(id: "2") {
...welcomeFields
}
}
fragment welcomeFields on Welcome {
id
message
}
In the above example, we used aliases and fragments, which allow us to define once which fields the server should respond with. The operation can then reference the fragment from all queries that return objects of type Welcome. The result of the above query will look something like the following:
{
"data": {
"john": {
"id": "1",
"message": "Welcome John"
},
"paul": {
"id": "2",
"message": "Welcome Paul"
}
}
}
Concepts like aliases and fragments are a great way to reduce duplication and structure query responses. However, as applications usually do more than just welcome users, we will look at object relationships and nested queries in the next section.
In real-world applications, different object types often are related to each other. In that case, when designing a data model, it helps to define the relationships between objects using simple terms, such as has-one or has-many. For example, a user of a Social Network has a profile, has many friends, has created many posts, and might have written many comments.
Now, these relationships can be defined as follows:
In a GraphQL schema, the API developer defines the relationships between objects with a nested connection type that allows embedding the fields of the nested type to the query of a parent type. For example, let's assume we want to welcome many musicians instead of just one. In that case, the Welcome object has-many Musicians, and the schema would look as follows:
type Welcome {
id: ID
message: String
musicianConnection: WelcomeMusiciansConnection
}
type WelcomeMusiciansConnection {
items: [Musician]
}
type Musician {
firstName: String
band: String
}
Please note that the exact syntax of how the schema defines a connection is up to the GraphQL server platform or if you're building a GraphQL API from scratch up to personal preference. With graphapi® we decided to append the word Connection to make it very explicit.
A welcome query with the user connection would look as follows:
query welcome_message {
welcome(id: "3") {
message
band: musicianConnection(filter: { band: { eq: BEATLES }}) {
members: items {
firstName
}
}
}
}
The result would look as follows:
{
"data": {
"welcome": {
"message": "Please welcome The Beatles!",
"band": {
"members": [
{ John }, { Paul }, { George }, { Ringo }
],
},
}
}
}
If list queries are available, they allow querying all items of a given type in a similar fashion.
query the_beatles($band: String = "The Beatles") {
beatles:listMusicians(filter: { band: { eq: $band }}, limit: 4) {
members:items {
firstName
}
}
}
Running the query leads to the following result:
{
"data": {
"beatles": {
"members": [{ John }, { Paul }, { George }, { Ringo }]
}
}
}
You might have already figured out that the client defines the response structure of a query by specifying aliases, arguments, and fields. This is another fundamental difference to classical REST APIs, where the server hardwires the response structure of a given request.
In the_beatles query above, we also used variables and query arguments. In the next section, we look deeper into these two concepts.
We added arguments and values directly to the query string in previous examples. While this makes sense for demonstration purposes, GraphQL variables are better for production-ready apps, which change context often and need to react to dynamic user input.
To use variables, you need to do four things:
Using variables eliminates the need to manipulate the query string at runtime in app clients. All declared variables must be either scalars, enums, or input object types.
In REST-based APIs, clients pass arguments using query parameters or URL segments. In GraphQL, however, every field and nested object can have typed arguments. In addition to the default set of scalar types, e.g., String, Int, or Float, a GraphQL server also can declare custom types. What type of arguments are available depends on the particular GraphQL Server implementation.
With graphapi we are providing the following arguments for all list- and connection-queries:
The GraphQL query is the core operation of GraphQL-based APIs. It offers a wide variety of options that influence the query response from a GraphQL server. Although the power and flexibility of GraphQL queries make GraphQL APIs easy to integrate for application developers, it can be challenging for teams tasked with implementing all the logic on the server side. With graphapi® we entirely abstract that complexity away, so teams can launch faster and focus on providing value for their customers.
Transform your ideas into reality quickly with our intuitive, AI-powered platform—no coding or design skills required.