TypeScript Tutorial: Building a Simple Web-Based E-commerce Product Catalog

In today’s digital age, e-commerce has exploded, with businesses of all sizes striving to showcase their products online. A crucial element of any e-commerce platform is a well-designed product catalog. This tutorial will guide you through building a simple, yet functional, web-based e-commerce product catalog using TypeScript. We’ll focus on the fundamental concepts, enabling you to understand how to structure your data, display it effectively, and even add basic filtering capabilities. This project will not only teach you TypeScript but also provide a practical understanding of how to build a core component of many online stores.

Why TypeScript for an E-commerce Product Catalog?

TypeScript, a superset of JavaScript, brings several advantages to this project:

  • Type Safety: TypeScript’s static typing helps catch errors early in the development process, reducing debugging time and improving code quality.
  • Code Readability: Type annotations make your code easier to understand and maintain, especially in larger projects.
  • Enhanced Developer Experience: TypeScript provides better autocompletion, refactoring, and other features in modern code editors.
  • Scalability: TypeScript code is easier to scale and refactor as your product catalog grows and evolves.

By using TypeScript, you’ll create a more robust and maintainable product catalog compared to using plain JavaScript.

Setting Up Your Development Environment

Before we dive into the code, you’ll need to set up your development environment. Here’s what you’ll need:

  • Node.js and npm (or yarn): These are essential for managing project dependencies and running the development server. Download and install them from nodejs.org.
  • A Code Editor: Visual Studio Code (VS Code) is highly recommended due to its excellent TypeScript support. You can download it from code.visualstudio.com.
  • A Web Browser: Chrome, Firefox, or any modern browser will work.

Once you have these installed, create a new project directory and initialize a new npm project:

mkdir product-catalog
cd product-catalog
npm init -y

This will create a package.json file in your project directory. Next, install TypeScript as a development dependency:

npm install typescript --save-dev

Now, create a tsconfig.json file in your project directory. This file configures the TypeScript compiler. You can generate a basic one using the following command:

npx tsc --init

Open tsconfig.json and modify it to suit your needs. Here’s a recommended configuration:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}
  • target: Specifies the JavaScript version to compile to (es5 for broader browser compatibility).
  • module: Specifies the module system (commonjs for Node.js).
  • outDir: Specifies the output directory for compiled JavaScript files.
  • esModuleInterop: Enables interoperability between CommonJS and ES modules.
  • forceConsistentCasingInFileNames: Enforces consistent casing in file names.
  • strict: Enables strict type checking.
  • skipLibCheck: Skips type checking of declaration files.
  • include: Specifies which files to include in the compilation.

Creating the Product Data Model

Let’s define the structure of our product data. Create a new directory called src and a file named product.ts inside it. In product.ts, we’ll define a TypeScript interface to represent a product:

// src/product.ts
export interface Product {
  id: number;
  name: string;
  description: string;
  price: number;
  imageUrl: string;
  category: string; // e.g., "Electronics", "Clothing"
  inStock: boolean;
}

This interface defines the properties of a product: id, name, description, price, imageUrl, category, and inStock. Using an interface ensures that our product data conforms to a specific structure, which helps prevent errors.

Implementing the Product Catalog Logic

Create a file named catalog.ts in the src directory. This file will contain the logic for managing our product catalog.

// src/catalog.ts
import { Product } from './product';

// Sample product data (replace with your actual data)
const products: Product[] = [
  {
    id: 1,
    name: "Laptop",
    description: "A powerful laptop for work and play.",
    price: 1200,
    imageUrl: "laptop.jpg",
    category: "Electronics",
    inStock: true,
  },
  {
    id: 2,
    name: "T-Shirt",
    description: "Comfortable cotton t-shirt.",
    price: 25,
    imageUrl: "tshirt.jpg",
    category: "Clothing",
    inStock: true,
  },
  {
    id: 3,
    name: "Headphones",
    description: "Noise-canceling headphones for immersive audio.",
    price: 150,
    imageUrl: "headphones.jpg",
    category: "Electronics",
    inStock: false,
  },
];

