BunPress Documentation
⌘ K
GuideAPIExamples

Cookie Handling

bun-router provides a simple yet powerful API for working with HTTP cookies. This guide will show you how to read, write, and manage cookies in your web applications.

Reading Cookies

To read cookies from an incoming request:

import { Router } from 'bun-router'

const router = new Router()

router.get('/', (req) => {
  // Get all cookies as an object
  const cookies = req.cookies

  // Access a specific cookie
  const theme = req.cookies.theme

  return new Response(`Your theme preference is: ${theme}`)
})

Setting Cookies

To set cookies in your response:

router.get('/set-theme', (req) => {
  const { theme } = req.query

  // Create a response
  const response = new Response(`Theme set to: ${theme}`)

  // Set a simple cookie
  response.cookie('theme', theme)

  return response
})

Setting Cookies with Options

You can set various cookie options:

router.get('/login', (req) => {
  // Create a response
  const response = new Response('You are logged in')

  // Set a cookie with options
  response.cookie('session', 'abc123', {
    // Cookie lifespan in milliseconds (1 day)
    maxAge: 24 * 60 * 60 * 1000,

    // Or use an exact expiration date
    // expires: new Date(Date.now() + 24 * 60 * 60 * 1000),

    // Restrict cookie to HTTPS connections
    secure: true,

    // Prevent client-side access to cookie
    httpOnly: true,

    // Controls when cookies are sent in cross-site requests
    sameSite: 'lax', // 'strict', 'lax', or 'none'

    // Cookie domain (defaults to current domain)
    domain: 'example.com',

    // Cookie path (defaults to '/')
    path: '/',

    // Priority hint (Chrome-specific)
    priority: 'high' // 'low', 'medium', or 'high'
  })

  return response
})

Deleting Cookies

To delete an existing cookie:

router.get('/logout', (req) => {
  const response = new Response('You are logged out')

  // Clear a cookie
  response.clearCookie('session')

  return response
})

You can also specify the same options as when setting the cookie to ensure it's properly removed:

router.get('/logout', (req) => {
  const response = new Response('You are logged out')

  // Clear a cookie with the same path and domain as when it was set
  response.clearCookie('session', {
    path: '/',
    domain: 'example.com'
  })

  return response
})

For more advanced cookie handling, bun-router provides a cookie parser middleware:

import { cookieParser, Router } from 'bun-router'

const router = new Router()

// Add cookie parser middleware with options
router.use(cookieParser({
  // Secret for signed cookies (optional)
  secret: 'your-secret-key',

  // Parser options
  decode: decodeURIComponent
}))

// Now you can access cookies in all route handlers
router.get('/profile', (req) => {
  // Regular cookies
  const theme = req.cookies.theme

  // Signed cookies (if secret was provided)
  const userId = req.signedCookies.userId

  if (!userId) {
    return new Response('Please log in', { status: 401 })
  }

  return new Response(`User ${userId} is using theme: ${theme}`)
})

Signed Cookies

Signed cookies help verify that a cookie hasn't been tampered with by the client:

import { cookieParser, Router } from 'bun-router'

const router = new Router()

// Add cookie parser middleware with secret
router.use(cookieParser({
  secret: 'your-strong-secret-key'
}))

// Setting a signed cookie
router.post('/login', async (req) => {
  const { username, password } = await req.json()

  // Validate user (example)
  const user = await validateUser(username, password)

  if (!user) {
    return new Response('Invalid credentials', { status: 401 })
  }

  const response = new Response('Logged in successfully')

  // Set a signed cookie
  response.signedCookie('userId', user.id.toString(), {
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000, // 1 day
    secure: process.env.NODE_ENV === 'production'
  })

  return response
})

// Reading a signed cookie
router.get('/profile', (req) => {
  // Access signed cookie
  const userId = req.signedCookies.userId

  if (!userId) {
    return new Response('Please log in', { status: 401 })
  }

  return new Response(`User profile for ID: ${userId}`)
})

To ensure cookie security, follow these best practices:

1. Use HttpOnly for Sensitive Cookies

