Skip to content

GraphQL - Query

In GraphQL, a Query is an operation used to read or fetch data from the server. It’s analogous to a GET request in REST. Clients construct queries specifying exactly which fields they need, potentially including nested related data and arguments for filtering or identifying specific objects.

The server processes the query, executes the necessary resolvers, and returns a JSON response that precisely matches the structure of the requested fields.

The simplest query involves requesting a field defined in the Query type of your schema:

# Syntax 1: Anonymous Query (shorthand)
{
fieldName
}
# Syntax 2: Named Query (recommended for clarity and multiple operations)
query OperationName {
fieldName
}

Example (assuming a greeting field exists in the Query type):

# Anonymous Query
{
greeting
}
# Named Query
query GetGreeting {
greeting
}

While the query keyword and operation name are optional for single queries, explicitly naming your operations is a best practice, especially when dealing with variables or multiple operations in one request.

One of GraphQL’s core strengths is avoiding over-fetching. You list only the fields you need for a given object type.

# Schema Snippet
# type Student {
# id: ID!
# firstName: String
# lastName: String
# email: String
# collegeId: String
# }
# type Query {
# students: [Student]
# }
# Query requesting only ID and firstName for all students
query GetStudentNames {
students {
id
firstName
# lastName and email are NOT requested
}
}

The response will only contain an array of objects, each with id and firstName.

Fields can accept arguments to filter data or identify specific objects.

# Schema Snippet
# type Query {
# studentById(id: ID!): Student
# }
# Query requesting a student with a specific ID
query GetSpecificStudent {
studentById(id: "S1001") { # Pass argument 'id' with value "S1001"
id
firstName
lastName
email
}
}

If you need to query the same field multiple times with different arguments in a single operation, you must use aliases to avoid naming conflicts in the response JSON.

# Query requesting two different students using aliases
query GetMultipleStudents {
student1001: studentById(id: "S1001") { # Alias 'student1001'
id
firstName
}
student1002: studentById(id: "S1002") { # Alias 'student1002'
id
firstName
}
}
# Response Structure
# {
# "data": {
# "student1001": { ... },
# "student1002": { ... }
# }
# }

Fragments are reusable units of fields. They are useful for breaking down complex queries or avoiding repetition when you need the same set of fields on multiple objects of the same type.

# Define a fragment on the Student type
fragment StudentCoreDetails on Student {
id
firstName
lastName
}
# Use the fragment in a query
query GetStudentsWithCoreDetails {
student1001: studentById(id: "S1001") {
...StudentCoreDetails # Spread the fragment fields here
email # Request additional fields specific to this part of the query
}
allStudents: students {
...StudentCoreDetails # Reuse the fragment here
}
}

Hardcoding arguments directly into query strings is inflexible. Variables allow you to pass dynamic values alongside your query. This makes queries reusable and is crucial for security (prevents injection issues).

Using variables involves three steps:

  1. Declare the variable in the query definition using $ prefix and specify its type (e.g., $studentId: ID!).
  2. Use the variable within the query fields (e.g., studentById(id: $studentId)).
  3. Send a separate JSON dictionary alongside the query string mapping variable names to their values.
# 1 & 2: Query definition using a variable
query GetStudentWithVariable($studentId: ID!) {
studentById(id: $studentId) {
id
firstName
lastName
}
}
# 3: Separate Variables JSON sent with the request
{
"studentId": "S1002"
}

GraphQL clients (like Apollo Client, or tools like Apollo Sandbox/GraphiQL) provide mechanisms to send both the query string and the variables object.

Directives provide a way to conditionally include or skip fields or fragments based on variables. Common built-in directives are @include(if: Boolean!) and @skip(if: Boolean!).

