Introduction to GraphQL
GraphQL was developed by Facebook in 2012 and was released as an open-source project in 2015. It has since become a popular alternative to REST APIs, especially for complex data-fetching scenarios.
What is GraphQL?
GraphQL is a query language for our API and a server-side runtime for executing queries using a type system we define for our data.
Terminologies
- Schema: The core of any GraphQL service, defining the types and the relationships between them.
- Query: A read-only fetch operation.
- Mutation: A write operation that allows us to insert, update, or delete data.
- Subscription: Allows clients to subscribe to real-time updates.
- Resolvers: Functions that handle fetching the data for each type in a schema.
💡 The correct term for the component that defines and serves the GraphQL schema is typically called the GraphQL Server or API Server.
Example from Octopus Energy API:
query EnergyProductQuery($productCode: String!) {
energyProduct(code: $productCode) {
code
fullName
displayName
description
term
}
}
Comparison with REST APIs
While we can implement GraphQL in a way that functions exactly the same as REST, when implemented correctly and wisely, GraphQL can introduce differences like below.
🐙 Structure
- REST: Multiple endpoints for different resources.
- GraphQL: Single endpoint for all queries and mutations.
🐙 Data Fetching
- REST: Over-fetching or under-fetching of data.
- GraphQL: Fetch exactly the data we need.
🐙 Flexibility
- REST: Fixed structure for responses.
- GraphQL: Flexible query structure.
🐙 Efficiency
- REST: Multiple requests may be needed to fetch related data.
- GraphQL: One request can fetch all required data.
Advantages of Using GraphQL over REST APIs
Efficient Data Fetching
With GraphQL, we can request exactly the data we need in a single query, reducing the number of requests and the amount of data transferred over the network.
Flexible Queries
GraphQL’s flexible query structure allows clients to specify the shape and size of the response, which can be particularly useful for mobile applications with varying screen sizes and data needs.
Example:
query WanCoverageQuery($postcode: String!) {
wanCoverage(postcode: $postcode)
wanCoverageDetail(postcode: $postcode) {
postcode
addressIdentifier
isCoverageAvailable
anticipatedCoverageAt
wanTechnology
auxiliaryEquipment
connectivityLikelihood
additionalInformation
}
}
Strongly Typed Schema
GraphQL uses a strongly typed schema to define the data and its relationships, providing clear, self-documenting APIs that make it easier to understand and use.
Example:
properties(accountNumber: String!, active: Boolean): [PropertyType]
type PropertyType implements PropertyInterface {
id: String
postcode: String!
address: String
richAddress: PropertyRichAddressType
splitAddress: [String]
occupancyPeriods: [OccupancyPeriodType]
}
Real-Time Data with Subscriptions
GraphQL subscriptions allow clients to receive real-time updates when data changes, which is beneficial for applications that require live updates, such as dashboards or chat applications.
💡 Subscriptions are typically implemented using WebSockets. Server-side support is required.
Example:
subscription MeterReadingSubscription($meterSerial: String!) {
meterReadingSubscribe(serialNumber: $meterSerial) {
readings {
readAt
source
readingSource
value
}
}
}
Working with GraphQL Schemas
The schema is defined on the server side. It describes the types, queries, mutations, and subscriptions that the server supports. The schema is central to the GraphQL server and is written using the GraphQL Schema Definition Language (SDL) or programmatically in code.
Client-Side Schema Handling
On the client side, we don’t necessarily need to define the schema like on the server. Instead, we use the schema to generate or define the data structures the client will use to interact with the GraphQL server.
💡 In part two of this article, we will see how tools like Apollo Android can introspect the schema and generate Kotlin data classes and query/mutation classes for us.
Handling Schema Changes
If the server changes its implementation (i.e., the schema), the client must be aware of these changes to avoid breaking functionality. Here’s how we can manage this:
- Schema Introspection:
The client can query the GraphQL server for its schema using introspection queries. This allows the client to understand the types and operations supported by the server. - Versioning:
The server can use schema versioning to manage changes. Major changes can be versioned so clients using older versions are not affected immediately. - Client Updates:
When the schema changes, we may need to update the client-side code to accommodate the new schema. This includes regenerating the data classes and updating the queries/mutations accordingly.
🧑💻 Example: Schema Introspection Query
This query lets the client fetch details about the entire schema, including all types and fields.
{
__schema {
types {
name
fields {
name
type {
name
}
}
}
}
}
REST API Versioning and Changes
When it comes to versioning and managing changes, REST APIs and GraphQL take distinct approaches, each with its own set of strategies and challenges.
Versioned Endpoints
- Path Versioning:
A common method is to include the version number in the URL (e.g.,/api/v1/resource
), allowing multiple versions to coexist and giving clients the flexibility to upgrade at their own pace. - Header Versioning:
Another approach is to specify the version in the request headers (e.g.,Accept: application/vnd.myapi.v1+json
), which also supports multiple versions.
Backward Compatibility
- Deprecation:
Old endpoints can be maintained while new versions are introduced, providing a transition period for clients. - Feature Flags:
New features can be introduced as optional parameters or flags within existing endpoints to avoid breaking changes.
Granularity
Each endpoint can be independently versioned and updated, offering flexibility but potentially leading to fragmentation and increased complexity in maintaining multiple versions.
Slow Version Bumps
REST APIs can introduce new features or endpoints within the same version if there are no conflicts. This approach allows for incremental updates and reduces the frequency of major version changes.
GraphQL Versioning and Changes
Single Schema
- GraphQL operates with a single schema that defines all available types and queries, making it challenging to version because all changes impact the same entry point.
Deprecation
- Fields and types in GraphQL can be marked as deprecated with a message indicating what should be used instead. This method allows clients to be aware of changes without breaking existing queries.
Backward Compatibility
- Changes in GraphQL are typically additive.
- New fields or types are added rather than modifying or removing existing ones, ensuring that old queries still function while new features are introduced.
Frequent Schema Changes
- Modifications to the schema are frequent but do not necessarily lead to a major version bump. Additive changes and deprecation strategies help manage the evolving schema without breaking existing clients.
Tooling and Practices
- Schema Stitching:
Combining multiple schemas into one can help transition between versions. - Schema Federation:
Splitting a single schema into multiple smaller schemas allows independent management and versioning. - GraphQL Directives:
Used to dynamically change schema behaviour without altering the schema itself.
Managing Changes: Industry Practices
While REST APIs manage slower version bumps through independent endpoint versioning and non-breaking changes, GraphQL’s single schema approach requires more frequent schema modifications. However, GraphQL mitigates the need for frequent version bumps through strong backward compatibility and deprecation strategies.
REST API
- Version Bumps: Major versions change less frequently, with minor updates and new endpoints added within the same major version when possible.
- Documentation: Clear documentation helps clients understand new features and changes within the same version.
GraphQL
- Deprecation Strategy: Deprecated fields remain in the schema for a long period to ensure clients have time to adapt.
- Additive Changes: Most changes are additive, such as adding new fields or types, avoiding the need for frequent version bumps.
- Versioning Alternatives: GraphQL relies on schema evolution and client adaptation to deprecated features rather than traditional versioning.
Before Moving On
In this first part of our series on transitioning from REST APIs to GraphQL, we’ve explored the fundamental differences and advantages that GraphQL offers over traditional RESTful services.
By leveraging GraphQL’s single endpoint for queries and mutations, its efficient data fetching, and the flexibility it provides through strongly typed schemas, developers can significantly enhance the way applications interact with APIs.
As we’ve discussed, GraphQL’s approach to handling data operations offers substantial improvements in efficiency and flexibility, particularly in complex data-fetching scenarios. Understanding these core principles is essential as we dive deeper into integrating GraphQL into our projects.
⭐️ The next part of our series, we will introduce setting up a GraphQL client on Android/Kotlin Multiplatform: