This package provides an ESM module with an updated implementation of the Express Session Store with MongoDb 6 Driver. This store implements touch
and TTL
This module exports a class with a constructor that includes these options:
type MongoSessionStoreOptions = {
uri: string;
collection: string;
ttl: number | ((data: SessionData) => number);
prefix: string;
createTTLIndex: boolean;
};
Setting up an express server with express-sessions
and using this MongoSessionStore as its store.
// store.mts
import { MongoSessionStore } from '@iwsio/mongodb-express-session'
// create a store with default options and custom mongo uri
export const store = new MongoSessionStore({ uri: 'mongodb://localhost/express_sessions' })
store.on('info', console.log)
store.on('error', console.error)
// app.mts
import express from 'express'
import session from 'express-session'
import { store as mongoStore } from './store.mjs'
// optional, listen to store errors
store.on('error', function(error: any) {
console.error(error)
})
const app = express()
app.enable('trust proxy', 1)
app.use(session({
secret: 'some secret',
resave: false,
store: mongoStore,
saveUninitialized: false,
rolling: true,
cookie: { httpOnly: true, sameSite: 'strict' },
name: 'connect.sid'
}))
// ./bin/web.mts
// Don't forget to close the db connection on shutdown
import { store as mongoStore } from './store.mjs'
//... server setup code above
function shutdownHandler(_sig: any, _n: any) {
if (server != null) server.close()
mongoStore.close()
}
process.on('SIGTERM', shutdownHandler)
process.on('SIGINT', shutdownHandler)
With the saveUninitialized: false
setting, testing this code out as-is without an established app will not save any session data to MongoDb. You need to modify the session in order to initialize it. Here's an example showing a simple middleware that modifies session during a request.
// global.d.ts
declare module 'express-session' {
// extends the SessionData type to include your customizations
interface SessionData {
something?: string
}
}
// app.mts: example initialization middleware
app.use((req, _res, next) => {
if (req.session.something) {
console.log('existing something: ', req.session.something)
return next()
}
req.session.something = nanoid()
console.log('new something: ', req.session.something)
next()
})
app.get('/', (req, res) => {
res.status(200).send('OK')
})
If you clone the source code, it includes a simple demo application. Build and run the demo from the project root with Node 20 active. This requires Docker Desktop (or skip this step if you have a local MongoDb server available, in which case, you'll want to update the config settings in app.mts
before this step).
# boots up the mongo db (this doesn't map data volume so it will lose everything when it shuts down)
# also FYI: shut it down with: `docker compose down`
docker compose up -d mongo
# install deps, build both workspaces and start the demo.
npm ci
npm run build
npm start -w demo
Ctrl-C
to stop the server.
Curl the site. Without providing an existing cookie, it should create one for you. You'll see it in the response as a Set-Cookie:
header.
curl -v http://localhost:3000
# yields:
* Trying 127.0.0.1:3000...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET / HTTP/1.1
> Host: localhost:3000
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< X-Powered-By: Express
< Content-Type: text/html; charset=utf-8
< Content-Length: 2
< ETag: W/"2-nOO9QiTIwXgNtWtBJezz8kv3SLc"
# this guy
< Set-Cookie: connect.sid=s%3ABKpbZsCtMVGsRi2uDwMYIa4pAsLMh2EE.jEiosn3OQ7RQ9WZUSAKptC%2B0KshKXPY3oA%2Fh1J%2BECPI; Path=/; Expires=Wed, 29 Nov 2023 03:27:55 GMT; HttpOnly; SameSite=Strict
< Date: Wed, 22 Nov 2023 03:27:55 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host localhost left intact
OK%
Check the database. use mongosh localhost/express_sessions
and check the sessions collection.
use express_sessions
db.sessions.find()
[
{
_id: 'BKpbZsCtMVGsRi2uDwMYIa4pAsLMh2EE',
expires: ISODate("2023-11-29T03:27:55.254Z"),
session: {
cookie: {
path: '/',
_expires: ISODate("2023-11-29T03:27:55.254Z"),
originalMaxAge: 604800000,
httpOnly: true,
sameSite: 'strict'
},
something: 'K-o_HNG5jN6XcJ9bfrTep'
}
}
]
// Check for the index (default option)
db.sessions.getIndexes()
[
{ v: 2, key: { _id: 1 }, name: '_id_' },
{
v: 2,
key: { expires: 1 },
name: 'expires_1',
expireAfterSeconds: 0
}
]
Curl the site again. Except this time, use the cookie data sent to you from the last response.
curl -v --cookie "connect.sid=s%3ABKpbZsCtMVGsRi2uDwMYIa4pAsLMh2EE.jEiosn3OQ7RQ9WZUSAKptC%2B0KshKXPY3oA%2Fh1J%2BECPI; Path=/; Expires=Wed, 29 Nov 2023 03:27:55 GMT; HttpOnly; SameSite=Strict" http://localhost:3000
Then check MongoDb; ensure that no additional cookies were created. You should still find one cookie, but with an updated expires
date. touch
works!
db.sessions.find()
[
{
_id: 'BKpbZsCtMVGsRi2uDwMYIa4pAsLMh2EE',
expires: ISODate("2023-11-29T03:39:18.430Z"),
session: {
cookie: {
path: '/',
_expires: ISODate("2023-11-29T03:27:55.254Z"),
originalMaxAge: 604800000,
httpOnly: true,
sameSite: 'strict'
},
something: 'K-o_HNG5jN6XcJ9bfrTep'
}
}
]