@fastify/etag
A Fastify plugin that automatically generates HTTP ETags and replies 304 Not Modified when a request's If-None-Match header matches.
Repository Health
Technical Analysis
@fastify/etag is an official Fastify plugin that automatically computes and attaches ETag headers to HTTP responses. It hooks into Fastify’s onSend lifecycle to hash string and Buffer payloads using Node’s crypto module (sha1 by default, or md5, or any other algorithm the runtime supports), or a custom hash function supplied by the consumer. Stream payloads are skipped entirely so the plugin never buffers a response body just to compute a hash, and it respects an etag a route handler has already set instead of overwriting it.
When a request’s If-None-Match header matches the computed (or existing) etag, the plugin automatically downgrades the response to a 304 Not Modified with an empty body, saving bandwidth on cache-validated requests. It supports weak validators via the weak option, ships a legacy FNV-1a implementation for fast hashing of very small payloads, and includes full TypeScript typings. As an official plugin under the fastify GitHub organization, its versions are tied directly to Fastify’s own major-version compatibility table.
What You Get
- Automatic ETag generation for string and Buffer response bodies via Node’s crypto module
- Automatic 304 Not Modified replies when a request’s If-None-Match header matches the generated tag
- Pluggable hashing: swap in a custom hashFn or pick any crypto algorithm (sha1, md5, etc.)
- Weak ETag (W/) support for semantically-equivalent responses
- Full TypeScript type definitions for the plugin options
Common Use Cases
- Enabling standards-compliant HTTP caching for JSON APIs served by Fastify
- Reducing bandwidth for polling or long-lived clients whose responses rarely change
- Adding weak ETags to responses that are semantically but not byte-for-byte identical
- Letting individual routes opt out by setting their own etag header before the hook runs
Under The Hood
Architecture
The plugin is a single CommonJS module (index.js) wrapped with fastify-plugin so it can decorate the parent Fastify instance without creating an encapsulation boundary. It builds a hash function once per registration via buildHashFn(algorithm, weak, hashFn) — lazily requiring either the built-in FNV-1a implementation (fnv1a.js) or Node’s crypto.createHash only when needed — and registers a single onSend hook that inspects the outgoing payload, generates or preserves an etag, and mutates the reply to a 304 when the request’s If-None-Match header matches (handling both strong/weak comparisons). Because the entire behavior funnels through this one hook and one hash-builder function, changing the hashing strategy or the 304-matching logic touches a very small, well-isolated surface.
Tech Stack
Plain Node.js, no build step, no bundler — the package ships hand-written .js files plus a hand-written types/index.d.ts. Its only runtime dependency is fastify-plugin (^6), with fastify (^5.x) as the expected host framework declared only in devDependencies/peer usage. Tooling is entirely dev-side: c8 for coverage, Node’s built-in node:test runner, tstyche for type-level testing against types/index.tst.ts, eslint with the neostandard shared config, and autocannon for the benchmarking scripts under benchmarks/.
Code Quality
Tests live in test/basic.test.js and a shared test/generic.js helper that parametrizes the same request/response assertions across every hashing mode (md5, sha1, fnv1a, custom hashFn, and the default), covering 304 matching, weak/strong mismatches, stream skipping, and pre-set etags. Coverage is enforced at 100% via c8 --100, invalid algorithms throw an explicit typed TypeError, and a deprecation warning path for the legacy fnv1a algorithm option is itself tested. Type definitions are validated with tstyche, and CI runs lint plus both test suites on every push.
API Design
The plugin registers with zero required options — app.register(Etag) is enough to get sane defaults (sha1, strong etags, automatic 304s) — while power users get three well-scoped escape hatches: algorithm, hashFn, and weak. The exported fnv1a helper doubles as both an internal implementation and a public utility consumers can pass directly as a hashFn, keeping the public surface small and consistent with other @fastify/* plugins.