A change stream watches a collection, database or deployment and delivers every change as a document. It is built on the replica set oplog, so it requires a replica set, and each event carries a resume token you can persist so a restart continues without missing or replaying changes.
Before you start
You should be comfortable with the aggregation pipeline and MongoDB replica sets. This article assumes the Node.js driver, though the concepts apply to any driver.
Step-by-step walkthrough
Step 1: Open a change stream with a filter
Call collection.watch(pipeline) and use a $match stage to limit the operation types you care about. Filtering server-side avoids shipping events you will discard, and you can request fullDocument: 'updateLookup' to include the current document on updates.
Step 2: Persist the resume token
Every change includes _id, the resume token. Store it after you have processed the change, and pass resumeAfter (or startAfter) when reopening. Persist only after processing so a crash reprocesses rather than skips work, which fits idempotent consumers.
Step 3: Handle invalidate and failover
If the watched collection is dropped, the stream emits an invalidate event and closes; reopen it. During a replica-set failover the stream may error, so wrap it in a retry loop that reopens with the last token. Bound the loop and alert on repeated failures.
Worked scenario
The loop writes each change and records its resume token.
const pipeline = [{ $match: { operationType: { $in: ['insert', 'update'] } } }];
const changeStream = db.collection('orders').watch(pipeline, {
fullDocument: 'updateLookup',
});
for await (const change of changeStream) {
await handle(change.fullDocument);
await saveResumeToken(change._id);
}Walk through the example
The filter limits the stream to inserts and updates, and updateLookup fills in the document body so the handler does not need a second read. After the handler finishes, the resume token is saved, so a restart resumes from the last processed event. If handle throws, the token is not advanced and the event is reprocessed, which requires the handler to be idempotent.
Common mistake
Saving the resume token before processing, which risks skipping an event if the process crashes mid-handler. Another is assuming the stream survives a failover without reopening; the driver often surfaces an error and the application must retry.
Verify the behavior
Insert and update documents and assert an event arrives for each with the expected fullDocument. Kill the consumer mid-stream, restart with the stored token, and confirm no event is lost or duplicated. Drop the watched collection and confirm the invalidate event is handled.
Interview exercise
Why must the change-stream handler be idempotent?
Answer and reasoning
Because a crash after processing but before the token is saved causes the same event to be delivered again on restart. Idempotency — for example, keying the write by the event id or a natural key — makes reprocessing harmless. Saving the token first would instead risk losing an event, so “at least once” delivery plus idempotent handling is the safe combination.
Continue learning
Compare delivery guarantees in Consumer deduplication and Idempotency keys. Read the MongoDB change streams documentation and try the MongoDB interview questions.