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 React, Vue, 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.