0
0
ExpressHow-ToBeginner · 4 min read

How to Implement Soft Delete in Express: Simple Guide

To implement soft delete in Express, add a flag like isDeleted to your data model and update it instead of removing the record. Then, filter queries to exclude records where isDeleted is true, keeping data safe but hidden.
📐

Syntax

Soft delete involves adding a boolean field like isDeleted to your data model. Instead of deleting a record, you update this field to true. When fetching data, you filter out records where isDeleted is true.

This pattern keeps data in the database but hides it from normal views.

javascript
app.delete('/items/:id', async (req, res) => {
  const id = req.params.id;
  await Item.updateOne({ _id: id }, { isDeleted: true });
  res.send({ message: 'Item soft deleted' });
});

app.get('/items', async (req, res) => {
  const items = await Item.find({ isDeleted: false });
  res.send(items);
});
💻

Example

This example shows a simple Express app with a MongoDB model using Mongoose. It implements soft delete by setting isDeleted to true instead of removing the item. The GET route only returns items not marked as deleted.

javascript
import express from 'express';
import mongoose from 'mongoose';

const app = express();
app.use(express.json());

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/softdelete').then(() => {
  console.log('Connected to MongoDB');
}).catch(err => {
  console.error('MongoDB connection error:', err);
});

// Define schema with isDeleted flag
const itemSchema = new mongoose.Schema({
  name: String,
  isDeleted: { type: Boolean, default: false }
});
const Item = mongoose.model('Item', itemSchema);

// Soft delete route
app.delete('/items/:id', async (req, res) => {
  const id = req.params.id;
  const result = await Item.updateOne({ _id: id }, { isDeleted: true });
  if (result.modifiedCount === 0) {
    return res.status(404).send({ message: 'Item not found' });
  }
  res.send({ message: 'Item soft deleted' });
});

// Get all non-deleted items
app.get('/items', async (req, res) => {
  const items = await Item.find({ isDeleted: false });
  res.send(items);
});

// Start server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});
Output
Server running on http://localhost:3000 // After adding items and deleting one: // GET /items returns only items with isDeleted: false
⚠️

Common Pitfalls

  • Forgetting to filter out soft deleted records in queries causes deleted data to show.
  • Not adding the isDeleted field to the schema or model leads to errors.
  • Using deleteOne or remove instead of updating the flag deletes data permanently.
  • Not handling errors when updating the flag can cause silent failures.
javascript
/* Wrong: Permanently deleting item */
app.delete('/items/:id', async (req, res) => {
  await Item.deleteOne({ _id: req.params.id });
  res.send({ message: 'Item deleted permanently' });
});

/* Right: Soft delete by updating flag */
app.delete('/items/:id', async (req, res) => {
  const result = await Item.updateOne({ _id: req.params.id }, { isDeleted: true });
  if (result.modifiedCount === 0) {
    return res.status(404).send({ message: 'Item not found' });
  }
  res.send({ message: 'Item soft deleted' });
});
📊

Quick Reference

  • Add a boolean isDeleted field to your data model.
  • Update isDeleted to true instead of deleting.
  • Filter queries to exclude records where isDeleted is true.
  • Handle errors when updating the flag.

Key Takeaways

Use a boolean flag like isDeleted to mark records as deleted instead of removing them.
Always filter your queries to exclude soft deleted records to hide them from users.
Never use deleteOne or remove if you want to keep data for recovery or audit.
Handle update errors to avoid silent failures during soft delete.
Soft delete helps keep data safe while allowing it to be restored or audited later.