Skip to main content

Guide

cookie vs tough-cookie vs set-cookie-parser 2026

Compare cookie, tough-cookie, and set-cookie-parser for HTTP cookie handling in Node.js. Cookie jar management, Set-Cookie parsing, security best practices.

·PkgPulse Team·
0

TL;DR

cookie is the basic cookie parser/serializer — parses Cookie headers into objects, serializes name/value pairs with options (maxAge, path, secure), used by Express internally. tough-cookie is the full cookie jar implementation — stores cookies across requests, handles domain matching, path scoping, expiration, Set-Cookie parsing, used by got, axios, and testing tools. set-cookie-parser parses Set-Cookie response headers — extracts cookies from HTTP responses, handles multiple Set-Cookie headers, works with fetch and undici. In 2026: cookie for basic parsing/serializing, tough-cookie for cookie jar management (crawlers, testing), set-cookie-parser for reading Set-Cookie from responses.

Key Takeaways

  • cookie: ~30M weekly downloads — basic parse/serialize, used by Express, zero deps
  • tough-cookie: ~15M weekly downloads — full cookie jar, domain/path matching, RFC 6265
  • set-cookie-parser: ~5M weekly downloads — parses Set-Cookie headers from responses
  • Different scopes: cookie does parse/serialize, tough-cookie manages state, set-cookie-parser reads headers
  • cookie is the lowest level — just string manipulation
  • tough-cookie implements the full browser cookie algorithm

cookie — basic cookie parsing:

Parse cookies

import cookie from "cookie"

// Parse a Cookie header:
const cookies = cookie.parse("session=abc123; theme=dark; lang=en")
// → { session: "abc123", theme: "dark", lang: "en" }

// In an Express/Node.js handler:
function handler(req, res) {
  const cookies = cookie.parse(req.headers.cookie || "")
  const session = cookies.session
  const theme = cookies.theme ?? "light"
}

Serialize cookies

import cookie from "cookie"

// Create a Set-Cookie header value:
cookie.serialize("session", "abc123", {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 60 * 60 * 24 * 7,  // 7 days
  path: "/",
})
// → "session=abc123; Max-Age=604800; Path=/; HttpOnly; Secure; SameSite=Strict"

// Multiple cookies — set multiple headers:
const sessionCookie = cookie.serialize("session", "abc123", { httpOnly: true })
const themeCookie = cookie.serialize("theme", "dark", { maxAge: 31536000 })

res.setHeader("Set-Cookie", [sessionCookie, themeCookie])
import cookie from "cookie"

// All serialization options:
cookie.serialize("name", "value", {
  domain: ".pkgpulse.com",     // Domain scope
  path: "/api",                 // Path scope
  maxAge: 86400,                // Seconds until expiry
  expires: new Date("2026-12-31"),  // Absolute expiry
  httpOnly: true,               // No JavaScript access
  secure: true,                 // HTTPS only
  sameSite: "lax",              // CSRF protection: "strict" | "lax" | "none"
  priority: "high",             // "low" | "medium" | "high"
  partitioned: true,            // CHIPS (partitioned cookies)
})

tough-cookie — full cookie jar:

import { CookieJar, Cookie } from "tough-cookie"

// Create a cookie jar:
const jar = new CookieJar()

// Store cookies:
await jar.setCookie("session=abc123; Path=/; HttpOnly", "https://pkgpulse.com")
await jar.setCookie("theme=dark; Path=/; Max-Age=31536000", "https://pkgpulse.com")

// Retrieve cookies for a URL:
const cookies = await jar.getCookies("https://pkgpulse.com/api/packages")
// → [Cookie { key: "session", value: "abc123" }, Cookie { key: "theme", value: "dark" }]

// Get as Cookie header string:
const cookieString = await jar.getCookieString("https://pkgpulse.com/api/packages")
// → "session=abc123; theme=dark"

Domain and path matching

import { CookieJar } from "tough-cookie"

const jar = new CookieJar()

// Domain-scoped cookie:
await jar.setCookie(
  "tracking=xyz; Domain=.pkgpulse.com; Path=/",
  "https://www.pkgpulse.com"
)

// Available on subdomains:
await jar.getCookieString("https://api.pkgpulse.com")
// → "tracking=xyz"

await jar.getCookieString("https://pkgpulse.com")
// → "tracking=xyz"

// NOT available on different domain:
await jar.getCookieString("https://other-site.com")
// → ""

// Path-scoped:
await jar.setCookie("admin=true; Path=/admin", "https://pkgpulse.com")

