Tired of manually parsing and stringifying HTTP header values in JavaScript? headers supercharges the standard Headers interface, providing a robust toolkit for effortless and type-safe header manipulation.
HTTP headers are packed with critical information—from content negotiation and caching directives to authentication tokens and file metadata. While the native Headers API provides a basic string-based interface, it leaves the complexities of parsing specific header formats (like Accept, Content-Type, or Set-Cookie) entirely up to you.
Features
- Type-Safe Accessors: Interact with complex header values (e.g., media types, quality factors, cookie attributes) through strongly-typed properties and methods, eliminating guesswork and manual parsing.
- Automatic Parsing & Stringification: The library intelligently handles the parsing of raw header strings into structured objects and stringifies your structured data back into spec-compliant header values.
- Fluent Interface: Enjoy a more expressive and developer-friendly API for reading and writing header information.
- Drop-in Enhancement: As a subclass of the standard
Headers object, it can be used anywhere a Headers object is expected, providing progressive enhancement to your existing code.
- Individual Header Utilities: For fine-grained control, use standalone utility classes for specific headers, perfect for scenarios outside of a full
Headers object.
Unlock a more powerful and elegant way to work with HTTP headers in your JavaScript and TypeScript projects!
Installation
npm install @remix-run/headers
Overview
The following should give you a sense of what kinds of things you can do with this library:
import Headers from '@remix-run/headers'
let headers = new Headers()
headers.accept = 'text/html, text/*;q=0.9'
headers.accept.mediaTypes
Object.fromEntries(headers.accept.entries())
headers.accept.accepts('text/html')
headers.accept.accepts('text/plain')
headers.accept.accepts('image/jpeg')
headers.accept.getPreferred(['text/plain', 'text/html'])
headers.accept.set('text/plain', 0.9)
headers.accept.set('text/*', 0.8)
headers.get('Accept')
headers.acceptEncoding = 'gzip, deflate;q=0.8'
headers.acceptEncoding.encodings
Object.fromEntries(headers.acceptEncoding.entries())
headers.acceptEncoding.accepts('gzip')
headers.acceptEncoding.accepts('br')
headers.acceptEncoding.getPreferred(['gzip', 'deflate'])
headers.acceptLanguage = 'en-US, en;q=0.9'
headers.acceptLanguage.languages
Object.fromEntries(headers.acceptLanguage.entries())
headers.acceptLanguage.accepts('en')
headers.acceptLanguage.accepts('ja')
headers.acceptLanguage.getPreferred(['en-US', 'en-GB'])
headers.acceptLanguage.getPreferred(['en', 'fr'])
headers.acceptRanges = 'bytes'
headers.allow = ['GET', 'POST', 'PUT']
headers.get('Allow')
headers.connection = 'close'
headers.contentType = 'application/json; charset=utf-8'
headers.contentType.mediaType
headers.contentType.charset
headers.contentType.charset = 'iso-8859-1'
headers.get('Content-Type')
headers.contentDisposition =
'attachment; filename="example.pdf"; filename*=UTF-8\'\'%E4%BE%8B%E5%AD%90.pdf'
headers.contentDisposition.type
headers.contentDisposition.filename
headers.contentDisposition.filenameSplat
headers.contentDisposition.preferredFilename
headers.cookie = 'session_id=abc123; user_id=12345'
headers.cookie.get('session_id')
headers.cookie.get('user_id')
headers.cookie.set('theme', 'dark')
headers.get('Cookie')
headers.host = 'example.com'
headers.ifMatch = ['67ab43', '54ed21']
headers.get('If-Match')
headers.ifMatch.matches('67ab43')
headers.ifMatch.matches('abc123')
headers.ifNoneMatch = ['67ab43', '54ed21']
headers.get('If-None-Match')
headers.ifNoneMatch.matches('67ab43')
headers.ifNoneMatch.matches('abc123')
headers.ifRange = new Date('2021-01-01T00:00:00Z')
headers.get('If-Range')
headers.ifRange.matches({ lastModified: 1609459200000 })
headers.ifRange.matches({ lastModified: new Date('2021-01-01T00:00:00Z') })
headers.lastModified = new Date('2021-01-01T00:00:00Z')
headers.get('Last-Modified')
headers.location = 'https://example.com'
headers.range = 'bytes=200-1000'
headers.range.unit
headers.range.ranges
headers.range.canSatisfy(2000)
headers.referer = 'https://example.com/'
headers.setCookie = ['session_id=abc123; Path=/; HttpOnly']
headers.setCookie[0].name
headers.setCookie[0].value
headers.setCookie[0].path
headers.setCookie[0].httpOnly
headers.setCookie[0].maxAge = 3600
headers.setCookie[0].secure = true
headers.get('Set-Cookie')
headers.setCookie.push('user_id=12345; Path=/api; Secure')
for (let cookie of headers.getSetCookie()) {
console.log(cookie)
}
Headers can be initialized with an object config:
let headers = new Headers({
contentType: {
mediaType: 'text/html',
charset: 'utf-8',
},
setCookie: [
{ name: 'session', value: 'abc', path: '/' },
{ name: 'theme', value: 'dark', expires: new Date('2021-12-31T23:59:59Z') },
],
})
console.log(`${headers}`)
Headers works just like DOM's Headers (it's a subclass) so you can use them anywhere you need a Headers.
import Headers from '@remix-run/headers'
let response = await fetch('https://example.com', {
headers: new Headers(),
})
let headers = new Headers(response.headers)
headers.set('Content-Type', 'text/html')
headers.get('Content-Type')
If you're familiar with using DOM Headers, everything works as you'd expect.
Headers are iterable:
let headers = new Headers({
'Content-Type': 'application/json',
'X-API-Key': 'secret-key',
'Accept-Language': 'en-US,en;q=0.9',
})
for (let [name, value] of headers) {
console.log(`${name}: ${value}`)
}
If you're assembling HTTP messages, you can easily convert to a multiline string suitable for using as a Request/Response header block:
let headers = new Headers({
'Content-Type': 'application/json',
'Accept-Language': 'en-US,en;q=0.9',
})
console.log(`${headers}`)
In addition to the high-level Headers API, headers also provides a rich set of primitives you can use to work with just about any complex HTTP header value. Each header class includes a spec-compliant parser (the constructor), stringifier (toString), and getters/setters for all relevant attributes. Classes for headers that contain a list of fields, like Cookie, are iterable.
If you need support for a header that isn't listed here, please send a PR! The goal is to have first-class support for all common HTTP headers.
Accept
import { Accept } from '@remix-run/headers'
let header = new Accept('text/html;text/*;q=0.9')
header.has('text/html')
header.has('text/plain')
header.accepts('text/html')
header.accepts('text/plain')
header.accepts('text/*')
header.accepts('image/jpeg')
header.getPreferred(['text/html', 'text/plain'])
for (let [mediaType, quality] of header) {
}
let header = new Accept({ 'text/html': 1, 'text/*': 0.9 })
let header = new Accept(['text/html', ['text/*', 0.9]])
Accept-Encoding
import { AcceptEncoding } from '@remix-run/headers'
let header = new AcceptEncoding('gzip,deflate;q=0.9')
header.has('gzip')
header.has('br')
header.accepts('gzip')
header.accepts('deflate')
header.accepts('identity')
header.accepts('br')
header.getPreferred(['gzip', 'deflate'])
for (let [encoding, weight] of header) {
}
let header = new AcceptEncoding({ gzip: 1, deflate: 0.9 })
let header = new AcceptEncoding(['gzip', ['deflate', 0.9]])
Accept-Language
import { AcceptLanguage } from '@remix-run/headers'
let header = new AcceptLanguage('en-US,en;q=0.9')
header.has('en-US')
header.has('en-GB')
header.accepts('en-US')
header.accepts('en-GB')
header.accepts('en')
header.accepts('fr')
header.getPreferred(['en-US', 'en-GB'])
header.getPreferred(['en', 'fr'])
for (let [language, quality] of header) {
}
let header = new AcceptLanguage({ 'en-US': 1, en: 0.9 })
let header = new AcceptLanguage(['en-US', ['en', 0.9]])
Cache-Control
import { CacheControl } from '@remix-run/headers'
let header = new CacheControl('public, max-age=3600, s-maxage=3600')
header.public
header.maxAge
header.sMaxage
let header = new CacheControl({ public: true, maxAge: 3600 })
header.public
header.private
header.noCache
header.noStore
header.noTransform
header.mustRevalidate
header.proxyRevalidate
header.maxAge
header.sMaxage
header.minFresh
header.maxStale
header.onlyIfCached
header.immutable
header.staleWhileRevalidate
header.staleIfError
Content-Disposition
import { ContentDisposition } from '@remix-run/headers'
let header = new ContentDisposition('attachment; name=file1; filename=file1.txt')
header.type
header.name
header.filename
header.preferredFilename
let header = new ContentDisposition({
type: 'attachment',
name: 'file1',
filename: 'file1.txt',
})
Content-Type
import { ContentType } from '@remix-run/headers'
let header = new ContentType('text/html; charset=utf-8')
header.mediaType
header.boundary
header.charset
let header = new ContentType({
mediaType: 'multipart/form-data',
boundary: '------WebKitFormBoundary12345',
charset: 'utf-8',
})
Content-Range
import { ContentRange } from '@remix-run/headers'
let header = new ContentRange('bytes 200-1000/67589')
header.unit
header.start
header.end
header.size
let header = new ContentRange('bytes */67589')
header.unit
header.start
header.end
header.size
let header = new ContentRange({
unit: 'bytes',
start: 200,
end: 1000,
size: 67589,
})
Cookie
import { Cookie } from '@remix-run/headers'
let header = new Cookie('theme=dark; session_id=123')
header.get('theme')
header.set('theme', 'light')
header.delete('theme')
header.has('session_id')
for (let [name, value] of header) {
}
let header = new Cookie({ theme: 'dark', session_id: '123' })
let header = new Cookie([
['theme', 'dark'],
['session_id', '123'],
])
If-Match
import { IfMatch } from '@remix-run/headers'
let header = new IfMatch('"67ab43", "54ed21"')
header.has('67ab43')
header.has('21ba69')
header.matches('"67ab43"')
header.matches('"abc123"')
let weakHeader = new IfMatch('W/"67ab43"')
weakHeader.matches('W/"67ab43"')
let header = new IfMatch(['67ab43', '54ed21'])
let header = new IfMatch({
tags: ['67ab43', '54ed21'],
})
If-None-Match
import { IfNoneMatch } from '@remix-run/headers'
let header = new IfNoneMatch('"67ab43", "54ed21"')
header.has('67ab43')
header.has('21ba69')
header.matches('"67ab43"')
let header = new IfNoneMatch(['67ab43', '54ed21'])
let header = new IfNoneMatch({
tags: ['67ab43', '54ed21'],
})
If-Range
import { IfRange } from '@remix-run/headers'
let header = new IfRange('Fri, 01 Jan 2021 00:00:00 GMT')
header.matches({ lastModified: 1609459200000 })
header.matches({ lastModified: new Date('2021-01-01T00:00:00Z') })
let header = new IfRange(new Date('2021-01-01T00:00:00Z'))
header.matches({ lastModified: 1609459200000 })
let header = new IfRange('"67ab43"')
header.matches({ etag: '"67ab43"' })
let weakHeader = new IfRange('W/"67ab43"')
header.matches({ etag: 'W/"67ab43"' })
let emptyHeader = new IfRange('')
emptyHeader.matches({ etag: '"67ab43"' })
Range
import { Range } from '@remix-run/headers'
let header = new Range('bytes=200-1000')
header.unit
header.ranges
header.canSatisfy(2000)
header.canSatisfy(500)
let header = new Range('bytes=0-499, 1000-1499')
header.ranges.length
let header = new Range('bytes=1000-')
header.normalize(2000)
let header = new Range({
unit: 'bytes',
ranges: [
{ start: 200, end: 1000 },
{ start: 2000, end: 2999 },
],
})
Set-Cookie
import { SetCookie } from '@remix-run/headers'
let header = new SetCookie('session_id=abc; Domain=example.com; Path=/; Secure; HttpOnly')
header.name
header.value
header.domain
header.path
header.secure
header.httpOnly
header.sameSite
header.maxAge
header.expires
let header = new SetCookie({
name: 'session_id',
value: 'abc',
domain: 'example.com',
path: '/',
secure: true,
httpOnly: true,
})
Related Packages
License
See LICENSE