// Function to get all products
export function getAllProducts(): Product[] {
  return products;
}

// Function to filter products by category
export function filterProductsByCategory(category: string): Product[] {
  return products.filter((product) => product.category === category);
}

// Function to get a product by ID
export function getProductById(id: number): Product | undefined {
  return products.find((product) => product.id === id);
}

Let’s break down this code:

  • We import the Product interface from ./product.
  • We create a sample products array. In a real application, you would fetch this data from a database or API.
  • getAllProducts(): Returns all products in the catalog.
  • filterProductsByCategory(category: string): Filters products based on the provided category.
  • getProductById(id: number): Retrieves a product by its ID. It returns undefined if the product is not found.

Building the User Interface (UI) with HTML and JavaScript

Now, let’s create a simple HTML page to display our product catalog. Create an index.html file in the root directory of your project:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Product Catalog</title>
  <style>
    .product-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
    }
    .product-card {
      border: 1px solid #ccc;
      padding: 10px;
      width: 200px;
    }
    img {
      max-width: 100%;
      height: auto;
    }
  </style>
</head>
<body>
  <h1>Product Catalog</h1>
  <div id="product-catalog" class="product-container"></div>

  <script src="./dist/index.js"></script>
</body>
</html>

This HTML sets up the basic structure of the page, including a heading and a div with the ID product-catalog where we’ll display the products. It also includes a basic CSS style to help with presentation. Finally, it includes a script tag that links to our compiled JavaScript file (dist/index.js). Next, we’ll create the index.ts file to handle the UI interaction.

Create an index.ts file in the src directory:

// src/index.ts
import { getAllProducts, filterProductsByCategory, Product } from './catalog';

function renderProduct(product: Product): string {
  return `
    <div class="product-card">
      <img src="${product.imageUrl}" alt="${product.name}">
      <h3>${product.name}</h3>
      <p>Price: $${product.price}</p>
      <p>Category: ${product.category}</p>
      <p>In Stock: ${product.inStock ? 'Yes' : 'No'}</p>
    </div>
  `;
}

function displayProducts(products: Product[]): void {
  const productCatalog = document.getElementById('product-catalog');
  if (!productCatalog) {
    console.error('Product catalog element not found.');
    return;
  }
  productCatalog.innerHTML = products.map(renderProduct).join('');
}

// Initial display of all products
displayProducts(getAllProducts());

// Example: Filter products by category (e.g., "Electronics")
// const electronicsProducts = filterProductsByCategory('Electronics');
// displayProducts(electronicsProducts);

Let’s break down the code in index.ts:

  • We import the necessary functions and types from ./catalog.
  • renderProduct(product: Product): string: This function takes a Product object and returns an HTML string representing a product card. It uses template literals to create the HTML dynamically.
  • displayProducts(products: Product[]): void: This function takes an array of Product objects, generates the HTML for each product using renderProduct, and inserts it into the product-catalog div.
  • The last two lines call displayProducts, initially rendering all products. The commented-out code shows how to filter products by category.

Compiling and Running the Application

Now, let’s compile our TypeScript code into JavaScript. Open your terminal and run the following command in the project directory:

tsc

This command will use the TypeScript compiler to transpile the .ts files into .js files in the dist directory, based on the configuration in tsconfig.json. After the compilation is successful, open index.html in your web browser. You should see the product catalog displayed, populated with the sample data. If you don’t see anything, open your browser’s developer console (usually by pressing F12) to check for any errors.

Adding Features and Enhancements

Now that we have a basic product catalog, let’s add some features to make it more useful:

1. Dynamic Data Loading

Instead of hardcoding product data, you’ll likely want to fetch it from a database or API. Here’s an example of how you might fetch data using the fetch API (modify this to connect to your specific API or database):

// src/catalog.ts (modified)
// ... (existing imports and code)

async function fetchProducts(): Promise<Product[]> {
  try {
    const response = await fetch('/api/products'); // Replace with your API endpoint
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data: Product[] = await response.json();
    return data;
  } catch (error) {
    console.error('Could not fetch products:', error);
    return [];
  }
}

