⇦ Back

WDX-180

Web Development X

Deleting Products (DELETE)

Completing CRUD

Creating data is easy.

Updating data is common.

Deleting data is where developers become nervous.

There is a reason many enterprise systems make deletion difficult.

Deleting data is often irreversible.

One careless query can transform:

  10,000 products

into:

  0 products

faster than you can say:

  "Do we have backups?"

Today we implement the final piece of CRUD:

Learning Objectives

By the end of this lesson, students will be able to:

Part 1 — Understanding DELETE

SQL provides:

  DELETE

for removing rows.

Example:

  DELETE
  FROM products
  WHERE id = 5

Result:

  Product 5 no longer exists

Without:

  WHERE id = 5

things become exciting.

Example:

  DELETE
  FROM products

Result:

  Entire table emptied

Every backend developer eventually learns to fear running SQL against production.

Usually only once.

Part 2 — Why Not Delete with GET?

Bad:

  <a href="/products/delete/5">
      Delete
  </a>

Why?

Because:

  GET

should not change data.

Search engines, crawlers, prefetchers, and browser tools may visit links automatically.

You don’t want:

  Googlebot

accidentally deleting your inventory.

Rule:

Action Method
Read GET
Create POST
Update POST / PUT
Delete POST / DELETE

Part 3 — Confirmation Page

First step:

  GET /products/delete/:id

Purpose:

  Show confirmation

Route:

  router.get(
      '/delete/:id',
      (req, res) => {

          const product = productRepository.findById(req.params.id);

          if (!product) {
              return res
                  .status(404)
                  .render('404');
          }

          res.render('products/delete',
              {
                title: "Delete Product",
                product
              }
          );

      }
  );

Confirmation View

views/products/delete.ejs:

  <h2>Delete Product</h2>
  <p>Are you sure you want
  to delete:
  <strong><%= product.name %></strong>?
  </p>

  <form
      method="post"
      action="/products/delete/<%= product.id %>"
  >
      <button type="submit">
          Delete
      </button>
  </form>

Result:

  Are you sure?

before deletion.

A simple but powerful safety mechanism.

Part 4 — Processing Deletion

Route:

  router.delete('/delete/:id',
      (req, res) => {
          productRepository.deleteById(req.params.id);
          res.redirect('/products');
      }
  );

Repository:

  function deleteById(id) {

      const stmt = db.prepare(`
          DELETE
          FROM products
          WHERE id = ?
      `);

      return stmt.run(id);

  }

Notice:

  WHERE id = ?

Always.

No exceptions.

Part 5 — Checking Results

Repository:

  const result = stmt.run(id);

Returns:

  {
      changes: 1
  }

Meaning:

  One record deleted

Or:

  {
      changes: 0
  }

Meaning:

  Nothing deleted

Handle properly:

  // routes/products.js POST /delete/:id
  if ( result.changes === 0 ) {
      return res
          .status(404)
          .render('404', { title: "Product not found" });
  }

Part 6 — Adding Delete Buttons

Product page:

  <a href="/products/delete/<%= product.id %>">
    Delete Product
  </a>

List page:

  <a href="/products/delete/<%= product.id %>">
    Delete
  </a>

Now every product can be removed.

Dangerous.

Useful.

Mostly dangerous.

Part 7 — Hard Deletes

Current behavior:

  DELETE
  FROM products
  WHERE id = ?

This is called:

  Hard Delete

Result:

  Data is physically removed

Advantages:

Disadvantages:

Part 8 — Soft Deletes

Many applications never truly delete.

Instead:

  ALTER TABLE products

  ADD COLUMN deleted_at DATETIME;

Deleting becomes:

  UPDATE products

  SET deleted_at = CURRENT_TIMESTAMP

  WHERE id = ?

Record remains:

  In database

but is hidden.

Query:

  SELECT *
  FROM products
  WHERE deleted_at IS NULL

This is called:

  Soft Delete

Why Large Systems Use Soft Deletes

Imagine:

  Employee deletes 500 products

Hard delete:

  Restore from backup

Potentially painful.

Soft delete:

  UPDATE products

  SET deleted_at = NULL

Done.

Many enterprise systems default to soft deletes.

Part 9 — Recycle Bin Pattern

Some applications provide:

  Trash
  Recycle Bin
  Archive

Workflow:

  flowchart LR

  A[Active Product]

  A --> B[Soft Delete]

  B --> C[Recycle Bin]

  C --> D[Restore]

  C --> E[Permanent Delete]

Examples:

Users appreciate second chances.

Developers appreciate third chances.

Part 10 — Foreign Key Considerations

Suppose later:

  Products
  Orders

exist.

Question:

  Can we delete a product
  referenced by orders?

Potential problem:

  Order references
  missing product

Broken data.

Future solutions:

  ON DELETE CASCADE

or:

  ON DELETE RESTRICT

We’ll revisit this when relationships are introduced.

Part 11 — Security Thinking

Never trust:

  req.params.id

Validate:

  const id = Number(req.params.id);
  if ( !Number.isInteger(id) ) {
      return res
          .status(400)
          .send(
              'Invalid ID'
          );

  }

Never assume:

  Delete requests
  are legitimate

Authentication and authorization will eventually become critical.

Part 12 — UX Improvements

After deletion:

  Product deleted successfully

is helpful.

Simple redirect:

  res.redirect('/products?deleted=1');

View:

  <% if(deleted) { %>

  <div>

  Product deleted.

  </div>

  <% } %>

Users should always know what happened.

Part 13 — RESTful Perspective

Current:

  POST /products/delete/5

Works.

True REST:

  DELETE /products/5

Browsers don’t support:

  <form method="delete">

natively.

So many applications use:

  POST

for deletes.

This is normal.

Common Beginner Mistakes

Forgetting WHERE

Catastrophic.

❌ Bad:

  DELETE
  FROM products

✅ Good:

  DELETE
  FROM products
  WHERE id = ?

Deleting with GET

❌ Bad:

  GET /delete/5

Never perform destructive actions via GET.

No Confirmation Step

Accidental clicks happen.

Confirmation pages save data.

Not Checking changes

Always verify:

  result.changes

No Recovery Strategy

Soft deletes often provide a safer long-term solution.

Bonus Challenge

Implement soft deletes.

Add:

  deleted_at DATETIME

to the table.


Update deletion:

  UPDATE products

  SET deleted_at = CURRENT_TIMESTAMP

  WHERE id = ?

Update queries:

  WHERE deleted_at IS NULL

for all product listings.

Add:

  Recycle Bin

page showing deleted products.

Add:

  Restore Product

functionality.

Congratulations.

You’ve just implemented a simplified version of what many production systems use.

Key Takeaways

Today you learned:

Your CMS now supports the complete CRUD lifecycle:

  Create
  Read
  Update
  Delete

This is a major milestone. Most business applications are, at their core, sophisticated variations of CRUD systems with additional layers of validation, permissions, workflows, and automation built on top.

At this stage, students should be capable of building a fully functional data management application from scratch using Express, EJS, and SQLite.


⚠️ A large part of the content of this module was created using Generative AI (ChatGPT). The synthetic (AI-generated) content was reviewed and curated by Kostas Minaidis.


Project maintained by in-tech-gration Hosted on GitHub Pages — Theme by mattgraham