🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)
🔧 AI Nachrichten Major AI platforms go down in unprecedented simultaneous outage(03.09.2026 um 17:34 Uhr)
🔧 AI Nachrichten ChatGPT, Claude, and Grok Down? Users Report Widespread Outages(03.09.2026 um 19:14 Uhr)
🔧 AI Nachrichten OpenAI Launches GPT-6 Astra, Says We May Have Entered the AGI Era(03.09.2026 um 22:08 Uhr)
🔧 AI Nachrichten Claude Comes to CarPlay as Fifth Major AI Chatbot App(05.09.2026 um 05:31 Uhr)
🔧 AI Nachrichten OpenAI’s GPT-6 Astra Is AGI, Says NVIDIA CEO Jensen Huang(07.09.2026 um 06:31 Uhr)
🔧 AI Nachrichten Blame AI companies for Mac mini and Mac Studio shortage(31.08.2026 um 10:32 Uhr)

🔧 Programmierung 🕛 kürzlich 9 Min Lesezeit
0

GraphQL Response Caching with Envelop

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

This article was published on Thursday, August 19, 2021 by







A Brief Introduction to Caching



Huge GraphQL query operations can slow down your server as deeply nested selection sets can cause a

lot of subsequent database reads or calls to other remote services. Tools like DataLoader can

reduce the amount of concurrent and subsequent requests via batching and caching during the

execution of a single GraphQL operation. Features like @defer and @stream can help with

streaming slow-to-retrieve result partials to the clients progressively. However, for subsequent

requests we hit the same bottle-neck over and over again.



What if we don't need to go through the execution phase at all for subsequent requests that execute

the same query operation with the same variables?



A common practice for reducing slow requests is to leverage caching. There are many types of caching

available. E.g. We could cache the whole HTTP responses based on the POST body of the request or an

in memory cache within our GraphQL field resolver business logic in order to hit slow services less

frequently.



Having a cache comes with the drawback of requiring some kind of cache invalidation mechanism.

Expiring the cache via a TTL (time to live) is a widespread practice, but can result in hitting the

cache too often or too scarcely. Another popular strategy is to incorporate cache invalidation logic

into the business logic. Writing such logic can potentially become too verbose and hard to maintain.

Other systems might use database write log observers for invalidating entities based on updated

database rows.



In a strict REST API environment, caching entities is significantly easier, as each endpoint

represents one resource, and thus a GET method can be cached and a PATCH method can be used for

automatically invalidating the cache for the corresponding GET request, which is described via the

HTTP path (/api/user/12).



With GraphQL such things become much harder and complicated. First, we usually only have a single

HTTP endpoint /graphql that only accepts POST requests. A query operation execution result could

contain many different types of entities, thus, we need different strategies for caching GraphQL

APIs.



SaaS services like FastQL and GraphCDN started popping providing proxies for your existing GraphQL

API, that magically add response based caching. But how does this even work?





How Does GraphQL Response Caching Work?





Caching Query Operations



In order to cache a GraphQL execution result (response) we need to build an identifier based on the

input that can be used to identify whether a response can be served from the cache or must be

executed and then stored within the cache.



Example: GraphQL Query Operation




CODE
query UserProfileQuery($id: ID!) {
user(id: $id) {
__typename
id
login
repositories
friends(first: 2) {
__typename
id
login
}
}
}






Example: GraphQL Variables




CODE
{
"id": "1"
}






Usually those inputs are the Query operation document and the variables for such an operation

document.



Thus, a response cache can store the execution result under a cache key that is built from those

inputs:




CODE
OperationCacheKey (e.g. SHA1) = hash(GraphQLOperationString, Stringify(GraphQLVariables))






Under some circumstances it is also required to cache based on the request initiator. E.g. a user

requesting his profile should not receive the cached profile of another user. In such a scenario,

building the operation cache key should also include a partial that uniquely identifies the

requestor. This could be a user ID extracted from an authorization token.




