HTTP Media Type Negotiator
🚀 Features
- No runtime dependencies.
- Runtime and framework agnostic.
- Negotiates HTTP media types (Accept header) against server-supported types following RFC 9110 §5.6.
- Parses and normalises media types and Accept headers with optional permissive mode to tolerate common real-world deviations.
- Supports q-value parsing and ranking.
- Tie-breaking by specificity, shared parameters, then server order for deterministic results.
- Exposes both a reusable factory for repeated negotiation and a one-shot convenience function.
- It also exposes convenience functions (also used internally) for:
- Parsing media types (
parseMediaType) - Normalising said media types (
normaliseMediaType) - Parsing
Acceptheades (parseAcceptHeader)
- Parsing media types (
💻 Installation
Install from npm or yarn:
npm install @apeleghq/http-media-type-negotiatoror
yarn add @apeleghq/http-media-type-negotiator📚 Usage
One-shot negotiation
Use the convenience function when you just need to negotiate once:
import { negotiateMediaType } from '@apeleghq/http-media-type-negotiator';
const available = [
'text/plain; charset=utf-8',
'application/json',
];
const best = negotiateMediaType(available, 'text/*;q=0.9, application/json;q=0.8');
// best -> 'text/plain; charset=utf-8'Reusable negotiator (recommended for repeated calls)
Create a negotiator once for a fixed set of server-supported media types to avoid reparsing:
import { negotiateMediaTypeFactory } from '@apeleghq/http-media-type-negotiator';
const available = [
'text/plain; charset=utf-8',
'application/json',
];
const negotiate = negotiateMediaTypeFactory(available);
// Later, for each request:
const best1 = negotiate('application/json');
const best2 = negotiate('text/*;q=0.9, application/json;q=0.8');Permissive mode
Pass the optional permissive flag to tolerate non-RFC-compliant inputs
(extra whitespace, empty parameter values, flag parameters like ;foo, and
truncated quoted values at EOF):
const best = negotiate(acceptHeader, true); // permissive parsingCompatibility with negotiator
An API that emulates the negotiator
package is available via the @apeleghq/http-media-type-negotiator/compat/Negotiator
export.
The mediaType and mediaTypes interfaces are exposed, although they don't
behave identically to those in negotiator:
mediaTypeswill return at most a single media type if theavailableMediaTypesparameter is provided.- The negotiation algorithm may produce different results.
⚙️ Behaviour notes
- Returned strings are the original server-provided strings from the
availableMediaTypesarray. - Q-values are parsed and converted to integer weights between 0 and
1000 (1.0 → 1000). Entries with
q=0are ignored. - Matching supports exact
type/subtype, type with wildcard subtype (e.g.text/*), and*/*. - When multiple acceptable ranges tie on
q, the negotiator prefers:- More specific type/subtype (non-
*), - Media-range that shares the most non-q parameters with the available type,
- Server order (the order of available types provided).
- More specific type/subtype (non-
- The parser returns substrings for
Acceptentries and reparses them into structured media types internally; this keeps the implementation allocation-light. - Parameter values are not normalised and evaluated as exact matches (meaning case-sensitively).
✅ Recommended usage pattern
- If negotiating repeatedly for the same server-supported types, use
negotiateMediaTypeFactoryonce and reuse the returned function to avoid reparsing available types. - Normalise case if you need canonical string comparisons beyond what the negotiator provides.
🤝 Contributing
Contributions welcome. Please open issues or pull requests on the repository. Consider adding unit tests for edge cases and performance benchmarks if you change parsing behaviour.
📜 License
This project is released under the ISC license. See the LICENSE file
for details.