1

I'am having trouble figuring out how to delete a document from mongo DB after a timeout. Anyone can help me out with a simple way of doing it and maybe explain to me why this one is wrong? (it works, the document is deleted after some time , but I get a error message and the server stops)

this is the code I used written and also as a picture together with the terminal errormessage, I note, the document is deleted after the setTimeout runs out, but server stops: documents are pretty simple consist of these:

server.js

import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import shareRoutes from "./routes/shares.js";

const app = express();

app.use(cors());

app.get("/", (req, res) => {
  res.json({ message: "API running..." });
  res.end("");
});
app.use(express.json());
app.use("/radar", shareRoutes);

mongoose
  .connect(
    "mongodb+srv://<creditentials>@cluster0.dqlf2.mongodb.net/locations?retryWrites=true&w=majority",
    { useNewUrlParser: true },
    { useFindAndModify: false }
  )
  .then(() => {
    app.listen(5000, () => {
      "Server Running on port 5000";
    });
  })
  .catch((err) => {
    console.log(err);
  });

shares.js for the route

import express from "express";

import {
  createLocation,
  getLocations,
} from "../controllers/shareController.js";

const router = express.Router();

// create location
router.post("/", createLocation);

// get Locations
router.get("/", getLocations);

export default router;

shareController.js

import express from "express";
import shareLocation from "../models/shareLocation.js";
const router = express.Router();

export const createLocation = async (req, res) => {
  const { latitude, longitude, dateShared, timeShared, city, road } = req.body;

  const newLocation = shareLocation({
    latitude,
    longitude,
    dateShared,
    timeShared,
    city,
    road,
  });

  try {
    await newLocation.save();

    res.status(201).json(newLocation);

    setTimeout(() => {
      (async () => {
        try {
          await shareLocation.findByIdAndRemove(newLocation._id);
          res.json({ message: "Shared location deleted" });
        } catch (error) {
          res.status(409).json({ message: error });
        }
      })();
    }, 30000);
  } catch (error) {
    res.status(409).json({ message: newLocation });
  }
};

export const getLocations = async (req, res) => {
  try {
    const locations = await shareLocation.find({});
    res.status(200).json(locations);
  } catch (error) {
    console.log(error);
    res.status(409).json("Unable to fetch Locations");
  }
};

export const deleteLocation = async (req, res) => {
  const { id } = req.params;
  try {
    await shareLocation.findByIdAndRemove(id);
    res.json({ message: "Shared location deleted" });
  } catch (error) {
    res.status(409).json({ message: error });
  }
};

export default router;

shareLocations.js for the schema

import mongoose from "mongoose";

const hours = new Date().getHours().toLocaleString();
const minutes = new Date().getMinutes().toLocaleString();
const actualHours = hours.length < 2 ? "0" + hours : hours;
const actualMinutes = minutes.length < 2 ? "0" + minutes : minutes;

const locationSchema = mongoose.Schema({
  timeShared: {
    type: String,
    default: actualHours + ":" + actualMinutes,
  },
  dateShared: {
    type: String,
    default: new Date().toDateString(),
  },
  latitude: {
    type: String,
    required: true,
  },
  longitude: {
    type: String,
    required: true,
  },
  city: {
    type: String,
  },
  road: {
    type: String,
  },
});

export default mongoose.model("shareLocation", locationSchema);

enter image description here

  • what is `newLocation` ? please show the implementation – Kavindu Vindika Jul 10 '22 at 14:53
  • edited the post, the code is pretty simple. As stated I am trying to schedule a delete/remove from the database of the document created with setTimeout in the shareController.js file, with the createLocation function. The document is indeed deleted from the database but the server/app crashes leaving me with this message on the terminal seen on the picture – MoonSungKil Jul 10 '22 at 15:59

1 Answers1

1

I'll start with what is the "proper" solution, we'll take a look at what's wrong with the code after.

Mongo provides a built in way to remove documents after a certain time period, the "proper" way is to built a TTL index on the required field, and to specify after how many seconds you want that document deleted.

Mongo will then periodically check the index and clear documents when the time is up, this removes all kinds of levels of complexity from your app ( for example this timeout can easily cause a memory leak if too many calls are called in a short time window ).

A TTL index is created by using the simple createIndex syntax:

db.collection.createIndex( { "created_at": 1 }, { expireAfterSeconds: 30 } )

This will make documents expire 30 seconds after creation, you'll just have to add this timestamp to your code:

  const newLocation = shareLocation({
    latitude,
    longitude,
    dateShared,
    timeShared,
    city,
    road,
    created_at: new Date() // this new line is required
  });

I can also tell you're using mongoose, then mongoose provides created_at field automatically if you set the Schema to include timestamps meaning your app can even ignore that.


Now what's wrong with your code?

It's simple, you first respond to the response in this line:

res.status(201).json(newLocation);

But then after a 30 second timeout you try to respond again, to the same response:

res.json({ message: "Shared location deleted" });

Node does not allow this behavior, you can only call set headers once ( which is called when responding ), I will not go into detail why as there are many stackoverflow answers (like this) that explain it.

Apart from the obvious issue that crashes your app, other issue's can arise from this code, as I mentioned before a memory leak can easily crash your app, If your app restarts for whatever reason the locations that were "pending" in memory will not be cleared from the db, and more.

This is why it's recommended to let the DB handle the deletion.

Tom Slabbaert
  • 21,288
  • 10
  • 30
  • 43
  • very good explanation, thank you. As you may see I am not that great at backend, and I just can't figure it out how to implement the TTL expire on the nodejs backend, I've added the `createdAt: new Date()` on the controller, and on the schema `const locationSchema = mongoose.Schema({ createdAt: { type: Date, default: new Date(), index: { expires: 300 }, }, });` It indeed expires and is removed from the db, but it happens usually within 1m range and not the time period I assigned (5min / 300s) in this caase – MoonSungKil Jul 10 '22 at 19:03
  • 1
    You give that field a default value of `new Date()`, which is a fixed point in time when the code starts running. at that point all new documents are created with a "past date" I recommend you drop the default value and enable timestamps in mongoose instead. – Tom Slabbaert Jul 11 '22 at 05:51