CODE
OperationCacheKey (e.g. SHA1) = hash(GraphQLOperationString, Stringify(GraphQLVariables), RequestorId)






This allows us to identify recurring operations with the same variables and serve it from the cache

for subsequent requests. If we can serve a response from the cache we don't need to parse the

GraphQL operation document and furthermore can skip the expensive execution phase. That will result

in significant speed improvements.



But in order to make our cache smart we still need a suitable cache invalidation mechanism.





Invalidating Cached GraphQL Query Operations



Let's take a look at a possible execution result for the GraphQL operation.



Example: GraphQL Execution Result




CODE
{
"data": {
"user": {
"__typename": "User",
"id": "1",
"login": "dotan",
"repositories": ["codegen"],
"friends": [
{
"__typename": "User",
"id": "2",
"login": "urigo"
},
{
"__typename": "User",
"id": "3",
"login": "n1ru4l"
}
]
}
}
}






Many frontend frameworks cache GraphQL operation results in a normalized cache. The identifier for

storing the single entities of a GraphQL operation result within the cache is usually the id field

of object types for schemas that use global unique IDs or a compound of the __typename and id

field for schemas that use non-global ID fields.



Example: Normalized GraphQL Client Cache




CODE
{
"User:1": {
"__typename": "User",
"id": "1",
"login": "dotan",
"repositories": ["codegen"],
"friends": ["$$ref:User:2", "$$ref:User:3"]
},
"User:2": {
"__typename": "User",
"id": "2",
"login": "urigo"
},
"User:3": {
"__typename": "User",
"id": "3",
"login": "n1ru4l"
}
}






Interestingly, the same strategy for constructing cache keys on the client can also be used on the

backend for tracking which GraphQL operations contain which entities. That allows invalidating

GraphQL query operation results based on entity IDs.



For the execution result entity IDS that could be used for invalidating the operation are the

following: User:1, User:2 and User:3.



And also keep a register that maps entities to operation cache keys.




CODE
Entity   List of Operation cache keys that reference a entity

User:1 OperationCacheKey1, OperationCacheKey2, ...
User:2 OperationCacheKey2, OperationCacheKey3, ...
User:3 OperationCacheKey3, OperationCacheKey1, ...






This allows us to keep track of which GraphQL operations must be invalidated once a certain entity

becomes stale.



The remaining question is, how can we track an entity becoming stale?



As mentioned before, listening to a database write log is a possible option - but the implementation

is very specific and differs based on the chosen database type. Time to live is also a possible, but

a very inaccurate solution.



Another solution is to add invalidation logic within our GraphQL mutation resolver. By the GraphQL

Specification mutations are meant to modify our GraphQL graph.



A common pattern when sending mutations from clients is to select and return affected/mutated

entities with the selection set.



For our example from above the following could be a possible mutation for adding a new repository to

the repositories field on the user entity.



Example: GraphQL Mutation




CODE
mutation RepositoryAddMutation($userId: ID, $repositoryName: String!) {
repositoryAdd(userId: $userId, repositoryName: $repositoryName) {
user {
id
repositories
}
}
}






Example: GraphQL Mutation Execution Result




CODE
{
"data": {
"repositoryAdd": {
"user": {
"id": "1",
"repositories": ["codegen", "envelop"]
}
}
}
}






Similar to how we build entity identifiers from the execution result of query operations for

identifying what entities are referenced in which operations, we can extract the entity identifiers

from the mutation operation result for invalidating affected operations.



In this specific case all operations that select User:1 should be invalidated.



Such an implementation makes the assumption that all mutations by default select affected entities

and, furthermore, all mutations of underlying entities are done through the GraphQL gateway via

mutations. In a scenario where we have actors that are not GraphQL services or services that operate

directly on the database, we can use this approach in a hybrid model with other methods such as

listening to database write logs.





Envelop Response Cache



The Envelop response cache plugin now provides primitives and a reference in memory store

implementation for adopting such a cache with all the features mentioned above with any GraphQL

