js-cookie vs cookie-parser vs cookies vs universal-cookie vs react-cookie vs cookie
Architectural Strategies for Cookie Management in Full-Stack JavaScript
js-cookiecookie-parsercookiesuniversal-cookiereact-cookiecookieSimilar Packages:

Architectural Strategies for Cookie Management in Full-Stack JavaScript

This comparison evaluates six critical npm packages used for handling HTTP cookies across different layers of the JavaScript stack. The selection spans from low-level server utilities (cookie, cookie-parser, cookies) designed for Node.js backends, to client-side specialists (js-cookie) for browser-only environments, and finally to React-integrated solutions (react-cookie, universal-cookie) that bridge the gap between server and client rendering. Understanding the distinct roles of these libraries is essential for architects building secure, performant, and maintainable full-stack applications.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
js-cookie31,537,96422,56925.9 kB104 months agoMIT
cookie-parser11,786,5252,03113 kB342 years agoMIT
cookies8,070,5591,29619.7 kB35a month agoMIT
universal-cookie2,249,72321563.7 kB55 months agoMIT
react-cookie1,031,54521571.7 kB55 months agoMIT
cookie01,53560.8 kB143 months agoMIT

Architectural Strategies for Cookie Management in Full-Stack JavaScript

Managing HTTP cookies is a deceptively complex task in modern web development. While browsers handle the storage, the JavaScript ecosystem offers a fragmented landscape of tools depending on where your code runs: the server (Node.js), the browser, or both (Universal/Isomorphic). Choosing the wrong tool can lead to security vulnerabilities, hydration mismatches, or unnecessary bundle bloat.

This analysis breaks down six key packages, categorizing them by their runtime environment and architectural intent.

🖥️ Server-Side Primitives: The Node.js Foundation

When working on the server, you are dealing with raw HTTP headers. The goal here is efficiency, security, and framework integration.

1. cookie: The Low-Level Utility

cookie is the atomic unit of cookie handling in Node.js. It does not interact with frameworks; it simply parses a Cookie header string into an object or serializes an object into a Set-Cookie header string.

Use this when you are writing custom middleware, working with serverless functions (like AWS Lambda) where you manage headers manually, or when you want zero dependencies.

// Server-side (Node.js)
const cookie = require('cookie');

// Parsing incoming header
const cookies = cookie.parse(req.headers.cookie || '');
const sessionId = cookies.session_id;

// Setting outgoing header
const setCookie = cookie.serialize('session_id', 'abc123', {
  httpOnly: true,
  maxAge: 60 * 60 * 24 * 7 // 1 week
});
res.setHeader('Set-Cookie', setCookie);

2. cookie-parser: The Express Standard

cookie-parser is middleware specifically designed for Express.js. It wraps the low-level cookie package to automatically populate req.cookies and req.signedCookies.

It simplifies development by removing the need to manually parse headers in every route handler. It also supports signed cookies, which ensures the cookie hasn't been tampered with, provided you share a secret key.

// Server-side (Express.js)
const cookieParser = require('cookie-parser');
const app = require('express')();

// Initialize with a secret for signed cookies
app.use(cookieParser('my-secret-key'));

app.get('/profile', (req, res) => {
  // Automatically available on the request object
  const userId = req.cookies.user_id; 
  const signedData = req.signedCookies.trust_token;
  
  res.json({ userId, signedData });
});

3. cookies: The Koa Powerhouse

cookies is the default cookie handler for the Koa framework, though it can be used elsewhere. Its standout feature is robust support for signed and encrypted cookies with key rotation.

Unlike cookie-parser, which signs the entire cookie value, cookies allows you to sign specific keys and handles the complexity of rotating signing keys without invalidating existing user sessions. This is critical for enterprise applications with strict security compliance.

// Server-side (Koa or custom)
const Cookies = require('cookies');