await jar.getCookieString("https://pkgpulse.com/admin/users")
// → "tracking=xyz; admin=true"

await jar.getCookieString("https://pkgpulse.com/api")
// → "tracking=xyz"  (admin cookie not included)

With HTTP clients

import { CookieJar } from "tough-cookie"
import got from "got"

// Use with got:
const jar = new CookieJar()

// Login — jar stores the session cookie:
await got.post("https://api.pkgpulse.com/auth/login", {
  json: { email: "user@example.com", password: "..." },
  cookieJar: jar,
})

// Subsequent requests include the cookie automatically:
const response = await got("https://api.pkgpulse.com/api/me", {
  cookieJar: jar,
})
// Session cookie sent automatically
import { CookieJar, Cookie } from "tough-cookie"

const jar = new CookieJar()
await jar.setCookie(
  "session=abc; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600",
  "https://pkgpulse.com"
)

// Inspect stored cookies:
const cookies = await jar.getCookies("https://pkgpulse.com")
const session = cookies[0]

session.key           // "session"
session.value         // "abc"
session.domain        // "pkgpulse.com"
session.path          // "/"
session.httpOnly      // true
session.secure        // true
session.sameSite      // "strict"
session.TTL()         // Time to live in ms
session.expiryDate()  // Date object

set-cookie-parser — parse Set-Cookie headers:

import setCookieParser from "set-cookie-parser"

// Parse Set-Cookie header(s):
const cookies = setCookieParser.parse([
  "session=abc123; Path=/; HttpOnly; Secure; SameSite=Strict",
  "theme=dark; Path=/; Max-Age=31536000",
])

// → [
//   { name: "session", value: "abc123", path: "/", httpOnly: true, secure: true, sameSite: "Strict" },
//   { name: "theme", value: "dark", path: "/", maxAge: 31536000 },
// ]

With fetch responses

import setCookieParser from "set-cookie-parser"

// Parse cookies from a fetch response:
const response = await fetch("https://api.pkgpulse.com/auth/login", {
  method: "POST",
  body: JSON.stringify({ email: "user@example.com" }),
})

// Get Set-Cookie headers:
const setCookieHeaders = response.headers.getSetCookie()
// → ["session=abc; Path=/; HttpOnly", "csrf=xyz; Path=/"]

const cookies = setCookieParser.parse(setCookieHeaders)
// → [{ name: "session", value: "abc", ... }, { name: "csrf", value: "xyz", ... }]

With undici/Node.js fetch

import setCookieParser from "set-cookie-parser"

// Node.js fetch (undici) returns combined Set-Cookie:
const response = await fetch("https://api.example.com/login", {
  method: "POST",
  body: JSON.stringify({ user: "test" }),
})

// Split combined header:
const combinedHeader = response.headers.get("set-cookie")
const cookies = setCookieParser.splitCookiesString(combinedHeader)
// → ["session=abc; HttpOnly", "theme=dark; Max-Age=86400"]

const parsed = setCookieParser.parse(cookies)

Map format

import setCookieParser from "set-cookie-parser"

// Parse as a map (keyed by cookie name):
const cookieMap = setCookieParser.parse(setCookieHeaders, {
  map: true,
})

// → {
//   session: { name: "session", value: "abc", httpOnly: true, ... },
//   theme: { name: "theme", value: "dark", maxAge: 31536000, ... },
// }

// Easy lookup:
const sessionValue = cookieMap.session?.value

Feature Comparison

Featurecookietough-cookieset-cookie-parser
Parse Cookie header
Serialize Set-Cookie
Parse Set-Cookie
Cookie jar
Domain matching
Path scoping
Expiration tracking
RFC 6265 compliantPartial
HTTP client integration✅ (got, axios)
TypeScript
Weekly downloads~30M~15M~5M

When to Use Each

Use cookie if:

  • Parsing Cookie request headers in Express/Node.js middleware
  • Serializing Set-Cookie response headers
  • Need basic cookie string manipulation
  • Building simple cookie middleware

Use tough-cookie if:

  • Building a crawler or scraper that needs cookie persistence
  • Testing authenticated API flows
  • Need automatic domain/path matching (browser-like behavior)
  • Using with HTTP clients (got, axios) for session management

Use set-cookie-parser if:

  • Reading Set-Cookie headers from fetch/undici responses
  • Need to extract cookies from HTTP responses
  • Building a proxy that needs to inspect Set-Cookie headers
  • Working with multiple Set-Cookie headers

