Skip to content

remix/session-middleware

The remix/session-middleware package provides middleware that connects a cookie and a session storage backend to your router. It handles reading the session from the incoming request and saving any changes to the outgoing response automatically.

Installation

The remix/session-middleware package is included with Remix. No additional installation is required.

ts
import { session } from 'remix/session-middleware'

Creates middleware that manages sessions for every request.

ts
function session(cookie: Cookie, storage: SessionStorage): Middleware

Parameters:

ParameterTypeDescription
cookieCookieA cookie created with createCookie() from remix/cookie. Defines the cookie name, signing secrets, and attributes (httpOnly, secure, sameSite, maxAge, etc.).
storageSessionStorageA session storage backend (cookie storage, filesystem, memory, Redis, Memcache, or a custom implementation).

Returns: A Middleware function that can be added to a router.


How It Works

The session middleware runs on every request and performs the following steps:

  1. Read -- Parses the session cookie from the incoming Cookie header and loads the session data from the storage backend using storage.read(cookieValue).
  2. Set context -- Makes the session available to downstream middleware and handlers via context.get(Session).
  3. Wait -- Calls next() and waits for the handler (and any downstream middleware) to complete.
  4. Save -- After the handler returns a response, calls storage.save(session) to persist any changes (new values, unset keys, flash consumption, regenerated IDs, or destruction).
  5. Set cookie -- Adds a Set-Cookie header to the response with the updated cookie value.
Request ──> [Parse Cookie] ──> [Load Session] ──> [Handler] ──> [Save Session] ──> [Set Cookie] ──> Response

Accessing the Session

Use the Session context key from remix/session to access the session in any handler or middleware that runs after the session middleware.

ts
import { Session } from 'remix/session'

router.map(route, async ({ context }) => {
  let session = context.get(Session)

  // Read values
  let userId = session.get('userId')

  // Write values
  session.set('lastVisit', Date.now())

  // Flash messages
  session.flash('notice', 'Settings saved.')

  // The session middleware will automatically save these changes
  return new Response('OK')
})

Auto-Save Behavior

The session middleware saves automatically after every request. You do not need to call a save method manually. This includes:

  • New values set with session.set(key, value).
  • Removed values cleared with session.unset(key).
  • Consumed flash values that were read with session.get(key).
  • Regenerated IDs from session.regenerateId().
  • Destroyed sessions from session.destroy() --- the storage entry is deleted and the cookie is cleared.

If the session was not modified during the request, the middleware skips the save step for efficiency. The Set-Cookie header is still included to refresh the cookie's maxAge if needed.


Middleware Ordering

The session middleware must come before any middleware or handler that accesses the session. A typical ordering:

ts
import { createRouter } from 'remix/fetch-router'
import { session } from 'remix/session-middleware'
import { auth } from 'remix/auth-middleware'
import { formData } from 'remix/form-data-middleware'

let router = createRouter({
  middleware: [
    formData(),                             // Parse form data
    session(sessionCookie, sessionStorage), // Load session
    auth({ schemes: [sessionScheme] }),     // Auth reads from session
  ],
})

The auth middleware depends on the session being loaded, so it must come after session().


Complete Example

ts
// app/session.ts
import { createCookie } from 'remix/cookie'
import { createCookieSessionStorage } from 'remix/session/cookie-storage'

export let sessionCookie = createCookie('__session', {
  httpOnly: true,
  secure: process.env.NODE_ENV === 'production',
  sameSite: 'Lax',
  secrets: [process.env.SESSION_SECRET!],
  maxAge: 60 * 60 * 24 * 7, // 1 week
})

export let sessionStorage = createCookieSessionStorage(sessionCookie)
ts
// app/server.ts
import { createRouter } from 'remix/fetch-router'
import { session } from 'remix/session-middleware'
import { Session } from 'remix/session'
import { sessionCookie, sessionStorage } from './session.ts'

let router = createRouter({
  middleware: [
    session(sessionCookie, sessionStorage),
  ],
})

// Set a value
router.map(loginAction, async ({ context }) => {
  let s = context.get(Session)
  s.set('userId', user.id)
  s.regenerateId()
  return new Response(null, { status: 302, headers: { Location: '/' } })
})

// Read a value
router.map(homeRoute, async ({ context }) => {
  let s = context.get(Session)
  let userId = s.get('userId')
  // ...
})

// Destroy the session
router.map(logoutAction, async ({ context }) => {
  let s = context.get(Session)
  s.destroy()
  return new Response(null, { status: 302, headers: { Location: '/login' } })
})

Released under the MIT License.