app.use((ctx, next) => {
  const cookies = new Cookies(ctx, ctx.request);
  
  // Get a signed cookie
  const token = cookies.get('session', { signed: true });
  
  // Set a signed cookie with automatic key rotation support
  cookies.set('session', 'xyz789', {
    signed: true,
    httpOnly: true,
    overwrite: true
  });
  
  return next();
});

🌐 Client-Side Specialists: Browser Only

When code runs only in the browser, server dependencies like httpOnly flags become irrelevant to the API, and DOM access is guaranteed.

4. js-cookie: The Browser Native

js-cookie is a lightweight library designed strictly for the browser. It provides a simple API to handle cookies, abstracting away the messy document.cookie string manipulation.

It is perfect for Single Page Applications (SPAs) that do not use Server-Side Rendering (SSR). Do not use this in Node.js environments, as it relies on the global document object.

// Client-side (Browser only)
import Cookies from 'js-cookie';

// Setting a cookie
Cookies.set('theme', 'dark', { expires: 7, path: '/' });

// Getting a cookie
const theme = Cookies.get('theme');

// Removing a cookie
Cookies.remove('theme');

⚛️ React Integration: Bridging the Gap

React applications often need to access cookies during rendering. This creates a challenge: how do you access cookies on the server (during SSR) and the client (after hydration) using the same code?

5. react-cookie: The Client-Side Hook

react-cookie exposes cookies as React state using hooks like useCookies. It is designed primarily for client-side React applications.

While it offers a great developer experience for CSR apps, using it in an SSR context requires careful setup to avoid hydration warnings, as the server render might not match the initial client state if cookies are accessed directly in the component body without a provider.

// Client-side React (CSR focus)
import { useCookies } from 'react-cookie';

function UserProfile() {
  const [cookies, setCookie, removeCookie] = useCookies(['user_id']);
  const userId = cookies.user_id;

  const login = () => {
    setCookie('user_id', '12345', { path: '/' });
  };

  return <div>Logged in as: {userId}</div>;
}

6. universal-cookie: The Isomorphic Standard

universal-cookie is the engine behind react-cookie but can be used standalone. It is architected to work identically in Node.js and the browser.

This is the correct choice for Next.js, Remix, or custom SSR setups. It allows you to read cookies during server-side data fetching (getServerSideProps or loaders) and then access the same values in React components without code duplication. It prevents the "window is not defined" errors common when mixing server and client code.

// Universal (Server & Client)
import Cookies from 'universal-cookie';

// Instantiate (works in Node or Browser)
const cookies = new Cookies();

// In a Server Component or Loader
export async function getServerSideProps() {
  const token = cookies.get('auth_token'); // Reads from req.headers on server
  return { props: { token } };
}

// In a Client Component
function Dashboard() {
  const token = cookies.get('auth_token'); // Reads from document.cookie in browser
  return <div>Token exists: {!!token}</div>;
}

🛡️ Security and Architecture Patterns

The choice of library directly impacts your security posture and application architecture.

Handling HttpOnly Cookies

Security best practices dictate that session tokens should be HttpOnly to prevent XSS attacks. This means JavaScript cannot read them.

  • js-cookie, react-cookie, and universal-cookie can set HttpOnly cookies, but they cannot read them. If you try to get() an HttpOnly cookie using these libraries, it will return undefined.
  • Server-side libraries (cookie, cookie-parser, cookies) can read and write HttpOnly cookies because they operate on the HTTP headers, not the browser's JavaScript context.
// Correct Pattern for Secure Sessions
// 1. Server sets HttpOnly cookie (using cookie-parser or cookies)
res.cookie('session', 'secret', { httpOnly: true });

// 2. Client cannot read it (Security Feature)
// const session = Cookies.get('session'); // Returns undefined

// 3. Client sends it automatically with requests
// fetch('/api/data') -> Browser includes 'Cookie: session=secret' automatically

The SSR Hydration Trap