Cookies appear deceptively simple — they're just key-value pairs stored in the browser. But the HTTP cookie specification is surprisingly complex, with a decade of extensions layered on top of the original design. Domains, paths, the Secure flag, the HttpOnly flag, SameSite attributes, expiration semantics, the distinction between session cookies and persistent cookies, and the public suffix list rules for domain matching all interact in ways that create subtle bugs when handled incorrectly.

Node.js provides no built-in cookie parsing: the raw Cookie request header is a single semicolon-separated string like session=abc123; theme=dark; lang=en, and responses set cookies by writing one or more Set-Cookie headers, each as a separate header entry. This raw format is what your application receives, and turning it into something useful requires string manipulation that, while not technically difficult, has enough edge cases (URL encoding, whitespace, attribute parsing) to warrant a well-tested library.

The three libraries solve different parts of the cookie problem. The cookie package is string manipulation — it parses the request Cookie header into a plain object and serializes name-value pairs with their attributes into the format needed for a Set-Cookie header. It does nothing else: no storage, no state, no domain matching. The tough-cookie package is state management — it implements a full cookie jar that stores cookies, matches them to requests by domain and path, handles expiration, and sends the right cookies on subsequent requests exactly the way a browser would. The set-cookie-parser package specifically addresses the response side — parsing the structured Set-Cookie headers that a server sends back into usable objects.

Most Express applications use cookie-parser middleware, which wraps the cookie package internally, for reading request cookies. Setting response cookies goes through res.cookie(), which also uses cookie.serialize() under the hood. If you never write a web scraper or integration test that maintains session state across requests, you may never need tough-cookie at all.

Cookie security is a layered problem that requires getting multiple independent settings right. Missing any one of them creates a vulnerability, but no single library enforces all of them automatically — correct configuration is always the developer's responsibility.

The four most common production cookie security failures are: missing HttpOnly on session cookies (which exposes the session token to any JavaScript running on the page, including injected XSS payloads), missing Secure in production (which sends the session cookie over plain HTTP connections, visible to anyone who can observe the network), incorrect SameSite configuration (which can allow cross-site request forgery attacks), and insufficient scoping via Domain (where setting Domain=.example.com when Domain=app.example.com would suffice exposes session cookies to other subdomains you may not control).

The cookie library's serialize() function provides all the right options — httpOnly, secure, sameSite, domain, path — but does not enforce any defaults. You must set them explicitly. A session cookie serialized without httpOnly: true and secure: true will work correctly in development but is unsafe in production. The right practice is to define a sessionCookieOptions constant with all security flags set and reuse it everywhere sessions are issued.

The sameSite option deserves special attention. Use "strict" for session cookies and anything sensitive — this prevents the cookie from being sent on any cross-site request, including navigations. Use "lax" for cookies needed for top-level navigation, like "remember me" tokens that must survive a user clicking a link from another site. Use "none" only when you intentionally need third-party cookie behavior (like embedding your site in an iframe), and only with secure: true. Tough-cookie implements the full public suffix list for domain matching, which is the same algorithm browsers use to prevent Domain=.com from matching all .com sites.

Tough-cookie shines in scenarios where you need to maintain cookie state across multiple HTTP requests in a way that mirrors what a browser would do. The two most common cases are web scraping and integration testing of authenticated flows.

For web scraping with Node.js HTTP clients like got or axios, attaching a CookieJar instance from tough-cookie means the library automatically captures Set-Cookie headers from every response and sends the appropriate cookies on subsequent requests. You log in once with POST /auth/login, tough-cookie stores the session cookie, and every subsequent request to the same domain includes that session cookie in the headers — exactly as a browser would. Without this, you would need to manually parse every Set-Cookie header, check domain and path rules, track expiration, and thread the cookie value into subsequent request headers yourself. That manual approach is error-prone, doesn't handle domain scoping correctly, and breaks as soon as the server sends multiple cookies or updates a cookie's value mid-session.

For integration testing, tough-cookie lets you write tests that log in once as a test user and carry that session through a sequence of assertions — testing that an authenticated user can access certain pages, that permission checks work correctly, and that logout properly invalidates the session. The alternative of manually extracting cookies from responses and adding them to subsequent request headers works for single-request tests but becomes unwieldy for multi-step authenticated flows. Tough-cookie's CookieJar abstracts the state management so your test code focuses on what it's testing rather than how cookies work.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on cookie v0.7.x, tough-cookie v5.x, and set-cookie-parser v2.x.

Using These Libraries Together in Real Applications

