npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

etag-mini

v0.2.1

Published

Compute strong/weak ETags for strings, buffers, streams. Includes If-Match / If-None-Match comparison helpers. Zero dependencies (Node crypto only).

Readme

etag

ci

npm downloads bundle

Compute strong/weak ETags for strings, Buffers, and streams. Plus conditional-request helpers. Zero dependencies; uses Node's built-in crypto.

import { etag, weakEtag, statEtag, etagStream, ifMatch, ifNoneMatch } from "etag-mini";

etag("hello world")               // '"b-Kq5sNclPz7QV2+lfQIuc6R7oRu0"'
await etagStream(fs.createReadStream("file"));
statEtag(stat.size, stat.mtimeMs);

ifNoneMatch(req.headers["if-none-match"], currentEtag);
// → false means "respond 304 Not Modified"

Install

npm install etag-mini

The npm name is etag-mini because etag is already taken on npm. The GitHub repo is etag.

Works with Node 20+ (uses node:crypto). ESM + CJS.

Why

ETags are HTTP's content-fingerprint header. Used right, they:

  • Save bandwidth — clients with a fresh copy get 304 Not Modified (empty body) instead of re-downloading.
  • Enable optimistic concurrency — If-Match ensures you don't overwrite changes you didn't know about.

etag-mini covers both directions: compute ETags and evaluate conditional request headers per RFC 7232. Most existing libraries do half the job — etag computes but doesn't evaluate; fresh evaluates but doesn't compute streams. This is both, ~150 lines.

Recipes

Conditional response in plain http

import { etag, ifNoneMatch } from "etag-mini";
import { createServer } from "node:http";

createServer((req, res) => {
  const body = renderPage();
  const tag = etag(body);
  res.setHeader("ETag", tag);

  if (!ifNoneMatch(req.headers["if-none-match"], tag)) {
    res.statusCode = 304;
    res.end();
    return;
  }
  res.setHeader("Content-Type", "text/html");
  res.end(body);
}).listen(3000);

Static file with stat-based ETag

import { statEtag, ifNoneMatch } from "etag-mini";
import { promises as fsp, createReadStream } from "node:fs";

const s = await fsp.stat(filepath);
const tag = statEtag(s.size, s.mtimeMs);
res.setHeader("ETag", tag);

if (!ifNoneMatch(req.headers["if-none-match"], tag)) {
  res.statusCode = 304;
  return res.end();
}
createReadStream(filepath).pipe(res);

Optimistic concurrency on PUT

import { etag, ifMatch } from "etag-mini";

async function updateDoc(req, res) {
  const current = await db.get(id);
  const currentTag = etag(JSON.stringify(current));

  if (!ifMatch(req.headers["if-match"], currentTag)) {
    res.statusCode = 412;
    return res.end("Precondition Failed — document changed since you read it");
  }

  await db.update(id, await readJsonBody(req));
  res.setHeader("ETag", etag(JSON.stringify(await db.get(id))));
  res.end();
}

Stream ETag for large files

import { etagStream } from "etag-mini";
import { createReadStream } from "node:fs";

const tag = await etagStream(createReadStream(largeFile));
// Hashes lazily without buffering the whole file

API

Compute

  • etag(stringOrBuffer): string"<hex-length>-<base64-sha1>", compatible with the Node etag package
  • weakEtag(stringOrBuffer): string — prefixed W/
  • statEtag(size, mtimeMs, weak?): string — cheap content-free ETag from stat data
  • etagStream(asyncIterable<Buffer|Uint8Array>): Promise<string> — works with Node Readable, Web ReadableStream, generators

Compare

  • tagsEqual(a, b, opts?: { strong?: boolean }): boolean — weak/strong-aware
  • ifMatch(header, currentEtag): booleantrue → proceed
  • ifNoneMatch(header, currentEtag): booleanfalse → reply 304

ifMatch requires strong equality (per RFC 7232); ifNoneMatch treats weak and strong as equivalent by default (also per RFC).

Strong vs weak

A strong ETag changes when bytes change. Two responses with the same strong ETag are byte-for-byte identical.

A weak ETag (W/"...") changes when semantic content changes — different whitespace, different gzip encoding could share a weak ETag. Use weak when generating ETags from content metadata (size+mtime, hash of a model object) rather than from the served bytes.

ifMatch for PUT-style writes wants strong equivalence. ifNoneMatch for GET caching is fine with weak equivalence.

Caveats

  • SHA-1 isn't cryptographically strong anymore. Fine for ETags — collision resistance for caching isn't a security property.
  • No tag normalization for user-supplied headers. Per spec, ETag headers should be quoted; if a buggy client sends If-None-Match: abc (no quotes), comparison fails.

License

Apache-2.0 © Vlad Bordei