Set httpOnly: true to prevent JavaScript access to sensitive cookies:

response.cookie('session', token, {
  httpOnly: true
})

2. Set Secure Flag in Production

Restrict cookies to HTTPS connections in production:

response.cookie('session', token, {
  secure: process.env.NODE_ENV === 'production',
  httpOnly: true
})

3. Use Appropriate SameSite Setting

Set sameSite attribute to prevent CSRF attacks:

// Most restrictive - cookie sent only for same-site requests
response.cookie('session', token, {
  sameSite: 'strict',
  httpOnly: true
})

// Balanced approach - cookie sent for same-site and top-level navigation
response.cookie('session', token, {
  sameSite: 'lax', // Default in modern browsers
  httpOnly: true
})

// Least restrictive - allows cross-site requests (requires Secure)
response.cookie('session', token, {
  sameSite: 'none',
  secure: true,
  httpOnly: true
})

4. Set Proper Expiration

Set appropriate expiration times based on the sensitivity of the data:

// Session cookie (expires when browser closes)
response.cookie('preference', value)

// Short-lived authentication
response.cookie('session', token, {
  maxAge: 30 * 60 * 1000 // 30 minutes
})

// Longer-lived preferences
response.cookie('theme', theme, {
  maxAge: 365 * 24 * 60 * 60 * 1000 // 1 year
})

Modern browsers support cookie prefixes that enforce security constraints:

// Requires Secure attribute
response.cookie('__Secure-Session', token, {
  secure: true,
  httpOnly: true
})

// Requires Secure, HttpOnly, and same-site origin
response.cookie('__Host-Session', token, {
  secure: true,
  httpOnly: true,
  path: '/',
  sameSite: 'strict'
})

Working with JSON Data in Cookies

For complex data, you can store JSON strings in cookies:

// Storing object data
router.get('/preferences', (req) => {
  const userPrefs = {
    theme: 'dark',
    fontSize: 'large',
    notifications: true
  }

  const response = new Response('Preferences saved')

  // Serialize the object to JSON string
  response.cookie('preferences', JSON.stringify(userPrefs), {
    maxAge: 365 * 24 * 60 * 60 * 1000 // 1 year
  })

  return response
})

// Reading object data
router.get('/theme', (req) => {
  try {
    // Parse the JSON string back to an object
    const preferences = JSON.parse(req.cookies.preferences || '{}')

    return new Response(`Your theme is: ${preferences.theme || 'default'}`)
  }
  catch (error) {
    // Handle invalid JSON
    return new Response('Invalid preferences data', { status: 400 })
  }
})

Browsers impose limits on cookie size (usually 4KB per cookie). For larger data, consider:

  1. Split data across multiple cookies
  2. Store only an identifier in the cookie and keep the data server-side (e.g., in a session)
  3. Use client-side storage alternatives for non-sensitive data (e.g., localStorage)
  4. // Better approach for large data
    router.get('/save-data', (req) => {
      const largeUserData = { /* ... large object ... */ }
    
      // Generate a unique ID
      const dataId = crypto.randomUUID()
    
      // Store data server-side (example)
      dataStore.set(dataId, largeUserData)
    
      const response = new Response('Data saved')
    
      // Store only the reference in the cookie
      response.cookie('dataRef', dataId, {
        maxAge: 24 * 60 * 60 * 1000 // 1 day
      })
    
      return response
    })

Third-Party Cookies and Tracking

Modern browsers place restrictions on third-party cookies. Be aware of these limitations when developing cross-domain applications:

// This cookie won't work in cross-site contexts in modern browsers
response.cookie('tracking', 'value', {
  domain: 'analytics.example.com'
})

// For legitimate cross-site cases, you need:
response.cookie('tracking', 'value', {
  domain: 'analytics.example.com',
  sameSite: 'none', // Required for cross-site
  secure: true // Required with sameSite=none
})

Handling Cookies in Middleware

You can create custom middleware to process cookies:

