<!-- PkgPulse AI-readable guide source -->
<!-- Canonical: https://www.pkgpulse.com/guides/cookie-vs-tough-cookie-vs-set-cookie-parser-cookie-2026 -->
<!-- Raw Markdown: https://www.pkgpulse.com/guides/cookie-vs-tough-cookie-vs-set-cookie-parser-cookie-2026/raw.md -->
<!-- Source path: content/guides/cookie-vs-tough-cookie-vs-set-cookie-parser-cookie-2026.mdx -->

---
og_image: "/images/guides/cookie-vs-tough-cookie-vs-set-cookie-parser-cookie-2026.webp"
title: "cookie vs tough-cookie vs set-cookie-parser 2026"
description: "Compare cookie, tough-cookie, and set-cookie-parser for HTTP cookie handling in Node.js. Cookie jar management, Set-Cookie parsing, security best practices."
date: "2026-03-09"
author: "PkgPulse Team"
tags: ["nodejs", "typescript", "developer-tools", "api"]
tier: 2
---

## 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

[cookie](https://github.com/jshttp/cookie) — basic cookie parsing:

### Parse cookies

```typescript
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

```typescript
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])
```

### Cookie options

```typescript
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

[tough-cookie](https://github.com/salesforce/tough-cookie) — full cookie jar:

### Cookie jar

```typescript
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

```typescript
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

```typescript
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
```

### Cookie inspection

```typescript
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

[set-cookie-parser](https://github.com/nfriedly/set-cookie-parser) — parse Set-Cookie headers:

### Parse Set-Cookie

```typescript
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

```typescript
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

```typescript
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

```typescript
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

| Feature | cookie | tough-cookie | set-cookie-parser |
|---------|--------|-------------|------------------|
| Parse Cookie header | ✅ | ✅ | ❌ |
| Serialize Set-Cookie | ✅ | ✅ | ❌ |
| Parse Set-Cookie | ❌ | ✅ | ✅ |
| Cookie jar | ❌ | ✅ | ❌ |
| Domain matching | ❌ | ✅ | ❌ |
| Path scoping | ❌ | ✅ | ❌ |
| Expiration tracking | ❌ | ✅ | ❌ |
| RFC 6265 compliant | Partial | ✅ | ✅ |
| 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

---

## The HTTP Cookie Ecosystem

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.

## Security Considerations for Production Cookie Handling

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.

## When You Need tough-cookie: Web Scraping and Integration Testing

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 →](https://www.pkgpulse.com)*

## 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](/guides/pm2-vs-node-cluster-vs-tsx-watch-process-2026) and [h3 vs polka vs koa 2026](/guides/h3-vs-polka-vs-koa-lightweight-http-frameworks-nodejs-2026), [better-sqlite3 vs libsql vs sql.js](/guides/better-sqlite3-vs-libsql-vs-sql-js-sqlite-nodejs-2026).*