# Query with conditional fields using directives
query GetStudentWithOptionalEmail($studentId: ID!, $includeEmail: Boolean!) {
studentById(id: $studentId) {
id
firstName
email @include(if: $includeEmail) # Include email only if $includeEmail is true
collegeId @skip(if: $includeEmail) # Skip collegeId if $includeEmail is true
}
}
# Variables JSON example 1 (includeEmail = true)
{
"studentId": "S1001",
"includeEmail": true
}
# -> Response will include 'email', skip 'collegeId'
# Variables JSON example 2 (includeEmail = false)
{
"studentId": "S1001",
"includeEmail": false
}
# -> Response will skip 'email', include 'collegeId'

Illustration 1: Query with a Custom Field Resolver

Section titled “Illustration 1: Query with a Custom Field Resolver”

Sometimes, a field you want to query doesn’t directly exist in your database model but can be computed from other fields. You handle this with a specific resolver for that field.

Goal: Query a fullName field for students, which is derived from firstName and lastName.

Add the fullName field to the Student type in your schema:

# In schema.graphql or server.js
type Student {
id: ID!
firstName: String
lastName: String
fullName: String # The computed field
# ... other fields
}

In your resolvers.js (or equivalent), add a resolver specifically for the fullName field within the Student type resolver map:

// In resolvers.js
const resolvers = {
Query: {
// ... your Query resolvers (e.g., students, studentById)
students: () => db.students.list(),
studentById: (_, { id }) => db.students.get(id),
},
Student: { // Resolvers for fields within the Student type
fullName: (parent, args, context, info) => {
// 'parent' here is the student object returned by the Query resolver
// Access firstName and lastName from the parent object
console.log(`Resolving Student.fullName for student ID: ${parent.id}`);
return `${parent.firstName} ${parent.lastName}`;
},
// No resolvers needed for id, firstName, lastName if they are directly
// available on the parent object from the Query resolver.
},
};

When you query for fullName, GraphQL will first resolve the parent Student object using the Query.students or Query.studentById resolver, and then it will execute the Student.fullName resolver, passing the resolved student object as the parent argument.

Run your server and use Apollo Sandbox to execute a query requesting the fullName:

query GetStudentFullNames {
students {
id
fullName # Request the computed field
}
}

The response should include the combined full names.

Illustration 2: Nested Query (Relationships)

Section titled “Illustration 2: Nested Query (Relationships)”

GraphQL excels at fetching related data in a single request.

Goal: Fetch student details along with their associated college information.

Ensure your schema defines the College type and links it from the Student type:

# In schema.graphql or server.js
type College {
id: ID!
name: String
location: String
}
type Student {
id: ID!
firstName: String
lastName: String
collegeId: String # Foreign key (data might be stored like this)
college: College # The field representing the relationship
}
type Query {
students: [Student]
# ... other queries
}

Step 2: Add Resolver for the Relationship Field

Section titled “Step 2: Add Resolver for the Relationship Field”

Add a resolver for the Student.college field. This resolver will use information from the parent student object (like collegeId) to fetch the related college data.

// In resolvers.js
// Assume db.colleges.get(id) exists in your mock db.js
const resolvers = {
Query: {
// ... Query resolvers
students: () => db.students.list(),
},
Student: {
// ... other Student resolvers like fullName
// Resolver for the 'college' relationship field
college: (parent, args, context, info) => {
// 'parent' is the student object
console.log(`Resolving Student.college for student ID: ${parent.id}, collegeId: ${parent.collegeId}`);
// Use the collegeId from the parent student to fetch the college
return db.colleges.get(parent.collegeId);
},
},
// Potentially resolvers for College fields if needed
};

This resolver runs for each student returned by Query.students. It takes the collegeId from the student (parent.collegeId) and uses it to look up the corresponding college in the (mock) college data source.

Run your server and execute a nested query in Apollo Sandbox:

query GetStudentsAndColleges {
students {
id
firstName
college { # Query the relationship field
# Request fields from the nested College object
id
name
location
}
}
}

The response will contain an array of students, each with a nested college object containing the requested college details, all fetched in a single request to the server.