59

Below is the command that can be used via the mongo terminal to set an expiry time for collections (a TTL):

db.log.events.ensureIndex( { "status": 1 }, { expireAfterSeconds: 3600 } )

How do I do this from my code in Node.js using mongoose?

ZachB
  • 13,051
  • 4
  • 61
  • 89
Amanda G
  • 1,931
  • 10
  • 33
  • 43

9 Answers9

131

In Mongoose, you create a TTL index on a Date field via the expires property in the schema definition of that field:

// expire docs 3600 seconds after createdAt
new Schema({ createdAt: { type: Date, expires: 3600 }});

Note that:

  • MongoDB's data expiration task runs once a minute, so an expired doc might persist up to a minute past its expiration.
  • This feature requires MongoDB 2.2 or later.
  • It's up to you to set createdAt to the current time when creating docs, or add a default to do it for you as suggested here.
    • { createdAt: { type: Date, expires: 3600, default: Date.now }}
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • ```updateLogSchema = mongoose.Schema({ success: { type: Boolean, required: true }, saveDate: { type: Date, required: true, default: Date.now, expires: 60*60*24*7 #Every 7 days } })``` So I have to do this on one property which is a date? What happens if I update my doc & my saveDate, does it live 7 days again? – Andi Giga Nov 26 '15 at 16:28
  • 1
    @AndiGiga you probably want to review the mongo docs: https://docs.mongodb.org/manual/tutorial/expire-data/ – mooreds Jan 27 '16 at 00:04
  • 1
    How can I apply ttl in specific elements of array. I am writing node API. I am persisting token in string array I want to remove those after a specified time interval ?? – Sunil Sharma Feb 22 '16 at 07:42
  • 2
    So if I set the expires to `updatedAt`, then it will refresh the count every time after the value updated? – Haven Jul 04 '16 at 21:14
  • but then how do i know when it expired? where is it written in the db? – dang Nov 16 '16 at 19:14
  • 2
    I'm following this approach, however no matter the amount of seconds I indicate, it deletes the document after one minute. `cartSchema.index({createdAt: 1},{expireAfterSeconds: 14400})` – George Cscnt Jun 28 '18 at 21:00
  • this solution does not work and there's no mention of this approach in the Mongoose docs. Use the approach outlined here: https://docs.mongodb.com/manual/tutorial/expire-data/ – Nick Feb 24 '19 at 07:54
  • @Nick Documentation for this feature in Mongoose is [here](https://mongoosejs.com/docs/api.html#schema_Schema-index). It does work, but you need drop any existing index on the field so that Mongoose can create it. – JohnnyHK Feb 24 '19 at 14:58
  • @JohnnyHK in your OP you use this line: `new Schema({ createdAt: { type: Date, expires: 3600 }});` which as far as I have tested does not work and is not mentioned in the docs. The structure mentioned in the docs - `new Schema({ date: { type: Date, index: { unique: true, expires: '1d' }})` - may indeed work. – Nick Feb 25 '19 at 10:57
  • @Nick I just tried your `createdAt` schema with Mongoose 5.1.4 and the expiration worked correctly. Can you post a new question if you're still having problems with it? – JohnnyHK Feb 25 '19 at 14:56
40

this code is working for me.

may it help

let currentSchema = mongoose.Schema({
    id: String,
    name: String,
    packageId: Number,
    age: Number
}, {timestamps: true});

currentSchema.index({createdAt: 1},{expireAfterSeconds: 3600});
Sadegh Teimori
  • 1,348
  • 12
  • 12
24

Providing a string to expires also works nicely with Mongoose if you do not want to deal with the expire time calculation and improve the overall readability of the schema.

For example here we are setting the expires to 2m (2 minutes) and mongoose would convert to 120 seconds for us:

var TestSchema = new mongoose.Schema({
  name: String,
  createdAt: { type: Date, expires: '2m', default: Date.now }
});

Mongoose would create an index in the background and auto set the expireAfterSeconds to in this case 120 seconds (specified by the 2m).

It is important to note that the TTL process runs once every 60 seconds so it is not perfectly on time always.

Akrion
  • 18,117
  • 1
  • 34
  • 54
  • Thank you for this answer, but can you tell me if I can use `expires: '2h'` for hours or `'2d'` for days –  May 20 '22 at 17:36
  • it works but I don't know why it deletes at later time, even when I set `expires` to `1 (1 second)`, it takes `30seconds` to delete the document, and I saw in atlas, it showed me ttl time 1second –  May 20 '22 at 18:46
  • @NaveenKumar As Akrion said it's because "It is important to note that the TTL process runs once every 60 seconds so it is not perfectly on time always.". – Gabor Szita Jun 23 '22 at 08:05
3

If you are working with Mongodb Atlas Replica Sets - try:

import * as mongoose from 'mongoose'; 

let currentSchema = new mongoose.Schema({
        createdAt: { type: Date, expires: 10000, default: Date.now },
        id: String,
        name: String,
        packageId: Number,
        age: Number
        });

currentSchema.index({"lastModifiedDate": 1 },{ expireAfterSeconds: 10000 });
Jason Mullings
  • 850
  • 14
  • 10
3
new Scehma({
    expireAt: {
      type: Date,
      expires: 11,
      default: Date.now
  }
)}

This is the solution that worked for me according to this in the current Mongoose docs.

Brandon
  • 31
  • 1
  • so simple! was searching for a solution for this for days, and got so many answers that i didn't know what to do with, till i realized i should be looking for a **mongoose** solution. thank you so much! – librogil Mar 19 '23 at 09:28
2

9/2022 Working Solution using Mongoose 6.5.4

None of the answers here worked for me, but I was able to finally get it working using the latest version of Mongoose currently available, 6.5.4.

Say our Schema looks like this:

const MySchema = new mongoose.Schema({
  id: { type: Number },
  myCustomTTLField: { type: Date }
});

myCustomTTLField is the field you want to index and have control the expiration. To achieve this, we add the following under our schema definition:

MySchema.path('myCustomTTLField').index({ expires: 60 });

The argument in MySchema.path is the name of the field you want to index for TTL. The expires option should be the number of seconds that will elapse from the Date represented in myCustomTTLField before the document is deleted. In the example above, the document will be deleted 60 seconds after whatever date is saved in myCustomTTLField. The full example:

const MySchema = new mongoose.Schema({
  id: { type: Number },
  myCustomTTLField: { type: Date }
});
MySchema.path('myCustomTTLField').index({ expires: 60 });

Please let me know if this works for you, I hope this helps. Mongoose TTL has been a thorn in my side for a long time, as their docs are notoriously tough to navigate. I found this solution via a small example buried in the docs here.

IMPORTANT NOTE: TTL is not guaranteed to happen at exactly the time specified by your date + expiration seconds. This is due to how MongoDB's background delete process works. It runs every 60 seconds, so you may theoretically wait up to 60 seconds past expected TTL before seeing your document deleted. More info on that from the MongoDB docs.

nhuesmann
  • 437
  • 3
  • 10
1

There is a npm library - 'mongoose-ttl'.:

var schema = new Schema({..});
schema.plugin(ttl, { ttl: 5000 });

you can see all the options of this library: https://www.npmjs.com/package/mongoose-ttl

arr
  • 19
  • 1
1
const Schema = new mongoose.Schema({id: {
type: Number}, 
createdAt: {
type: Date, expires: '4h', index: true,
default: Date.now}});

You need to add index: true while creating you schema

Pratik Khadtale
  • 265
  • 4
  • 11
0

FWIW I could only get the expires feature to work on a field called expiresAt. Here's my interface, and schema for implementing this in Typescript.

import { model, Schema, Types } from 'mongoose';

export interface ISession {
  sessionId: string;
  userId: Types.ObjectId;
  role: string;
  expiresAt?: Date;
}

const sessionSchema = new Schema<ISession>({
  sessionId: { type: String, required: true, indexes: { unique: true} },
  userId: { type: Schema.Types.ObjectId, required: true, ref: 'users'},
  role: { type: String, required: true, enum: [ 'ADMIN', 'BASIC_USER' ]},
  expiresAt: { type: Date, expires: '1h', default: Date.now }
}, { versionKey: false });

Reading the Mongoose documentation it seems like all the other proposed solutions should work too. I don't know why they were not for me. You can read the official Mongoose docs on expiresAt here.

HopAlongPolly
  • 1,347
  • 1
  • 20
  • 48