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

@pronghorn/cookie

v0.1.1

Published

Cookie parsing and signed-cookie middleware for Pronghorn. Reads incoming cookies into context.locals.cookies and provides setCookie/clearCookie helpers with HMAC signing support.

Downloads

42

Readme

Cookie 🍪

Cookie is a lightweight, TypeScript-first cookie parsing and signing middleware built as an external plugin for Pronghorn. It reads incoming Cookie headers into context.locals.cookies and exposes setCookie/clearCookie helpers that queue outgoing Set-Cookie headers, with optional HMAC signing to prevent client-side tampering.

Built as a standalone package (@pronghorn/cookie), pairs naturally with createJwtAuth for session-style, stateful auth flows alongside stateless bearer tokens.

Why Cookie

Pronghorn ships JWT auth for stateless bearer tokens, but has no built-in way to read or write cookies, which blocks session IDs, CSRF tokens, and "remember me" flows. Cookie fills that gap without pulling in a heavyweight cookie-jar dependency.

  • Parses the Cookie request header into a flat Record<string, string> on context.locals.cookies.
  • setCookie/clearCookie helpers queue Set-Cookie headers, applied to the response after your handler runs.
  • Optional HMAC-SHA256 signing via node:crypto, verified with a timing-safe comparison to prevent tampering and timing attacks.
  • Zero runtime dependencies beyond Node's built-in crypto module (available in Bun).
  • Single global middleware, no plugin/decorator indirection needed since cookies are inherently per-request.

Installation

bun add @pronghorn/cookie

Requires Bun >=1.3.0 and pronghorn >=0.1.2 as a peer dependency (used for typing the middleware only).

Quick Start

import { createApp } from 'pronghorn'
import { cookieParser } from '@pronghorn/cookie'

const app = createApp()

app.use(cookieParser({ secret: process.env.COOKIE_SECRET ?? 'dev-secret' }))

app.get('/', context => {
  const visits = Number(context.locals.cookies['visits'] ?? '0') + 1
  context.locals.setCookie('visits', String(visits), { signed: true, httpOnly: true, maxAge: 86_400 })
  return context.json({ visits })
})

await app.listen(4000)

Core Concepts

Reading cookies

Once cookieParser is registered globally, every request gets context.locals.cookies, a flat map of cookie name to decoded value. If a secret was provided and a cookie was signed, its value is automatically unsigned and verified before it reaches your handler.

app.get('/profile', context => {
  const sessionId = context.locals.cookies['session']
  if (!sessionId) return context.json({ error: 'No session' }, 401)
  return context.json({ sessionId })
})

Writing cookies

context.locals.setCookie(name, value, options?) queues a Set-Cookie header. Multiple calls in the same request each append their own header, matching how browsers expect multiple cookies to be set.

app.post('/login', context => {
  context.locals.setCookie('session', 'abc123', {
    httpOnly: true,
    secure: true,
    sameSite: 'Lax',
    maxAge: 3600
  })
  return context.json({ loggedIn: true })
})

Clearing cookies

context.locals.clearCookie(name, options?) sets the cookie to an empty value with Max-Age=0, causing the browser to delete it immediately.

app.post('/logout', context => {
  context.locals.clearCookie('session')
  return context.json({ loggedOut: true })
})

Signed cookies

Pass signed: true when writing a cookie to append an HMAC-SHA256 signature, and provide a secret to cookieParser so incoming values are verified and stripped back to their original form automatically. This prevents users from editing cookie values client-side without invalidating the signature.

app.use(cookieParser({ secret: process.env.COOKIE_SECRET! }))

app.post('/cart', context => {
  context.locals.setCookie('cartTotal', '49.99', { signed: true })
  return context.json({ ok: true })
})

app.get('/cart', context => {
  // Already verified and unsigned by cookieParser, tampering returns the raw (invalid) value instead
  const total = context.locals.cookies['cartTotal']
  return context.json({ total })
})

Calling setCookie with signed: true but no secret configured throws immediately, so misconfiguration fails loudly instead of silently shipping unsigned cookies.

Cookie Options

| Option | Type | Default | Description | | ---------- | ----------------------------- | ------- | --------------------------------------------------------- | | maxAge | number | - | Lifetime in seconds; sent as Max-Age | | expires | Date | - | Absolute expiration; sent as Expires | | path | string | '/' | Cookie path scope | | domain | string | - | Cookie domain scope | | secure | boolean | false | Only sent over HTTPS | | httpOnly | boolean | false | Inaccessible to client-side JS | | sameSite | 'Strict' \| 'Lax' \| 'None' | - | Cross-site request behavior | | signed | boolean | false | HMAC-signs the value; requires secret on cookieParser |

These map directly to standard Set-Cookie attributes.

Middleware Options

| Option | Type | Default | Description | | -------- | -------- | ----------- | -------------------------------------------------------------- | | secret | string | undefined | Enables signing/verification for cookies marked signed: true |

API Reference

cookieParser(options?: CookieParserOptions): Middleware - global middleware factory, register via app.use(cookieParser(options)).

| Locals property | Type | Description | | ------------------------------------------------- | ------------------------ | ---------------------------------------------------- | | context.locals.cookies | Record<string, string> | Parsed (and auto-unsigned) incoming cookies | | context.locals.setCookie(name, value, options?) | void | Queues an outgoing Set-Cookie header | | context.locals.clearCookie(name, options?) | void | Queues a Set-Cookie header that deletes the cookie |

Lower-level building blocks are also exported for advanced use outside the middleware:

import { parseCookieHeader, serializeCookie, signValue, unsignValue } from '@pronghorn/cookie'

const cookies = parseCookieHeader(request.headers.get('cookie'))
const header = serializeCookie('theme', 'dark', { maxAge: 604_800 })
const signed = signValue('user-42', 'my-secret')
const original = unsignValue(signed, 'my-secret') // 'user-42', or null if tampered

Architecture

Cookie is split into three small modules, each with a single responsibility.

| Module | Responsibility | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | parser.ts | Parses the Cookie request header, and serializes outgoing cookies into Set-Cookie header values | | signer.ts | HMAC-SHA256 signing/verification using node:crypto, with timingSafeEqual to avoid timing side-channels | | middleware.ts | Wires parsing/signing into context.locals, and rebuilds the response with appended Set-Cookie headers after the handler resolves |

Because a Response returned by Response.json() has immutable headers, the middleware constructs a new Response with a merged Headers object rather than mutating the original, the same pattern used by Pronghorn's built-in cors middleware.

Security Notes

  • Always set httpOnly: true on cookies holding session identifiers or tokens, to block access from client-side JavaScript.
  • Always set secure: true in production so cookies are never sent over plain HTTP.
  • Use signed: true for any cookie whose value influences server-side logic (cart totals, role flags, etc.), unsigned cookies can be freely edited by the client.
  • Prefer sameSite: 'Lax' or 'Strict' to reduce CSRF exposure unless you specifically need cross-site cookie delivery.

License

WTFPL (Do What the Fuck You Want to Public License), see LICENSE for details.