@itrocks/fastify-file-session-store
v0.4.1
Published
Simple session data persistence for Fastify using JSON files
Maintainers
Readme
fastify-file-session-store
Simple session data persistence for Fastify using JSON files.
This documentation was written by an artificial intelligence and may contain errors or approximations. It has not yet been fully reviewed by a human. If anything seems unclear or incomplete, please feel free to contact the author of this package.
Installation
npm i @itrocks/fastify-file-session-store @fastify/session fastify@itrocks/fastify-file-session-store does not install fastify or
@fastify/session as direct dependencies of your application: you must declare
them yourself in your project.
Usage
FileStore is an implementation of SessionStore for
@fastify/session that stores each
session as a JSON file on disk. It is intended for simple Fastify
applications, where lightweight session persistence is enough and you do not
want to set up a dedicated database.
It also implements the transport-neutral
@itrocks/session store contract so
applications can list and revoke authenticated sessions without depending on
Fastify.
Minimal example
import Fastify from 'fastify'
import fastifySession from '@fastify/session'
import { FileStore } from '@itrocks/fastify-file-session-store'
const app = Fastify()
app.register(fastifySession, {
secret: 'a very secret key with at least 32 characters',
cookie: {
secure: false, // set to true when running behind HTTPS
},
store: new FileStore('./sessions'),
})
app.get('/', async (request, reply) => {
request.session.views = (request.session.views ?? 0) + 1
return { views: request.session.views }
})
app.listen({ port: 3000 })
.then(() => console.log('Server listening on http://localhost:3000'))Complete and realistic example
The following example shows a more advanced configuration using
node:path to define a sessions
directory, basic error handling, and reuse of the same store across several
routes.
import Fastify from 'fastify'
import fastifySession from '@fastify/session'
import { FileStore } from '@itrocks/fastify-file-session-store'
import { join, normalize } from 'node:path'
const app = Fastify({ logger: true })
// Absolute path to the sessions directory, for example in a data folder
const sessionDir = normalize(join(__dirname, '../data/sessions'))
app.register(fastifySession, {
secret: process.env.SESSION_SECRET ?? 'change-me-in-production',
cookie: {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
},
store: new FileStore(sessionDir),
})
app.get('/login', async (request, reply) => {
const userId = request.query.userId ?? 'guest'
request.session.user = { id: userId }
return { ok: true, user: request.session.user }
})
app.get('/me', async (request, reply) => {
if (!request.session.user) {
reply.code(401)
return { error: 'Not authenticated' }
}
return { user: request.session.user }
})
app.get('/logout', async (request, reply) => {
// Destroy the session in the store and in the cookie
await request.session.destroy()
return { ok: true }
})
app.listen({ port: 3000 }).catch((error) => {
app.log.error(error, 'Cannot start server')
process.exit(1)
})API
class FileStore implements SessionStore
Implementation of SessionStore (the @fastify/session interface) that saves
sessions into individual JSON files.
Each session id corresponds to a file in the directory provided to the constructor. Reads return detached snapshots. Per-session locks coordinate authoritative reads, writes and revocation.
new FileStore(directory: string)
directory: path to the directory where session files will be created. It can be relative to the current process directory or absolute. The directory must exist and be readable and writable by the Node.js process.
Returns a FileStore instance to pass to the store option of
@fastify/session.
sessionFile(sessionId: string): string
Builds the full path to the session file corresponding to sessionId. This
method is exposed publicly but is generally only useful if you want to
diagnose or manipulate the session files manually.
set(sessionId: string, session: Session, callback: (error?: any) => void): void
Method called by @fastify/session when a session must be stored.
sessionId: unique identifier of the session.session:Sessionobject provided by Fastify, serializable to JSON.callback(error?): function to call once the write is finished or in case of error.
The store merges changes made since this request loaded its snapshot, then atomically replaces the JSON file. The callback runs only after persistence succeeds. Nested mutations are detected without retaining live cache objects.
compareAndSet(sessionId, expectedGeneration, session, callback): void
Implements the generation-checked write required by @fastify/session 11.1.6. The callback receives ENOENT when the
stored session is missing, expired, revoked or belongs to a different generation. Other storage errors are preserved.
Generation checks and persistence share the same per-session lock as transactions and revocation; acknowledgement
follows the atomic file replacement.
Saves of the same generation merge request changes. Regeneration replaces the business state and invalidates previous
request leases while retaining Fastify's stable storage identifier. Legacy files without Fastify generation metadata
use their storage identifier as the expected generation, allowing existing cookies to be upgraded.
Fastify's immutable session metadata is retained without assigning to its read-only property and is excluded from
business snapshots and session management listings. The store also supports @fastify/session 11.1.4.
get(sessionId: string, callback: (error: any, session?: Session | null) => void): void
Method called by @fastify/session to load an existing session.
sessionId: identifier of the session to read.callback(error, session?):errorisnullorundefinedwhen everything went well;sessionis:- the
Sessionobject that was found; null/undefinedwhen no valid session exists for that id.
- the
The store reads the corresponding session file, parses it as JSON and returns the session.
Missing or expired files return no session. Other read/parse failures reach the callback as errors; corrupt or unavailable storage is never treated as permission to recreate a missing generation.
destroy(sessionId: string, callback: (error?: any) => void): void
Removes the corresponding session from disk under the same lock as writes.
sessionId: identifier of the session to destroy.callback(error?): function called after the deletion, or with an error if something went wrong.
This method is typically called when a user logs out or when the session expires.
list(): Promise<StoredSession[]>
Returns every readable stored session with its identifier and file modification date. Invalid files and files removed concurrently are ignored. A missing session directory produces an empty list.
revoke(sessionId: string): Promise<void>
Removes a session from memory and disk. Revocation is idempotent: an already absent session is treated as successfully revoked.
Typical use cases
- Simple Fastify projects without a dedicated session database: you want session persistence across server restarts, but you do not want to set up Redis, a SQL database or another external service.
- Development or demo environments: file-based persistence is convenient to quickly test an application or share a local demo.
- Single-instance applications: when your application runs on a single machine (or with a shared filesystem) and you do not need session consistency across several nodes.
- Session debugging: because sessions are stored as JSON files, you can
open the file corresponding to a given
sessionIdto inspect or debug the session state.
Transactions and generation checks
transact<T>(sessionId, session, operation): Promise<T> reloads the authoritative snapshot under a session-wide lock,
merges only the request's changes, invokes operation(data), then atomically persists the result before
acknowledging it.
An operation that throws commits nothing. File failures leave the previous snapshot intact and release coordination.
refresh(sessionId, session) reloads a live request session; it rejects a missing, revoked or expired generation.
SessionExpiredError carries code SESSION_STALE for these rejections.
Generation metadata is reserved internally. Session management list() exposes business data without that marker.
Snapshots are detached, scoped to the store instance and attached to request leases using weak references.
The lock registry contains coordination promises only and removes completed entries. All set, compareAndSet,
transact, refresh,
get, revoke and destroy operations share it. Absolute expiry is checked again before a transaction commits.
Session file replacement is atomic within the same directory; directories/files use modes 0700/0600.
This contract supports one store instance per session directory in a single server process. It does not provide cross-process locking. Separate directory instances remain isolated; a new instance can read durable files on restart.
npm test exercises the store and the installed Fastify session plugin. Set FASTIFY_SESSION_MODULE to another installed
@fastify/session package directory to run the HTTP contract against that version without replacing application packages.