function authCookieMiddleware(req, next) {
  const authToken = req.cookies.authToken

  if (!authToken) {
    return new Response('Unauthorized', { status: 401 })
  }

  try {
    // Verify the token (example)
    const user = verifyAuthToken(authToken)

    // Attach user info to request for downstream handlers
    req.user = user

    return next(req)
  }
  catch (error) {
    // Invalid token
    const response = new Response('Invalid authentication', { status: 401 })

    // Clear the invalid cookie
    response.clearCookie('authToken')

    return response
  }
}

// Use the middleware on specific routes
router.get('/dashboard', authCookieMiddleware, (req) => {
  return new Response(`Welcome, ${req.user.name}!`)
})

Testing Cookies

When writing tests for code that uses cookies:

import { expect, test } from 'bun:test'
import { Router } from 'bun-router'

test('should set theme cookie', async () => {
  const router = new Router()

  router.get('/set-theme', (req) => {
    const { theme } = req.query
    const response = new Response(`Theme set to: ${theme}`)
    response.cookie('theme', theme)
    return response
  })

  // Create a request with query parameter
  const request = new Request('http://localhost/set-theme?theme=dark')

  // Process the request
  const response = await router.handle(request)

  // Verify response status
  expect(response.status).toBe(200)

  // Verify response text
  expect(await response.text()).toBe('Theme set to: dark')

  // Verify cookie was set
  const setCookieHeader = response.headers.get('Set-Cookie')
  expect(setCookieHeader).toContain('theme=dark')
})

test('should read theme cookie', async () => {
  const router = new Router()

  router.get('/theme', (req) => {
    return new Response(`Theme: ${req.cookies.theme || 'default'}`)
  })

  // Create a request with a cookie
  const request = new Request('http://localhost/theme', {
    headers: {
      Cookie: 'theme=dark'
    }
  })

  // Process the request
  const response = await router.handle(request)

  // Verify response
  expect(response.status).toBe(200)
  expect(await response.text()).toBe('Theme: dark')
})

Cookies are commonly used with session management. See the Session Management guide for more on this topic.

Remember Me Functionality

Implement a "Remember Me" feature:

router.post('/login', async (req) => {
  const { username, password, rememberMe } = await req.json()

  // Authenticate user (example)
  const user = await authenticateUser(username, password)

  if (!user) {
    return new Response('Invalid credentials', { status: 401 })
  }

  const response = new Response('Logged in successfully')

  // Set session cookie
  response.cookie('sessionId', generateSessionId(), {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    // If remember me is checked, set a longer expiration
    ...(rememberMe ? { maxAge: 30 * 24 * 60 * 60 * 1000 } : {}) // 30 days if rememberMe
  })

  return response
})

Language/Locale Preference

Store user language preference:

router.get('/set-language/:lang', (req) => {
  const { lang } = req.params

  const response = new Response(`Language set to: ${lang}`)

  // Store language preference for 1 year
  response.cookie('language', lang, {
    maxAge: 365 * 24 * 60 * 60 * 1000,
    sameSite: 'lax'
  })

  return response
})

// Apply language preference in middleware
function languageMiddleware(req, next) {
  // Set default language
  req.language = 'en'

  // Override with user preference if available
  if (req.cookies.language) {
    req.language = req.cookies.language
  }

  return next(req)
}

router.use(languageMiddleware)

Implement a cookie consent banner:

router.get('/', (req) => {
  const consentGiven = req.cookies.cookieConsent === 'true'

  // Generate HTML with or without consent banner
  const html = `
    <!DOCTYPE html>
    <html>
      <body>
        <h1>Welcome to Our Site</h1>
        ${!consentGiven
          ? `
          <div class="cookie-banner">
            <p>We use cookies to improve your experience.</p>
            <button onclick="acceptCookies()">Accept</button>
          </div>
          <script>
            function acceptCookies() {
              document.cookie = 'cookieConsent=true;max-age=31536000;path=/;samesite=lax';
              document.querySelector('.cookie-banner').style.display = 'none';
            }
          </script>
        `
          : ''}
      </body>
    </html>
  `

  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  })
})

Next Steps

Now that you understand cookie handling in bun-router, check out these related topics: