Caching & conditional requests
QUERY responses are cacheable and support conditional requests, the same way GET responses do. This plugin follows Fastify's model: the core enables the method; caching is composed from the ecosystem — exactly as you would cache GET. There is no bespoke cache here.
Conditional requests with @fastify/etag
@fastify/etag computes an ETag from the response payload and answers If-None-Match with 304. Because the ETag is derived from the results, two QUERY requests with different bodies naturally produce different ETags — so conditional handling is correct for QUERY with no extra configuration.
import Fastify from 'fastify'
import fastifyHttpQuery from '@thecodepace/fastify-http-query'
import etag from '@fastify/etag'
const app = Fastify()
await app.register(fastifyHttpQuery)
await app.register(etag)
app.query('/search', (request) => runSearch(request.body))
await app.listen({ port: 3000 })Try:
# First call — full response, with an ETag.
curl -i -X QUERY http://localhost:3000/search \
-H 'content-type: application/json' \
--data '{"q":"a"}'
# Same body, matching If-None-Match — 304 Not Modified.
curl -i -X QUERY http://localhost:3000/search \
-H 'content-type: application/json' \
-H 'if-none-match: "<the-etag-from-above>"' \
--data '{"q":"a"}'
# Different body, same If-None-Match — 200 with a fresh response.
curl -i -X QUERY http://localhost:3000/search \
-H 'content-type: application/json' \
-H 'if-none-match: "<the-etag-from-above>"' \
--data '{"q":"b"}'Cache-Control with @fastify/caching
@fastify/caching manages Cache-Control / Expires and provides reply.etag():
import caching from '@fastify/caching'
await app.register(caching, {
privacy: caching.privacy.PRIVATE,
expiresIn: 3600
})
// QUERY responses now carry: Cache-Control: private, max-age=3600Shared-cache caveat (body-keying)
The spec requires a cache key that incorporates the request body. Origin- side tools like @fastify/etag and @fastify/caching key on the results or the URL+method, and are safe because the body that produced a cached entry is implicit in the entry itself.
Shared intermediary caches (CDNs / proxies) key on method + URL only and are not body-aware — they can serve the wrong result for a different body sent to the same URL. So:
- Do not let an untrusted shared cache store
QUERYresponses. - Keep them
private/no-storeat the edge. - Or place a body-aware cache in front.
Fastify likewise does not manage downstream caches for GET; the same caveats apply.
Next steps
- Set a Content-Location on
2xxresponses so caches can be revalidated viaGET.