export async function getAllProducts(): Promise<Product[]> {
  const products = await fetchProducts();
  return products;
}

// ... (rest of the code)

In this modification to catalog.ts:

  • We define an async function fetchProducts() that uses the fetch API to retrieve product data. Make sure to replace '/api/products' with the actual URL of your API endpoint.
  • The getAllProducts() function is now also asynchronous and calls fetchProducts() to get the data.
  • We’ve added error handling to gracefully handle potential API errors.

In index.ts, you’ll need to update the initial display to handle the asynchronous nature of fetching the data:

// src/index.ts (modified)
// ... (existing imports and code)

async function init() {
  const products = await getAllProducts();
  displayProducts(products);
}

init();

// Example: Filter products by category (e.g., "Electronics")
// const electronicsProducts = filterProductsByCategory('Electronics');
// displayProducts(electronicsProducts);

Here, we’ve wrapped the getAllProducts() call in an async function init() and called it. This ensures that the products are fetched before the UI is rendered.

2. Filtering by Category (UI Enhancement)

Let’s add a dropdown menu to filter products by category. Modify your index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Product Catalog</title>
  <style>
    .product-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
    }
    .product-card {
      border: 1px solid #ccc;
      padding: 10px;
      width: 200px;
    }
    img {
      max-width: 100%;
      height: auto;
    }
  </style>
</head>
<body>
  <h1>Product Catalog</h1>
  <label for="category-filter">Filter by Category: </label>
  <select id="category-filter">
    <option value="all">All</option>
    <option value="Electronics">Electronics</option>
    <option value="Clothing">Clothing</option>
  </select>
  <div id="product-catalog" class="product-container"></div>

  <script src="./dist/index.js"></script>
</body>
</html>

Then, modify index.ts to handle the filter selection:

// src/index.ts (modified)
// ... (existing imports and code)

async function init() {
  const products = await getAllProducts();
  displayProducts(products);

  const categoryFilter = document.getElementById('category-filter') as HTMLSelectElement;
  if (categoryFilter) {
    categoryFilter.addEventListener('change', () => {
      const selectedCategory = categoryFilter.value;
      let filteredProducts: Product[];

      if (selectedCategory === 'all') {
        filteredProducts = products;
      } else {
        filteredProducts = filterProductsByCategory(selectedCategory);
      }
      displayProducts(filteredProducts);
    });
  }
}

init();

Here’s what changed:

  • We added a <select> element with the ID category-filter to the HTML, along with the options.
  • In index.ts, we get a reference to the select element.
  • We add an event listener to the select element to listen for changes.
  • When the user selects a different category, we filter the products accordingly and re-render the catalog.

3. Product Details Page (Basic)

To display more information about each product, you can create a product details page. First, add a link to the product card in renderProduct in index.ts:

// src/index.ts (modified renderProduct)
function renderProduct(product: Product): string {
  return `
    <div class="product-card">
      <a href="product-details.html?id=${product.id}">
        <img src="${product.imageUrl}" alt="${product.name}">
        <h3>${product.name}</h3>
        <p>Price: $${product.price}</p>
        <p>Category: ${product.category}</p>
        <p>In Stock: ${product.inStock ? 'Yes' : 'No'}</p>
      </a>
    </div>
  `;
}

Then, create a new HTML file named product-details.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Product Details</title>
    <style>
        img {
            max-width: 100%;
            height: auto;
        }
    </style>
</head>
<body>
    <h1>Product Details</h1>
    <div id="product-details"></div>
    <script src="./dist/product-details.js"></script>
</body>
</html>

Finally, create a product-details.ts file in the src directory:

// src/product-details.ts
import { getProductById, Product } from './catalog';

function getProductIdFromURL(): number | null {
    const params = new URLSearchParams(window.location.search);
    const productId = params.get('id');
    return productId ? parseInt(productId, 10) : null;
}