In practice, these three packages are not competitors — they cover different layers of the same problem and are commonly used together. A typical server-side application might use all three: cookie to parse incoming Cookie request headers and serialize Set-Cookie response headers in middleware, set-cookie-parser to parse Set-Cookie headers from upstream API calls, and tough-cookie to manage session state across multiple requests in a crawler or test harness.

Consider a Next.js API route that proxies authentication to an upstream service. The route receives a login request, forwards credentials to the upstream API, then must extract the session cookie from the upstream response and re-set it for the browser client. Here, set-cookie-parser reads the upstream Set-Cookie header, cookie serializes the extracted cookie into a new Set-Cookie header for the browser, and the upstream response's cookie attributes (domain, path, httpOnly) are adjusted to match the proxy's domain. This three-library combination appears in many reverse proxy, BFF (Backend for Frontend), and testing setups.

When writing integration tests that exercise authenticated endpoints, tough-cookie is the correct tool. It handles the full browser-like cookie lifecycle: storing cookies from login responses, scoping them by domain and path, expiring them appropriately, and sending them with subsequent requests. The got HTTP client accepts a CookieJar instance directly, making authenticated test flows straightforward without manually tracking and re-injecting session values between requests.

Security Considerations for Each Library

The cookie package occupies the lowest level — it performs no verification and no security enforcement. It will parse any Cookie header string, including malformed or injected values, and return them as-is. Applications using cookie directly must implement their own HMAC signing or encryption to prevent cookie tampering. Express's cookieParser middleware wraps cookie and adds signature verification via cookie-signature when a secret is provided.

tough-cookie respects the RFC 6265 security model by default: it enforces SameSite semantics, respects HttpOnly (preventing JavaScript access in browser emulation), and handles cookie expiration and domain-scoping rules. Its allowSpecialUseDomain option (defaulting to false) prevents cookies from being set on top-level domains like .com. When using tough-cookie in a web scraper or test harness, the security boundary is different — the "client" is your own code, so HttpOnly and SameSite restrictions are advisory rather than enforced. The cookie jar stores all cookies regardless of HttpOnly, which is intentional for automated use cases.

set-cookie-parser is a parsing-only library with no security implications of its own — it does not store, transmit, or validate cookies. The security concern when using it is downstream: once you have parsed a Set-Cookie header into a structured object, your code is responsible for deciding which attributes to trust, which cookies to forward, and how to prevent header injection if any parsed cookie values are re-serialized and set on a downstream response.

Compare cookie handling and HTTP tooling on PkgPulse →

When to Use Each

Use the cookie package if:

  • You are building a Node.js HTTP server and need to parse Cookie headers and serialize Set-Cookie values
  • You want the minimal, dependency-free package used by Express for cookie handling
  • You need RFC 6265-compliant cookie serialization with options like HttpOnly, Secure, SameSite, MaxAge

Use tough-cookie if:

  • You are building an HTTP client that needs to store and send cookies like a browser
  • You need a full cookie jar implementation: persistence, domain/path matching, expiry
  • You are writing test utilities or scrapers that need to maintain session cookies across requests

Use set-cookie-parser if:

  • You need to parse the Set-Cookie response header (not the request Cookie header)
  • You want to extract individual cookie attributes from a complex Set-Cookie string
  • You are building an HTTP client interceptor or proxy that inspects response cookies

The three packages solve different parts of the cookie lifecycle. cookie handles request/response cookie formatting. tough-cookie handles cookie storage and automatic cookie sending (like a browser). set-cookie-parser is a specialized parser for the response side. In most server-side applications, only cookie is needed. In HTTP client applications that need session persistence, tough-cookie is required.

A security note applicable to all three: always set HttpOnly and Secure flags on session cookies, and use SameSite=Strict or SameSite=Lax to mitigate CSRF. The cookie package supports all of these flags in its serialize options.

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on cookie v0.7.x, tough-cookie v5.x, and set-cookie-parser v2.x. The cookie package is maintained by the Express team and ships with Express 4/5. tough-cookie implements the full RFC 6265 cookie specification including third-party cookie policies. The split between cookie (request formatting) and set-cookie-parser (response parsing) exists because the Cookie request header and the Set-Cookie response header have different formats: request cookies are a semicolon-separated flat list, while response Set-Cookie headers are one header per cookie with full attribute syntax.

See also: pm2 vs node:cluster vs tsx watch and h3 vs polka vs koa 2026, better-sqlite3 vs libsql vs sql.js.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.