Svelte eCommerce

Building an eCommerce shopping cart with Svelte


In this article, you will learn some significant concepts in svelte by building an eCommerce shopping cart with Svelte.

Svelte is a modern JavaScript compiler that allows you to write easy-to-understand JavaScript code that is then compiled to highly efficient code that runs in the browser.

The framework is similar to React or Vue, but we don’t have any dependencies in Svelte. That means it will not take any time to interpret our code to get pure JavaScript before run-time.

All you have to do is write your code in regular JavaScript with some svelte concept, and svelte will compile that code to a highly optimized JavaScript code bundle that runs directly in the browser.

Svelte ecommerce cart app Live Demo

We will start by creating a svelte cli project.

We will create a new directory called sveltecart in our home directory using our terminal for the project.

npx degit sveltejs/template sveltecart

Let’s go to our code editor and start modifying the app. I will recommend using Vscode. make sure you have Svelte extension installed.

Taking a look at the  package.json file reveals two things.

The first is that Svelte uses Rollup by default for module bundling which is an alternative for webpack. If desired, it can be changed to use Webpack or Parcel. The second is that Svelte apps have no required runtime dependencies, only devDependencies.

The most important starting files in this project are:

  • src/main.js
  • src/App.svelte
  • public/index.html

The file public/index.html contains the following:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width,initial-scale=1'>
    <title>Svelte app</title>
    <link rel='icon' type='image/png' href='/favicon.png'>
    <link rel='stylesheet' href='/global.css'>
    <link rel='stylesheet' href='/build/bundle.css'>
    <script defer src='/build/bundle.js'></script>
</head>
<body>
</body>
</html>

Note how it calls in two CSS files and one JavaScript file from the same public directory. The global.css holds a global CSS that can affect any component.The build/bundle.css is generated from the CSS in each component. The build/bundle.js is generated from the JavaScript and HTML in each component and any other JavaScript in the app.

The file src/main.js contains the following:

import App from './App.svelte';
const app = new App({
    target: document.body,
    props: {
        name: 'world'
    }
});
export default app;

This renders the App component. The target property specifies where the component should be rendered. For most apps, this is the body of the document. The name prop is passed to the App component.

The file src/App.svelte contains the following:

<script>
    export let name;
</script>
<main>
    <h1>Hello {name}!</h1>
    <p>Visit the <a href="https://svelte.dev/tutorial">Svelte tutorial</a> to learn how to build Svelte apps.</p>
</main>
<style>
    main {
        text-align: center;
        padding: 1em;
        max-width: 240px;
        margin: 0 auto;
    }
    h1 {
        color: #ff3e00;
        text-transform: uppercase;
        font-size: 4em;
        font-weight: 100;
    }
    @media (min-width: 640px) {
        main {
            max-width: none;
        }
    }
</style>

The exported variables in the script tag is the prop gotten from the src/main.js files. Curly braces are used to output the value of a JavaScript expression. This is referred to as interpolation. The style tag holds all the css styles that is scoped to this particular component.

Building the Svelte eCommerce shopping cart

Let’s get started by creating directories, files and initializing the project.

to create the required components, execute the following commands in the terminal:

cd src
mkdir CartComponents
mkdir Stores
cd CartComponents
touch card.svelte
touch cardwrapper.svelte
touch checkout.svelte
touch checkoutitem.svelte
touch navbar.svelte
cd ..
cd Stores
touch stores.js
cd..
touch items.js

In the src directory, we have created a CartComponents folder, and inside the CartComponents folder we created five Svelte files:

  • card.svelte
  • cardwrapper.svelte
  • checkout.svelte
  • checkoutitem.svelte
  • navbar.svelte

Similarly, In the src directory, we have created a Stores folder, and inside the Stores folder, we created a stores.js file. Then in the src folder, we created an Items.js file.

We will be using Bootstrap CSS for our project development, let’s add the Bootstrap cdn in our index.html file. Go to public/index.html and add the Bootstrap cdn in the head section like this:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1" />
    <title>Svelte app</title>
    <link rel="icon" type="image/png" href="/favicon.png" />
    <link rel="stylesheet" href="/global.css" />
    <link rel="stylesheet" href="/build/bundle.css" />
   <!-- boostarp cdn starts here  -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
      integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
      crossorigin="anonymous"
    />
    
    <!-- bootstrap cdn ends here  -->
    <script defer src="/build/bundle.js"></script>
    <style>
      body {
        background: #f2f4f8;
      }
    </style>
  </head>
  <body></body>