A common architectural mistake is using js-cookie inside a Next.js page component. Since js-cookie crashes on the server, developers often wrap it in useEffect. This causes a hydration mismatch: the server renders one state (no cookie), and the client renders another (cookie found), causing a flash of incorrect content.

universal-cookie solves this by allowing you to read the cookie synchronously on the server during the initial render, ensuring the HTML sent to the client already contains the correct data.

// ❌ Bad: Hydration Mismatch Risk
function MyPage() {
  const [user, setUser] = useState(null);
  useEffect(() => {
    // Only runs in browser, causing server/client mismatch
    setUser(Cookies.get('user')); 
  }, []);
  return <div>{user ? 'Hello' : 'Loading'}</div>; // Flashes "Loading" on load
}

// ✅ Good: Universal Access
import Cookies from 'universal-cookie';
const cookies = new Cookies();

function MyPage() {
  // Works on server and client immediately
  const user = cookies.get('user'); 
  return <div>{user ? 'Hello' : 'Guest'}</div>; // Consistent render
}

📊 Summary: Selection Matrix

Featurecookiecookie-parsercookiesjs-cookiereact-cookieuniversal-cookie
RuntimeNode.jsNode.js (Express)Node.js (Koa)BrowserReact (Client)Universal (ISO)
Primary UseManual Header LogicExpress MiddlewareSigned/Encrypted CookiesSimple Browser APIReact Hooks (CSR)SSR/CSR Consistency
Signed CookiesNoYesYes (Rotatable)NoVia underlying libVia underlying lib
React HooksNoNoNoNoYesNo (Library only)
SSR SafeYesYesYesNoPartial*Yes

react-cookie requires a Provider and careful configuration to be SSR safe; universal-cookie is the safer underlying primitive.

💡 Final Architectural Recommendation

Your choice should follow the boundary of your application:

  1. For Backend APIs (Node/Express/Koa): Stick to cookie-parser (Express) or cookies (Koa). They are optimized for server throughput and security features like signing. Use cookie only if you are building a custom server without a framework.
  2. For Browser-Only SPAs: Use js-cookie. It is small, reliable, and purpose-built for the DOM.
  3. For Full-Stack React (Next.js/Remix): Use universal-cookie. It is the only tool in this list that guarantees consistent behavior across the server-client boundary, preventing hydration errors and allowing secure data fetching patterns. Avoid js-cookie in these projects entirely to prevent runtime crashes on the server.

How to Choose: js-cookie vs cookie-parser vs cookies vs universal-cookie vs react-cookie vs cookie

  • js-cookie:

    Use js-cookie exclusively for client-side code running in the browser when you need a simple, readable API to get, set, and remove cookies. It is the best fit for SPAs or static sites that do not require server-side rendering (SSR) and need to avoid the complexity of server-only dependencies.

  • cookie-parser:

    Select cookie-parser if you are building a standard Express.js application and want automatic population of req.cookies and req.signedCookies. It is the conventional choice for legacy and current Express projects where simplicity and convention over configuration are prioritized for session management.

  • cookies:

    Opt for cookies when working with Koa or when you need robust support for signed and encrypted cookies with automatic key rotation. This package is superior for high-security applications requiring strict integrity checks and seamless integration with the Koa middleware context.

  • universal-cookie:

    Select universal-cookie when building Isomorphic or Universal React applications (using Next.js, Remix, or custom SSR setups) that require a single API to access cookies on both the server and the client. It is the architectural standard for ensuring consistent cookie access during hydration and server-side data fetching.

  • react-cookie:

    Choose react-cookie if your project is a client-side React application (CSR) and you prefer managing cookie state via React Hooks and Context. It is suitable for dashboards or user portals where SSR is not a requirement, allowing you to treat cookies as reactive state within your component tree.

  • cookie:

    Choose cookie when you need a lightweight, dependency-free utility to manually parse or serialize cookie strings in a Node.js environment. It is ideal for custom middleware, serverless functions, or scenarios where you require full control over the HTTP header logic without the overhead of a framework-specific wrapper.

