You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HTTP caching is keyed on method and URL. POST /graphql with the operation in the body has one URL for every query and the body is not part of the cache key, so ETag, If-None-Match, Last-Modified and Cache-Control on a POST response are inert – no browser cache, CDN or shared proxy stores or revalidates it. The NDE Stack notes this as “an edge the single POST /graphql endpoint” lacks (layers/platform.md, REST API). The search API currently offers no cacheable path.
Proposal
Serve query operations over GET with persisted operations, and emit HTTP caching headers on those responses.
GET for query operations. The GraphQL spec permits GET /graphql?query=…&variables=… for queries (not mutations); graphql-yoga (ADR 14) already handles it. Document it as the cacheable path.
Persisted operations. Full queries are too long for URLs and defeat cache keys by whitespace. With persisted operations a client sends GET /graphql?extensions={"persistedQuery":{"sha256Hash":"…"}}&variables={…} – a short, stable URL per operation and variables. graphql-yoga’s @graphql-yoga/plugin-persisted-operations supports it; the operations can be extracted from a consumer’s committed documents at build time (LOL already commits schema.graphql; the client documents are the natural next artefact). Allow-listing is a bonus: a deployment can refuse arbitrary operations from the public internet while keeping the playground open.
Caching headers on GET responses, per the NDE generic API specification’s caching chapter, which is the well-specified text for this: Cache-Control (a deployment-chosen max-age), ETag, Last-Modified, 304 on If-None-Match / If-Modified-Since with If-None-Match taking precedence, and Vary: Accept-Language (Search API varies by Accept-Language without sending Vary or Content-Language #800) plus Vary: Accept.
What the validator means. The honest ETag is a hash of the response body – cheap, exact, and independent of writer mode. Last-Modified needs a source: the collection’s last successful commit for a blue/green deployment (one moment for every representation); for an In-place deployment the newest last_seen/indexed-at among the documents in the response, since documents change continuously beneath a long-lived collection and there is no single invalidation moment. State which one a deployment uses. (Schema/index migration support: keep SearchSchema and the search index from drifting during a deploy #531 – keeping schema and index from drifting during a deploy – is adjacent.)
Not in scope
Per-entity URLs: GraphQL has none; entity-level caching stays with the client’s normalised cache keyed by id, which is one more reason id must be the stable IRI.
Response-level Cache-Control on POST: leave it absent rather than pretend.
Context
From the review of the NDE generic API specification against LDE and LOL. The specification’s HTTP chapters – caching, rate limiting, CORS, client identification, versioning, problem details – are its strongest part and transport-agnostic; rate limiting, CORS, User-Agent and API-Version apply to POST /graphql as they are, but caching applies to a GraphQL API only via GET with persisted operations. This issue is that step.
HTTP caching is keyed on method and URL.
POST /graphqlwith the operation in the body has one URL for every query and the body is not part of the cache key, soETag,If-None-Match,Last-ModifiedandCache-Controlon a POST response are inert – no browser cache, CDN or shared proxy stores or revalidates it. The NDE Stack notes this as “an edge the singlePOST /graphqlendpoint” lacks (layers/platform.md, REST API). The search API currently offers no cacheable path.Proposal
Serve query operations over
GETwith persisted operations, and emit HTTP caching headers on those responses.GETfor query operations. The GraphQL spec permitsGET /graphql?query=…&variables=…for queries (not mutations); graphql-yoga (ADR 14) already handles it. Document it as the cacheable path.GET /graphql?extensions={"persistedQuery":{"sha256Hash":"…"}}&variables={…}– a short, stable URL per operation and variables. graphql-yoga’s@graphql-yoga/plugin-persisted-operationssupports it; the operations can be extracted from a consumer’s committed documents at build time (LOL already commitsschema.graphql; the client documents are the natural next artefact). Allow-listing is a bonus: a deployment can refuse arbitrary operations from the public internet while keeping the playground open.GETresponses, per the NDE generic API specification’s caching chapter, which is the well-specified text for this:Cache-Control(a deployment-chosenmax-age),ETag,Last-Modified,304onIf-None-Match/If-Modified-SincewithIf-None-Matchtaking precedence, andVary: Accept-Language(Search API varies by Accept-Language without sending Vary or Content-Language #800) plusVary: Accept.ETagis a hash of the response body – cheap, exact, and independent of writer mode.Last-Modifiedneeds a source: the collection’s last successful commit for a blue/green deployment (one moment for every representation); for an In-place deployment the newestlast_seen/indexed-at among the documents in the response, since documents change continuously beneath a long-lived collection and there is no single invalidation moment. State which one a deployment uses. (Schema/index migration support: keep SearchSchema and the search index from drifting during a deploy #531 – keeping schema and index from drifting during a deploy – is adjacent.)Not in scope
id, which is one more reasonidmust be the stable IRI.Cache-ControlonPOST: leave it absent rather than pretend.Context
From the review of the NDE generic API specification against LDE and LOL. The specification’s HTTP chapters – caching, rate limiting, CORS, client identification, versioning, problem details – are its strongest part and transport-agnostic; rate limiting, CORS,
User-AgentandAPI-Versionapply toPOST /graphqlas they are, but caching applies to a GraphQL API only viaGETwith persisted operations. This issue is that step.