</html>

Now open up stores/stores.js and add the following code:

import { writable } from 'svelte/store'

export const cart = writable({})

In the above code, we import the writable() function from svelte/store and use it to create a new store called cart.

similarly, open up src/items.js and add the following code:

export default [
	{
		name: 'laptop',
		price: '500',
		img: 'laptop1.png'
	},
	{
		name: 'Latest PC',
		price: '1,000',
		img: 'mobile1.png'
	},
	{
		name: 'Latest laptop',
		price: '1000',
		img: 'laptop2.png'
	},
	{
		name: 'latest smart watch',
		price: '5,000,000',
		img: 'smartwatch.png'
	},
	{
		name: 'Monitor',
		price: '2000',
		img: 'display.png'
	},
	
	{
		name: 'playstation',
		price: '2,670',
		img: 'playstation.png'
	}
]

Building the Cards Components

Open the src\CartComponents\card.svelte file & update it with the following code:

<script>
  import { get } from "svelte/store";
  import { cart } from "../Stores/stores.js";
  export let item;
  let { img, name, price } = item;
  img = `img/${img}`;
  const cartItems = get(cart);
  let inCart = cartItems[name] ? cartItems[name].count : 0;
  function addToCart() {
    inCart++;
    cart.update(n => {
      return { ...n, [name]: { ...item, count: inCart } };
    });
  }
</script>