README for js-cookie

JavaScript Cookie CI npm size jsDelivr Hits

A simple, lightweight JavaScript API for handling cookies, client-side.

👉👉 If you're viewing this at https://github.com/js-cookie/js-cookie, you're reading the documentation for the main branch. View documentation for the latest release. 👈👈

Installation

NPM

npm i js-cookie

The npm package has a module field pointing to an ES module variant of the library, mainly to provide support for ES module aware bundlers, whereas its browser field points to an UMD module for full backward compatibility.

Not all browsers support ES modules natively yet. For this reason the npm package/release provides both the ES and UMD module variant and you may want to include the ES module along with the UMD fallback to account for this:

CDN

Alternatively, include js-cookie via jsDelivr CDN.

Basic Usage

Import the library:

import Cookies from 'js-cookie'
// or
const Cookies = require('js-cookie')

Create a cookie, valid across the entire site:

Cookies.set('name', 'value')

Create a cookie that expires 7 days from now, valid across the entire site:

Cookies.set('name', 'value', { expires: 7 })

Create an expiring cookie, valid to the path of the current page:

Cookies.set('name', 'value', { expires: 7, path: '' })

Read cookie:

Cookies.get('name') // => 'value'
Cookies.get('nothing') // => undefined

Read all visible cookies:

Cookies.get() // => { name: 'value' }

Note: It is not possible to read a particular cookie by passing one of the cookie attributes (which may or may not have been used when writing the cookie in question):

Cookies.get('foo', { domain: 'sub.example.com' }) // `domain` won't have any effect...!

The cookie with the name foo will only be available on .get() if it's visible from where the code is called; the domain and/or path attribute will not have an effect when reading.

Delete cookie:

Cookies.remove('name')

Delete a cookie valid to the path of the current page:

Cookies.set('name', 'value', { path: '' })
Cookies.remove('name') // fail!
Cookies.remove('name', { path: '' }) // removed!

IMPORTANT! When deleting a cookie and you're not relying on the default attributes, you must pass the exact same path, domain, secure and sameSite attributes that were used to set the cookie:

Cookies.remove('name', { path: '', domain: '.yourdomain.com', secure: true })

Note: Removing a nonexistent cookie neither raises any exception nor returns any value.

Namespace conflicts

If there is any danger of a conflict with the namespace Cookies, the noConflict method will allow you to define a new namespace and preserve the original one. This is especially useful when running the script on third party sites e.g. as part of a widget or SDK.

// Assign the js-cookie api to a different variable and restore the original "window.Cookies"
var Cookies2 = Cookies.noConflict()
Cookies2.set('name', 'value')

Note: The .noConflict method is not necessary when using AMD or CommonJS, thus it is not exposed in those environments.

Encoding

This project is RFC 6265 compliant. All special characters that are not allowed in the cookie-name or cookie-value are encoded with each one's UTF-8 Hex equivalent using percent-encoding. The only character in cookie-name or cookie-value that is allowed and still encoded is the percent % character, it is escaped in order to interpret percent input as literal. Please note that the default encoding/decoding strategy is meant to be interoperable only between cookies that are read/written by js-cookie. To override the default encoding/decoding strategy you need to use a converter.

Note: According to RFC 6265, your cookies may get deleted if they are too big or there are too many cookies in the same domain, more details here.

Cookie Attributes

Cookie attribute defaults can be set globally by creating an instance of the api via withAttributes(), or individually for each call to Cookies.set(...) by passing a plain object as the last argument. Per-call attributes override the default attributes.

expires

Define when the cookie will be removed. Value must be a Number which will be interpreted as days from time of creation or a Date instance. If omitted, the cookie becomes a session cookie.

To create a cookie that expires in less than a day, you can check the FAQ on the Wiki.

Default: Cookie is removed when the user closes the browser.

Examples:

