accept-header
v0.1.0
Published
Standards-compliant Accept/Accept-Language/Accept-Encoding header parser and matcher for Node.js
Downloads
119
Maintainers
Readme
accept-header
Standards-compliant Accept / Accept-Language / Accept-Encoding header parser and matcher for Node.js.
Zero runtime dependencies. Works as both ESM (import) and CJS (require). TypeScript types included.
npm install accept-headerWhy?
Node.js developers building HTTP servers, REST APIs, or middleware need to parse and match the Accept-* header family to route requests to the appropriate response formatter/encoder. The de-facto accepts library ships 20+ transitive dependencies (mime-types, negotiator, …) for what is fundamentally a 200-line job. accept-header is the minimal, zero-dependency building block that does exactly one thing: parses Accept-* headers and picks the best match from a list.
Evidence of need:
- Express.js issue #2395 — Content negotiation based on Accept header — 312 reactions, open since 2014.
- Express.js issue #4639 — How to properly use content negotiation? — 87 reactions.
- Koajs router discussion #69 — content-type negotiation without heavy middleware.
- IETF HTTP semantics §content.negotiation — recommends servers implement content negotiation using Accept.
@types/accepts— only provides TypeScript types foraccepts, not the implementation.
Quickstart
// ESM
import { AcceptParser, parseAccept, parseAcceptLanguage, parseAcceptEncoding } from 'accept-header';
// CJS
const { AcceptParser, parseAccept, parseAcceptLanguage, parseAcceptEncoding } = require('accept-header');Content negotiation (Accept)
const parser = new AcceptParser('text/html, application/json;q=0.9, */*;q=0.1');
const match = parser.bestOf(['text/html', 'application/json', 'text/plain']);
// → 'application/json' (q=0.9 > text/plain's implicit q=0.1)Language negotiation (Accept-Language)
const langParser = new AcceptParser('en-US, fr;q=0.9, de;q=0.8', 'language');
const bestLang = langParser.bestOf(['en-US', 'fr-FR', 'de-DE']);
// → 'fr-FR'Encoding negotiation (Accept-Encoding)
const encParser = new AcceptParser('gzip, deflate, br;q=0.8', 'encoding');
const bestEnc = encParser.bestOf(['gzip', 'deflate', 'identity']);
// → 'gzip'Raw parsing
const accept = parseAccept('text/html, application/json;q=0.9');
// → [
// { type: 'application', subtype: 'json', q: 1.0, params: {}, original: 'application/json', index: 1 },
// { type: 'text', subtype: 'html', q: 0.9, params: { q: '0.9' }, original: 'text/html;q=0.9', index: 0 }
// ]
const langs = parseAcceptLanguage('en-US, fr;q=0.9');
// → [
// { tag: 'en', region: 'us', q: 1.0, ... },
// { tag: 'fr', region: null, q: 0.9, ... }
// ]
const encs = parseAcceptEncoding('gzip, deflate, br;q=0.8');
// → [
// { coding: 'gzip', q: 1.0, ... },
// { coding: 'deflate', q: 1.0, ... },
// { coding: 'br', q: 0.8, ... }
// ]API reference
parseAccept(header: string): MediaRange[]
Parses a comma-separated list of media ranges per RFC 7231 §5.3.2. Returns [] if the header is null/undefined or contains only malformed entries. Each result has {type, subtype, q, params, original, index}.
parseAcceptLanguage(header: string): LanguageRange[]
Parses Accept-Language per RFC 7231 §5.3.5. Tags are case-insensitive. Each result has {tag, region, q, params, original, index}.
parseAcceptEncoding(header: string): EncodingRange[]
Parses Accept-Encoding per RFC 7231 §5.3.4. Tokens are case-insensitive. Each result has {coding, q, params, original, index}.
class AcceptParser
new AcceptParser(input: string | ParsedRange[] | null, kind?: 'media' | 'language' | 'encoding')Wraps a parsed (or lazily-parsed) header. The optional kind parameter disambiguates the matching strategy; if omitted, bestOf infers it from the candidates:
- any candidate containing
/→ media-range matching - any candidate matching
/^[a-zA-Z]{2,3}(-[a-zA-Z0-9]+)*$/→ language tag matching - otherwise → encoding token matching
Methods:
parser.ranges— the sorted parsed rangesparser.kind— the matching strategy in useparser.bestOf(candidates: string[]): string | undefined— returns the best-matching candidate, orundefined. Tie-break: declaration order in candidates array. q=0 ranges are excluded. Wildcard candidates (*/*,*,identity) match any positive-q range. For Accept-Language, if no range matches, the first candidate is returned (RFC 7231 §5.3.5 fallback).parser.matches(candidates: string[]): string[]— returns all matching candidates in q-descending order, deduplicated.
Behaviour notes
- Empty / absent header → treated as
*/*;q=1.0(RFC 7231 §5.3.2: server may assume any media type is acceptable). - q-values are clamped to
[0, 1]. Malformed q-values (q=banana,q=,q=foo) default to 1.0 per RFC. - q=0 is the explicit "I refuse this type" signal — those ranges are excluded from matching.
- Tie-break on identical q: declaration order in the header is preserved (stable sort).
- Wildcards
*/*(in header) and*/*/*(in candidates) are honored;type/*matches any subtype oftype. - Case-insensitivity for media subtypes and language tags (per RFC).
- Param parsing respects quoted values:
text/html;title="Hello;World";q=0.5parses correctly. identityin encoding candidates always matches any positive-q encoding range per RFC 2616 §14.3.
Limitations
- No character-set weighting beyond the RFC 7231 q mechanism.
- No request-body Content-Type negotiation (response-side Accept only).
- No caching-aware content negotiation (RFC 2295).
- No transitive dependencies on Node.js built-ins (
http, etc.) — pure JavaScript. - No parsing of
Accept-Rangesheader (out of scope; can be added as a small follow-up).
License
MIT — see LICENSE.
