⇦ Back

WDX-180

Web Development X

Updating Products (UPDATE)

Completing the Second Quarter of CRUD

Creating data is exciting.

Updating data is where reality begins.

In the real world:

Lots of mistakes.

If your application can only create products, eventually users start creating:

Keyboard
Keyboard v2
Keyboard v3
Keyboard FINAL
Keyboard FINAL FINAL
Keyboard FINAL USE THIS ONE

Congratulations.

You’ve accidentally recreated Microsoft Word document versioning.

Today we learn how to update existing records properly.


Learning Objectives

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


What We Are Building

Current:

flowchart TD

A[Product Page]

A --> B[View Product]

After today:

flowchart TD

A[Product Page]

A --> B[Edit Product]

B --> C[Update Database]

C --> D[View Updated Product]

Part 1 — Understanding UPDATE

Creating records:

INSERT INTO products (...)

adds new data.


Updating records:

UPDATE products
SET ...
WHERE ...

modifies existing data.


Example:

Before:

Mechanical Keyboard

$89.99

After:

Mechanical Keyboard Pro

$99.99

Same record.

New values.


Part 2 — SQL UPDATE

Basic syntax:

UPDATE products

SET
    name = ?,
    description = ?,
    price = ?

WHERE id = ?

Very important:

WHERE id = ?

Without it:

UPDATE products
SET price = 0

becomes:

Every product now costs $0

Customers love this.

Management usually does not.


Part 3 — The Edit Route

URL:

/products/edit/5

Route:

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

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

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

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

});

Why Load the Product First?

Users need to see:

Current Values

before editing.


Without loading:

Name:

[____________]

With loading:

Name:

[Mechanical Keyboard]

Much better.


Part 4 — Building the Edit Form

views/products/edit.ejs

<h2>Edit Product</h2>

<form
    action="/products/edit/<%= product.id %>"
    method="post"
>

    <div>

        <label>Name</label>

        <input
            type="text"
            name="name"
            value="<%= product.name %>"
        >

    </div>

    <div>

        <label>Description</label>

        <textarea
            name="description"
        ><%= product.description %></textarea>

    </div>

    <div>

        <label>Price</label>

        <input
            type="number"
            step="0.01"
            name="price"
            value="<%= product.price %>"
        >

    </div>

    <button type="submit">
        Save Changes
    </button>

</form>

Understanding Pre-Populated Forms

The form displays:

value="<%= product.name %>"

instead of:

value=""

This is how nearly every edit form on the internet works.


Part 5 — Processing Updates

Route:

router.post('/edit/:id', (req, res) => {

    const {
        name,
        description,
        price
    } = req.body;

    const id =
        req.params.id;

    productRepository.update(
        id,
        name,
        description,
        price
    );

    res.redirect(
        `/products/${id}`
    );

});

Repository Function

function update(
    id,
    name,
    description,
    price
) {

    const stmt = db.prepare(`
        UPDATE products

        SET
            name = ?,
            description = ?,
            price = ?

        WHERE id = ?
    `);

    return stmt.run(
        name,
        description,
        price,
        id
    );

}

What Does run() Return?

Example:

{
    changes: 1
}

Meaning:

1 row updated

If:

{
    changes: 0
}

then nothing changed.

Possibly because the product doesn’t exist.


Part 6 — Validation

Updating requires validation just like creating.

❌ Bad:

{
    name: '',
    price: ''
}

❌ Bad:

{
    name: 'Keyboard',
    price: -100
}

Validation:

if (!name) {

    return res.render(
        'products/edit',
        {
          title: "Edit Product",
          error: 'Name is required.',
          product: req.body
        }
    );

}

Price Validation:

const numericPrice = Number(price);

if (Number.isNaN(numericPrice)) {
    return res.render(...);
}

Part 7 — Preserving User Input

Suppose:

Name OK

Price Invalid

Validation fails.


Bad UX:

All form data disappears

Good UX:

res.render(
    'products/edit',
    {
        error: 'Invalid price',
        product: {
            id,
            name,
            description,
            price
        }
    }
);

The form remains populated.

Users remain calm.


Part 8 — Adding Edit Links

Product page:

<a href="/products/edit/<%= product.id %>">Edit Product</a>

List page:

<a href="/products/edit/<%= product.id %>">Edit</a>

Now editing is accessible everywhere.


Part 9 — Reusing Forms

Notice:

create.ejs

and

edit.ejs

look almost identical.

Professional developers avoid duplication.

Example Partial:

views/products/_form.ejs
<input name="name" value="<%= product?.name || '' %>">

Create:

<%- include('_form',{ product: {} }) %>

Edit:

<%- include('_form',{ product }) %>

One form.

Two use cases.

Less maintenance.


Part 10 — Understanding Idempotency

Interesting concept:

Create:

POST /products/create

twice

creates:

Two Products

Update:

POST /products/edit/5

with same values twice

results in:

Same Product State

This property is called:

Idempotency

A useful concept for APIs.


Part 11 — Defensive Programming

Imagine:

/products/edit/999999

Repository:

const result = productRepository.update(...);

Check:

if ( result.changes === 0) {

    return res
        .status(404)
        .render('404');

}

Never assume records exist.

Verify.


Part 12 — Audit Thinking

Imagine:

Original Price

$100

User changes:

$10

Question:

Who changed it?
When?
Why?

Most CRUD tutorials ignore this.

Real businesses care deeply.

Future systems often add:

updated_at
updated_by

columns.

We’ll keep things simple for now.


Common Beginner Mistakes

Forgetting WHERE

Bad:

UPDATE products
SET price = 0

Dangerous.


Not Checking Existence

Bad:

update(id)

without verifying product exists.


Repeating Form Code

Create and Edit forms often become duplicates.

Extract shared partials.


Skipping Validation

Updates require validation just as much as creation.


Trusting Route Parameters

Validate:

req.params.id

before using them.


Bonus Challenge

Add:

updated_at

to the table.

Schema:

ALTER TABLE products

ADD COLUMN updated_at DATETIME;

Update query:

UPDATE products

SET
    name = ?,
    description = ?,
    price = ?,
    updated_at = CURRENT_TIMESTAMP

WHERE id = ?

Display:

Last Updated:

on the product page.

This is the first step toward real audit tracking.


Key Takeaways

Today you learned:

At this point, your CMS can:

Create Products
Read Products
Update Products

Three quarters of CRUD are complete.

The application is beginning to resemble the core of a real content management system rather than a collection of isolated pages.


⚠️ 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