This error occurs when attempting to use a MongoDB ClientSession after it has been explicitly ended or has expired. Sessions must be active to perform database operations within transactions or to maintain operation consistency.
The MongoExpiredSessionError indicates that your application is trying to execute database operations using a ClientSession that is no longer valid. MongoDB sessions are used to enable causally consistent operations, retryable writes, and multi-document ACID transactions. Once a session is ended—either by calling endSession() explicitly, by the session timing out due to inactivity, or by the database connection being closed prematurely—any subsequent attempt to use that session will trigger this error. Sessions in MongoDB have a lifecycle and are meant to be short-lived within the context of specific operations or transactions. The driver tracks the session state, and once a session transitions to an "ended" state, the underlying resources are released and the session object becomes unusable. This error typically surfaces when there's a mismatch between when your code expects the session to be active and when it has actually been terminated. Common scenarios include closing database connections before asynchronous operations complete, reusing session objects across multiple sequential operations without proper lifecycle management, or having concurrent requests share the same connection/session in a way that causes one request's cleanup to invalidate another's active session.
Instead of opening and closing connections per request, create a single persistent connection when your application starts. This ensures sessions remain valid throughout operation execution.
// BAD: Per-request connection
app.post('/users', async (req, res) => {
const client = await MongoClient.connect(uri);
const db = client.db('myapp');
await db.collection('users').insertMany(users);
await client.close(); // Closes before insertMany completes!
res.send('OK');
});
// GOOD: Persistent connection
let client;
let db;
async function startApp() {
client = await MongoClient.connect(uri);
db = client.db('myapp');
app.listen(3000);
}
app.post('/users', async (req, res) => {
await db.collection('users').insertMany(users);
res.send('OK');
});
startApp();This pattern ensures the connection and its sessions remain active for the lifetime of your application.
Ensure all database operations complete before calling endSession() or closing connections. Use async/await consistently.
// BAD: Session closed before operation completes
const session = client.startSession();
collection.insertMany(docs, { session });
session.endSession(); // Called immediately, operation still running!
// GOOD: Await operation completion
const session = client.startSession();
try {
await collection.insertMany(docs, { session });
} finally {
await session.endSession();
}The finally block ensures the session is properly cleaned up even if errors occur, but only after operations complete.
MongoDB's withTransaction() helper automatically manages session lifecycle and handles retries for transient errors.
// Using withTransaction helper
const session = client.startSession();
try {
await session.withTransaction(async () => {
await collection1.insertOne(doc1, { session });
await collection2.updateOne(filter, update, { session });
// Session automatically managed, no manual endSession needed
});
} finally {
await session.endSession();
}
// For Mongoose users
await mongoose.connection.transaction(async (session) => {
await User.create([{ name: 'John' }], { session });
await Order.create([{ userId: '123' }], { session });
// Mongoose handles session lifecycle
});This approach eliminates manual transaction management errors and ensures proper cleanup.
For operations that genuinely require more time, configure longer session timeouts in your connection options.
const client = new MongoClient(uri, {
maxIdleTimeMS: 120000, // 2 minutes instead of default 30 seconds
serverSelectionTimeoutMS: 30000,
});However, prefer optimizing your queries over increasing timeouts. Use maxTimeMS for individual operations rather than relying on socket timeouts:
// Better: Set operation-level timeout
await collection.find(query)
.maxTimeMS(10000) // 10 seconds for this query
.toArray();This allows MongoDB to cancel slow operations server-side rather than waiting for socket timeouts.
Ensure errors in async operations aren't silently caught, which can cause sessions to be cleaned up while retries are attempted.
// BAD: Silent error handling breaks retry logic
session.withTransaction(async () => {
try {
await collection.insertOne(doc, { session });
} catch (err) {
console.log('Error occurred'); // Swallowed!
// Session state now inconsistent
}
});
// GOOD: Let errors propagate
session.withTransaction(async () => {
await collection.insertOne(doc, { session });
// Errors propagate to withTransaction retry logic
});If you must handle errors, ensure they're re-thrown so the session management logic can respond appropriately.
Ensure each request gets its own session and doesn't share sessions across concurrent operations.
// BAD: Shared global session
let globalSession = client.startSession();
app.post('/data', async (req, res) => {
await collection.insertOne(doc, { session: globalSession });
});
// GOOD: Per-operation session
app.post('/data', async (req, res) => {
const session = client.startSession();
try {
await collection.insertOne(doc, { session });
} finally {
await session.endSession();
}
});For most use cases, let the driver manage sessions implicitly—explicit sessions are only needed for multi-document transactions or causal consistency requirements.
Session Lifecycle Best Practices: MongoDB sessions are designed for short-lived operations. The default session idle timeout is 30 minutes on the server side, but client drivers may enforce shorter timeouts. Always match your session usage patterns to their intended scope—sessions for transactions should span only the transaction duration, not the entire request lifecycle.
Connection Pooling Implications: When using connection pooling (the default in most drivers), sessions are tied to specific connections from the pool. Abruptly closing connections or having pool exhaustion can cause sessions to become invalidated. Configure appropriate pool sizes (minPoolSize/maxPoolSize) based on your concurrency needs.
Mongoose-Specific Considerations: Mongoose adds its own session management layer. When using Mongoose's Connection#transaction() or Session#withTransaction(), the library handles session creation and cleanup. Mixing manual session management with Mongoose's helpers can lead to lifecycle conflicts. Stick to one pattern consistently.
Debugging Tips: Enable MongoDB driver debug logging to track session creation and destruction. For the Node.js driver, set the environment variable DEBUG=mongo:* or use driver-level logging options. This visibility helps identify where sessions are being ended prematurely or reused incorrectly.
Serverless/Lambda Environments: In serverless functions where connections may persist across invocations, ensure you're not accidentally reusing sessions from previous function executions. Either create fresh sessions per invocation or use explicit session lifecycle management with proper cleanup in finally blocks.
Retryable Writes: MongoDB's retryable writes feature (enabled by default in driver versions 4.2+) may retry operations automatically. If a session expires during a retry, you'll see this error. The solution is the same: ensure sessions remain active for the full operation duration, or let the driver manage sessions implicitly for non-transactional operations.
StaleShardVersion: shard version mismatch
How to fix "StaleShardVersion: shard version mismatch" in MongoDB
MongoOperationTimeoutError: Operation timed out
How to fix "MongoOperationTimeoutError: Operation timed out" in MongoDB
MongoServerError: PlanExecutor error during aggregation :: caused by :: Sort exceeded memory limit of 104857600 bytes, but did not opt in to external sorting. Aborting operation.
How to fix "QueryExceededMemoryLimitNoDiskUseAllowed" in MongoDB
MissingSchemaError: Schema hasn't been registered for model
How to fix "MissingSchemaError: Schema hasn't been registered for model" in MongoDB/Mongoose
CastError: Cast to ObjectId failed for value "abc123" at path "_id"
How to fix "CastError: Cast to ObjectId failed" in MongoDB