server.



The goal of the response cache plugin is to educate how such mechanisms are implemented and

furthermore give developers the building blocks for constructing their own global cache with their

cloud provider of choice.



Adding a response cache to an existing envelop GraphQL server setup is as easy as adding the plugin:




CODE
import { envelop } from '@envelop/core'
import { useResponseCache } from '@envelop/response-cache'

const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useResponseCache()
]
})






If you need to imperatively invalidate you can do that by providing the cache to the plugin:




CODE
import { envelop } from '@envelop/core'
import { createInMemoryCache, useResponseCache } from '@envelop/response-cache'
import { emitter } from './event-emitter'

const cache = createInMemoryCache()

emitter.on('invalidate', entity => {
cache.invalidate([
{
typename: entity.type,
id: entity.id
}
])
})

const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useResponseCache({ cache })
]
})






The caching behavior can be fully customized. A TTL can be provided global or more granular per type

or schema coordinate.




CODE
import { envelop } from '@envelop/core'
import { useResponseCache } from '@envelop/response-cache'

const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useResponseCache({
// cache operations for 1 hour by default
ttl: 60 * 1000 * 60,
ttlPerType: {
// cache operation containing Stock object type for 500ms
Stock: 500
},
ttlPerSchemaCoordinate: {
// cache operation containing Query.rocketCoordinates selection for 100ms
'Query.rocketCoordinates': 100
},
// never cache responses that include a RefreshToken object type.
ignoredTypes: ['RefreshToken']
})
]
})






Need to cache based on the user? No problem.




CODE
import { envelop } from '@envelop/core'
import { useResponseCache } from '@envelop/response-cache'

const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useResponseCache({
// context is the GraphQL context that would be used for execution
session: context => (context.user ? String(context.user.id) : null),
// never serve cache for admin users
enabled: context => (context.user ? isAdmin(context.user) === false : true)
})
]
})






Don't want to automatically invalidate based on mutations? Also, configurable!




CODE
import { envelop } from '@envelop/core'
import { useResponseCache } from '@envelop/response-cache'

const getEnveloped = envelop({
plugins: [
// ... other plugins ...
useResponseCache({
// some might prefer invalidating only based on a database write log
invalidateViaMutation: false
})
]
})






Want a global cache on Redis? Build a cache that implements the Cache interface and share it with

the community!




CODE
export type Cache = {
/** set a cache response */
set(
/** id/hash of the operation */
id: string,
/** the result that should be cached */
data: ExecutionResult,
/** array of entity records that were collected during execution */
entities: Iterable<CacheEntityRecord>,
/** how long the operation should be cached */
ttl: number
): PromiseOrValue<void>
/** get a cached response */
get(id: string): PromiseOrValue<Maybe<ExecutionResult>>
/** invalidate operations via typename or id */
invalidate(entities: Iterable<CacheEntityRecord>): PromiseOrValue<void>
}






More information about all possible configuration options can be found on

the response cache docs on the Plugin Hub.








What Is Next?



We are excited to explore new directions and make enterprise solutions accessible for all kinds of

developers.



What if the response cache could be used as a proxy on edge cloud functions distributed around the

world, which would allow using envelop as a http proxy to your existing GraphQL server? This is

something we would love to explore more (or even see contributions and projects from other

open-source developers).



We also want to make other practices such as rate limits based on operation cost calculation as used

by huge corporations like Shopify available as envelop plugins.



Do you have any ideas, want to contribute or report issues? Start a GitHub discussion/issue or

contact us via the chat!

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
GPT-6 Astra Release Today? OpenAI’s Next Major AI Model Is Almost Here
1 Quelle
Apple accuses OpenAI of destroying evidence as trade-secrets fight intensifies
1 Quelle
Major AI platforms go down in unprecedented simultaneous outage
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten GraphQL Response Caching with Envelop

Thematisch verwandte Begriffe: GraphQL, Response, Caching, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...