<div class="card">
  <img  class="card-img-top" width="200" src={img} alt={name} />
  <div class="card-body">

  <h5 class="card-title">{name}</h5>
  

  <b class=alert alert-info > $ {price}</b>
  <p class=alert alert-info >{#if inCart > 0}
      <span>
        <em>({inCart} in cart)</em>
      </span>
    {/if}</p> </div>
  <div class="btn-group" role="group">
    <button type="button" class="btn btn-primary" on:click={addToCart}>
      <object
        aria-label="shopping cart"
        type="image/svg+xml"
        data="img/svg/shopping-cart.svg" />
      Add to cart
    </button>
  </div>

  
</div>

In the above code, we import the get() function from svelte/store. It is used get and set the value manually.

And Update the code in src\CartComponents\CardWrapper.svelte  to this :

<script>
  import Card from "./Card.svelte";
  import items from "../items.js";
</script>

<div class="container">
  <div class="row">
  

    {#each items as item}
    <div class="col-md-4">

    <Card {item} /> 
  </div> 
  {/each} 
 </div> </div> 

In the above code, we are importing the Card.svelte & item.js files we created earlier and displaying the cards using Bootstrap classes.

Working with the Checkout Components

Open the src\CartComponents\CheckoutItem.svelte file & update it with the following code:

<script>
  import { cart } from "../Stores/stores.js";
  export let item;
  let { name, price, img, count } = item;
  const countButtonHandler = (e) => {
    if (e.target.classList.contains("add")) {
      count++;
    } else if (count >= 1) {
      count--;
    }
    cart.update((n) => ({ ...n, [name]: { ...n[name], count } }));
  };
  const removeItem = () => {
    cart.update((n) => {
      delete n[name];
      return n;
    });
  };
</script>

<div class="row">
  <img
    class="img-fluid img-thumbnail"
    width="300"
    src={`img/${img}`}
    alt={name}
  />
  <div class="item-meta-data">
    <h3 class="title">{name}</h3>
    <p class="price">Price: $ {price}</p>
    <div class="col">
      <button
        type="button"
        class="btn btn-success add"
        on:click={countButtonHandler}>+</button
      >
      {" "}
      <span>{count}</span>
      {" "}
      <button
        type="button"
        class="btn btn-warning"
        on:click={countButtonHandler}>-</button
      >
      {" "}
      <button type="button" class="btn btn-danger" on:click={removeItem}>
        <object
          aria-label="remove"
          type="image/svg+xml"
          data="img/svg/cancel.svg"
        />
        Remove
      </button>
    </div>
  </div>
</div>

In the above code, we are importing cart from store.js file we created earlier & then we have written a conditional statement to increase and decrease items in the cart.

And Update the code in src\CartComponents\Checkout.svelte  to this:

<script>
  import CheckoutItem from "./CheckoutItem.svelte";
  import { cart } from "../Stores/stores.js";
  let checkedOut = false;
  let cartItems = [];
  const unsubscribe = cart.subscribe(items => {
    cartItems = Object.values(items);
  });
  const checkout = () => {
    checkedOut = true;
    cart.update(n => {
      return {};
    });
  };
</script>

<div class="container">
  <h1>My Cart</h1>
  <div class="row">
    
<div class="col-sm">
  {#if cartItems.length === 0}
    {#if checkedOut}
      <p class="empty-message">Thank you for shopping with us</p>
    {:else}
      <p class="empty-message">Your cart is empty</p>
    {/if}
  {:else}
  <div class="row">
    {#each cartItems as item (item.name)}
      <CheckoutItem {item} />
    {/each}
    </div>
 <br>
   <div class="btn btn-success btn-lg btn-block" on:click={checkout}>Checkout</div>
  {/if}
</div>
</div>
</div>

In the code above, we have written a simple logic that returns the Items in the cart, checks we have checked-out the items from the cart and displays the message according to the fulfilled condition.

And, add the following code in src\CartComponents\Navbar.svelte :

<script>
    import { cart } from "../Stores/stores.js";
    import { createEventDispatcher } from "svelte";
    const dispatch = createEventDispatcher();
    let cart_sum = 0;
    const unsubscribe = cart.subscribe(items => {
      const itemValues = Object.values(items);
      cart_sum = 0;
      itemValues.forEach(item => {
        cart_sum += item.count;
      });
    });
    function goToHome() {
      dispatch("nav", {
        option: "home"
      });
    }
    function goToCheckout() {
      dispatch("nav", {
        option: "checkout"
      });
    }
  </script>
  


  <nav class="navbar navbar-dark bg-primary navbar-expand-lg navbar-dark ">
    <div class="container">

      <a class="navbar-brand logo-font" id="brand" on:click={goToHome}> SvelteCart </a>  
      
        <!-- links toggle -->
        <button class="navbar-toggler order-first" type="button" data-toggle="collapse" data-target="#links" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fa fa-bars"></i>
        </button>
        <!-- account toggle -->
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#account" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
            <i class="fa fa-shopping-cart fa-1x" aria-hidden="true"></i>
            <span class="badge badge-light">88</span>
        </button>
        
        <div class="collapse navbar-collapse" id="links">
            <ul class="navbar-nav mr-auto">
              
                <li class="nav-item">
                    <a class="nav-link" href="#"> Electronics</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">customer care</a>
                </li>

            </ul>
        </div>
        <div class="collapse navbar-collapse" id="account">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active"><a class="nav-link" on:click={goToCheckout}>Items in Cart
                  <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-cart-dash-fill" viewBox="0 0 16 16">
                    <path d="M.5 1a.5.5 0 0 0 0 1h1.11l.401 1.607 1.498 7.985A.5.5 0 0 0 4 12h1a2 2 0 1 0 0 4 2 2 0 0 0 0-4h7a2 2 0 1 0 0 4 2 2 0 0 0 0-4h1a.5.5 0 0 0 .491-.408l1.5-8A.5.5 0 0 0 14.5 3H2.89l-.405-1.621A.5.5 0 0 0 2 1H.5zM6 14a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm7 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zM6.5 7h4a.5.5 0 0 1 0 1h-4a.5.5 0 0 1 0-1z"/>
                  </svg>
                </a></li>
                <li class="nav-link active"> {#if cart_sum > 0} {cart_sum}
                  {/if} </li>
            </ul>
        </div>
    </div>
</nav>

<br>

In the code above, we imported the createEventDispatcher function from the svelte package and call it to get an event dispatcher.

Finally we will update our App.svelte with the following code:

<script>
	import CardWrapper from "./CartComponents/CardWrapper.svelte";
	import Navbar from "./cartComponents/Navbar.svelte";
	import Checkout from "./CartComponents/Checkout.svelte";
	let nav = "home";
	function navHandler(event) {
	  nav = event.detail.option;
	}
  </script>
  
  <Navbar on:nav={navHandler} />
  {#if nav === 'home'}
	<CardWrapper />
  {:else}
	<Checkout />
  {/if}

In the code above, we imported all the required Svelte components for rendering to App.svelte file, which is the top-level component of our App.

Conclusion

Svelte is a worthy alternative to the currently popular options of ReactVue, and Angular. It has many benefits, including small bundle sizes, simple component definitions, easy state management, and reactivity without a virtual DOM.

I hope you learned a few things about Svelte by building a simple Svelte eCommerce cart app. Every article can be made better, so please leave your suggestions and contributions in the comments below. If you have questions about any of the steps, please do also ask in the comments section below.

P.s – don’t forget to add Images in the public/img folder to get the app working. You can download the images & Source Code from Github.


Share on social media

//