⇦ Back

WDX-180

Web Development X

Creating Products (CREATE)

The First Half of CRUD

Reading data is useful.

Creating data is where applications become interactive.

Up until now, every product in our system was inserted manually through:

  INSERT INTO products (...)

Today we allow users to create products from the browser.

This is our first complete end-to-end workflow:

  flowchart LR

  A[Browser Form]
  B[Express Route]
  C[Validation]
  D[(SQLite)]

  A --> B
  B --> C
  C --> D
  D --> B
  B --> A

This pattern will become the foundation for nearly everything we build.

Learning Objectives

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

Part 1 — Understanding Forms

Most web applications revolve around forms.

Examples:

Application Form Purpose
Amazon Add Product
GitHub Create Repository
Facebook Create Post
YouTube Upload Video
CMS Create Product

A form allows users to send data to a server.


Anatomy of a Form

  <form action="/products/create" method="post">

      <input
          type="text"
          name="name"
      >

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

  </form>

Important Attributes

action

Defines where data is sent.

  action="/products/create"

method

Defines how data is sent.

  method="post"

GET vs POST

GET

Used for:

  Reading data

Example:

  GET /products

POST

Used for:

  Creating data

Example:

  POST /products/create

Rule of thumb:

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

Part 2 — Showing the Form

Route:

  router.get('/create', (req, res) => {

      res.render('products/create', {
          title: 'Create Product'
      });

  });

View:

  <h2>Create Product</h2>

  <form
      action="/products/create"
      method="post"
  >

      <div>

          <label>Name</label>

          <input
              type="text"
              name="name"
          >

      </div>

      <div>

          <label>Description</label>

          <textarea
              name="description"
          ></textarea>

      </div>

      <div>

          <label>Price</label>

          <input
              type="number"
              step="0.01"
              name="price"
          >

      </div>

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

  </form>

Part 3 — Understanding req.body

When a form is submitted:

  <input name="name">

becomes:

  req.body.name

Example:

  <input
      name="name"
      value="Keyboard"
  >

Results in:

  req.body

containing:

  {
      name: 'Keyboard'
  }

Body Parsing Middleware

Required:

index.js:

  app.use(
      express.urlencoded({
          extended: true
      })
  );

Without it:

  req.body

will be:

  undefined

Which leads to approximately seventeen minutes of confusion and a Stack Overflow search.

Part 4 — Handling Form Submission

Route:

  router.post('/create', (req, res) => {

      console.log(req.body);

      res.send('Form received');

  });

Example Submission

  {
      name: 'Keyboard',
      description: 'RGB Keyboard',
      price: '89.99'
  }

Notice:

  price

is a string.

Everything from forms arrives as text.

Always.

Even numbers.

Even dates.

Even your hopes and dreams.

Part 5 — Inserting into SQLite

SQL:

  INSERT INTO products (
      name,
      description,
      price
  )
  VALUES (?, ?, ?)

Route:

  router.post('/create', (req, res) => {

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

      const stmt = db.prepare(`
          INSERT INTO products (
              name,
              description,
              price
          )
          VALUES (?, ?, ?)
      `);

      stmt.run(
          name,
          description,
          price
      );

      res.redirect('/products');

  });

Why Redirect?

Bad:

  Submit Form
  Refresh Browser
  Submit Again
  Duplicate Record

Good:

  Submit Form
  Redirect
  Refresh Safely

POST-Redirect-GET Pattern

  flowchart LR

  A[Form]
  B[POST]
  C[(Database)]
  D[Redirect]
  E[GET]

  A --> B
  B --> C
  C --> D
  D --> E

Most professional applications use this pattern.

Part 6 — Input Validation

Never trust user input.

Never.

Not even your own.

Especially your own.

Bad Submission:

  {
      name: '',
      price: ''
  }

Bad Submission:

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

Validation:

  if (!name) {

      return res.render(
          'products/create',
          {
              title: 'Create Product',
              error: 'Name is required.'
          }
      );

  }

Price Validation

  const numericPrice = Number(price);

  if ( Number.isNaN(numericPrice)) {

      return res.render(
          'products/create',
          {
              title: 'Create Product',
              error: 'Price must be numeric.'
          }
      );

  }

Positive Price Validation

  if (numericPrice <= 0) {

      return res.render(
          'products/create',
          {
              title: 'Create Product',
              error:
                  'Price must be greater than zero.'
          }
      );

  }

Part 7 — Displaying Validation Errors

Route:

  res.render(
      'products/create',
      {
          title: 'Create Product',
          error: 'Name is required.'
      }
  );

View:

  <% if ( typeof error !== "undefined" ) { %>

  <div class="error">

      <%= error %>

  </div>

  <% } %>

Result:

  Name is required.

appears above the form.

Part 8 — Preserving Form Data

A common beginner mistake:

User submits:

  Name
  Description
  Price

Validation fails.

Everything disappears.

User cries.

Better approach:

  res.render(
      'products/create',
      {
          title: 'Create Product',
          error: 'Invalid data',
          product: req.body
      }
  );

View:

  <input name="name" value="<%= product?.name || '' %>">

Textarea:

  <textarea name="description">
    <%= product?.description || '' %>
  </textarea>

Now the form remains populated.

Professional UX.

Part 9 — Creating a Repository Function

Instead of:

  router.post(...)

containing SQL,

move SQL into:

  db/productRepository.js

Example:

  function create(
      name,
      description,
      price
  ) {

      const stmt = db.prepare(`
          INSERT INTO products (
              name,
              description,
              price
          )
          VALUES (?, ?, ?)
      `);

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

  }

Route:

  productRepository.create(
      name,
      description,
      numericPrice
  );

Cleaner.

More maintainable.

Easier to test.

Part 10 — User Experience Improvements

After creation:

  Product saved successfully

is helpful.

Simple example:

routes/products.js:

  // POST /products/create
  res.redirect('/products?success=1');
  // GET /products
  const success = req.query.success;
  res.render('products/list', {
    success,
    // ...
  });

Display:

  <% if ( success ) { %>

  <div style="background: limegreen; color: white; display: inline-block">
      Product created successfully.
  </div>

  <% } %>

Later we’ll introduce:

  Flash Messages

for this.

Part 11 — Understanding SQL INSERT Results

Example:

  const result = stmt.run(...);

Result:

  {
      changes: 1,
      lastInsertRowid: 11
  }

Useful when redirecting:

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

Common Beginner Mistakes

Missing Middleware

  req.body

undefined.

Forgetting Validation

Never trust:

  req.body

directly.

Storing Strings as Numbers

Always convert:

  Number(price)

Refreshing POST Pages

Use:

  res.redirect(...)

Mixing Validation and SQL

Separate concerns whenever possible.

Bonus Challenge

After creating a product:

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

where:

  id

is the newly created record.

Users should immediately land on the product page they just created.

Key Takeaways

Today you learned:

This is the first truly interactive feature in the CMS. Users can now create data through the UI, which means the application has crossed an important threshold: it is no longer a static viewer of information—it is a system for managing information.


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