function renderProductDetails(product: Product): string {
    return `
        <img src="${product.imageUrl}" alt="${product.name}">
        <h2>${product.name}</h2>
        <p>Description: ${product.description}</p>
        <p>Price: $${product.price}</p>
        <p>Category: ${product.category}</p>
        <p>In Stock: ${product.inStock ? 'Yes' : 'No'}</p>
    `;
}

async function displayProductDetails() {
    const productId = getProductIdFromURL();
    if (!productId) {
        console.error('Product ID not found in URL.');
        return;
    }

    const product = await getProductById(productId);

    if (!product) {
        console.error('Product not found.');
        return;
    }

    const productDetailsElement = document.getElementById('product-details');
    if (productDetailsElement) {
        productDetailsElement.innerHTML = renderProductDetails(product);
    }
}

displayProductDetails();

Here’s what changed:

  • In index.ts, we added a link around the product card. This link points to product-details.html and includes the product ID as a query parameter.
  • product-details.html is a basic HTML page with a placeholder for the product details.
  • In product-details.ts:
    • getProductIdFromURL(): This function extracts the product ID from the URL’s query parameters.
    • renderProductDetails(): This function creates the HTML for the product details page.
    • displayProductDetails(): This function gets the product ID, fetches the product data, and displays it on the page.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Incorrect TypeScript Setup: Double-check your tsconfig.json file to ensure it’s configured correctly. Pay close attention to the target, module, and outDir options. Make sure you are compiling your TypeScript files before running your HTML file.
  • Typo Errors: TypeScript helps catch typos, but you still need to be careful! Double-check your variable and function names.
  • Incorrect File Paths: Make sure your file paths (e.g., in import statements and <script src=""> tags) are correct.
  • Asynchronous Data Fetching Issues: When working with asynchronous operations (like fetching data from an API), make sure you’re using async/await correctly and handling potential errors. Ensure your functions that depend on data from the API are also marked as `async`.
  • HTML Element Selection Problems: If you’re having trouble accessing HTML elements, make sure your element IDs are correct and that the script is running after the HTML elements have loaded. Consider placing your <script> tag just before the closing </body> tag.

Key Takeaways

  • TypeScript Enhances Development: TypeScript improves code quality, readability, and maintainability.
  • Data Modeling is Crucial: Defining interfaces and data structures is essential for organizing your data.
  • UI Updates and Event Handling: Dynamically updating the UI based on user interactions is key to a functional web application.
  • Asynchronous Operations are Common: Fetching data from external sources requires understanding asynchronous programming.
  • Error Handling is Important: Implementing error handling makes your application more robust.

FAQ

  1. Why use TypeScript instead of JavaScript? TypeScript offers features like static typing, which helps catch errors early, improves code readability, and makes it easier to maintain large projects. It also provides better tooling support in code editors.
  2. How do I handle errors when fetching data from an API? Use a try...catch block to handle potential errors during the API call. Check the response status and handle errors accordingly.
  3. How can I deploy this application? You can deploy your application to a web server. You’ll need to compile your TypeScript code into JavaScript, and then upload the HTML, JavaScript, and any other assets (like images) to the server. Services like Netlify, Vercel, and GitHub Pages provide easy ways to deploy static websites.
  4. How do I add more features to my product catalog? You can add features such as:
    • Product search functionality
    • Shopping cart integration
    • User authentication
    • Product reviews and ratings
    • Responsive design
  5. Where can I learn more about TypeScript? The official TypeScript documentation (typescriptlang.org/docs/) is an excellent resource. You can also find many tutorials and courses online on platforms like Udemy, Coursera, and freeCodeCamp.

Building a web-based e-commerce product catalog using TypeScript is a great way to learn and apply fundamental web development concepts. This tutorial has provided a solid foundation, from setting up your development environment to creating the user interface and adding features like filtering and product details. By following these steps and exploring the additional enhancements, you can create a functional and maintainable product catalog. Remember that this is just the beginning. The world of web development is vast, and there’s always more to learn. Keep experimenting, exploring new technologies, and refining your skills. With each project, your understanding and abilities will grow, allowing you to build increasingly complex and impressive applications. Embrace the learning process, and enjoy the journey of becoming a proficient TypeScript developer!