Cookies.set('name', 'value', { expires: 365 })
Cookies.get('name') // => 'value'
Cookies.remove('name')

path

A String indicating the path where the cookie is visible.

Default: /

Examples:

Cookies.set('name', 'value', { path: '' })
Cookies.get('name') // => 'value'
Cookies.remove('name', { path: '' })

Note regarding Internet Explorer:

Due to an obscure bug in the underlying WinINET InternetGetCookie implementation, IE’s document.cookie will not return a cookie if it was set with a path attribute containing a filename.

(From Internet Explorer Cookie Internals (FAQ))

This means one cannot set a path using window.location.pathname in case such pathname contains a filename like so: /check.html (or at least, such cookie cannot be read correctly).

In fact, you should never allow untrusted input to set the cookie attributes or you might be exposed to a XSS attack.

domain

A String indicating a valid domain where the cookie should be visible. The cookie will also be visible to all subdomains.

Default: Cookie is visible only to the domain or subdomain of the page where the cookie was created, except for Internet Explorer (see below).

Examples:

Assuming a cookie that is being created on site.com:

Cookies.set('name', 'value', { domain: 'subdomain.site.com' })
Cookies.get('name') // => undefined (need to read at 'subdomain.site.com')

Note regarding Internet Explorer default behavior:

Q3: If I don’t specify a DOMAIN attribute (for) a cookie, IE sends it to all nested subdomains anyway? A: Yes, a cookie set on example.com will be sent to sub2.sub1.example.com. Internet Explorer differs from other browsers in this regard.

(From Internet Explorer Cookie Internals (FAQ))

This means that if you omit the domain attribute, it will be visible for a subdomain in IE.

secure

Either true or false, indicating if the cookie transmission requires a secure protocol (https).

Default: No secure protocol requirement.

Examples:

Cookies.set('name', 'value', { secure: true })
Cookies.get('name') // => 'value'
Cookies.remove('name')

partitioned

Either true or false, indicating if the cookie opts into partitioned storage. Must also set secure: true.

Default: No partitioned storage.

Examples:

Cookies.set('name', 'value', { partitioned: true })
Cookies.get('name') // => 'value'
Cookies.remove('name')

sameSite

A String, allowing to control whether the browser is sending a cookie along with cross-site requests.

Default: not set.

Note that more recent browsers are making "Lax" the default value even without specifying anything here.

Examples:

Cookies.set('name', 'value', { sameSite: 'strict' })
Cookies.get('name') // => 'value'
Cookies.remove('name')

Setting up defaults

const api = Cookies.withAttributes({ path: '/', domain: '.example.com' })

Converters

Read

Create a new instance of the api that overrides the default decoding implementation. All get methods that rely in a proper decoding to work, such as Cookies.get() and Cookies.get('name'), will run the given converter for each cookie. The returned value will be used as the cookie value.

Example from reading one of the cookies that can only be decoded using the escape function:

document.cookie = 'escaped=%u5317'
document.cookie = 'default=%E5%8C%97'
var cookies = Cookies.withConverter({
  read: function (value, name) {
    if (name === 'escaped') {
      return unescape(value)
    }
    // Fall back to default for all other cookies
    return Cookies.converter.read(value, name)
  }
})
cookies.get('escaped') // 北
cookies.get('default') // 北
cookies.get() // { escaped: '北', default: '北' }

Write

Create a new instance of the api that overrides the default encoding implementation:

Cookies.withConverter({
  write: function (value, name) {
    return value.toUpperCase()
  }
})

TypeScript declarations

npm i @types/js-cookie

Server-side integration

Check out the Servers Docs

Contributing

Check out the Contributing Guidelines

Releasing

Releasing should be done via the Release GitHub Actions workflow, so that published packages on npmjs.com have package provenance.

GitHub releases are created as a draft and need to be published manually! (This is so we are able to craft suitable release notes before publishing.)

Supporters

Many thanks to BrowserStack for providing unlimited browser testing free of cost.

Authors