Create a shopping cart using Qwik and Turso
Tags: turso, qwik, typescript, ecommerce
Published: 2023-07-11 12:21:53

---
title: Create a shopping cart using Qwik and Turso
published: true
description: Learn how to create a robust shopping cart for the web using the Qwik framework and Turso, the edge database
tags: turso, qwik, typescript, ecommerce
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ot1c3z5urhtum5oe3fqj.png
canonical_url: https://blog.turso.tech/create-a-shopping-cart-using-qwik-and-turso-b51994f6ab73
series: Everything Edge
---

A shopping cart is an e-commerce software that enables visitors to purchase items on the web. It is one of the most prevalent programs that users of the web come across. If your company sells things online, it’s likely that you’ve purchased, built, or are subscribed to such a service.

Shopping carts facilitate e-commerce and are of significant importance to merchants since they automate product listing, the verification of payment cards, the storage and management of customer details and their orders, and lastly, checking out. Customers get to manage their orders giving them more control on the amounts they’ll be required to pay at the end.

## Why build a shopping cart with Qwik and Turso?

In the modern accelerated world of short attention spans, users expect to be able to complete tasks fast, regardless of the quality of their internet or where they are located in the world.

We can use Qwik to to render the web experience as close to user’s expectations because the framework makes it easy to provide users with an instant-on web experience regardless of the size of the app or type of device they are accessing it through. We employ Turso to handle the data persistence side of the puzzle, since it is a database at the edge, meaning it will be able to provide data from the closest possible location to the user, hence reducing latency which translates to a faster browsing experience. Turso does all this without neglecting the data security obligations it has towards its user data.

Research shows that slow websites:

- [Result in shopping cart abandonment](https://www.cloudways.com/blog/shopping-cart-abandonment-statistics/#statistic-4)
- [Causes users to leave, choosing not to return](https://www.cloudways.com/blog/shopping-cart-abandonment-statistics/#statistic-6)

![Home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ogjup9maix126wk6dzg.png)

## Building the shopping cart

As established, we’ll employ Qwik on the front-end side of the equation and use [Turso](https://turso.tech/) to persist the shopping data. We won’t address user authentication on this post and instead assume that the user is already signed in.

In this post I’ll break down the implementation of a shopping cart into several parts:

- Product listing
- The cart
- Checkout
- Order placement

> **Pre-requisites:** You need the latest LTS version of node.js and the Turso CLI installed in your system to proceed.
> 

We will not cover the creation of a Qwik project and Turso database [as we have excellent resources to follow on that topic](https://docs.turso.tech/tutorials/get-started-turso-cli). [There is a GitHub repository](https://github.com/turso-extended/app-turqw-store)that has the full source code of the shopping cart being created here, you can use its [README](https://github.com/turso-extended/app-turqw-store#database) to assist you with setting up the database, its schema, and indexes.

Follow the [data seeding instructions](https://github.com/turso-extended/app-turqw-store#seeding-data) to add some demo data to the database if you’ll opt to go straight to the repo and skip this post.

As in most apps, we’ll first need a set of utility functions that we will be referencing in the steps that follow. We’ll place these functions under the `/src/utils/` directory

The first utility function is the Turso client that exports a libSQL client instance.

```ts
// /src/utils/turso-db.ts

import { createClient } from "@libsql/client";
const config = {
  url: import.meta.env.VITE_TURSO_DATABASE_URL,
  authToken: import.meta.env.VITE_TURSO_DATABASE_TOKEN
}
export const client = createClient(config);
```

And lastly the types that we are going to use within the app, which can also be found [in this file](https://github.com/turso-extended/app-turqw-store/blob/master/src/utils/types.ts).

## Product listing

For product listings we make database requests that query data by category and display it on the listing pages.

For example, in the category page where we list products based on shared categories whose `id`s are passed as part of the page URL, we have the following Turso query.

```ts
client.execute(
  sql: "select * from products where category_id = ?",
  args: [categoryId]
);
```

We add the above query inside Qwik’s `routeLoader$` where we are able to access the route parameters and collect the category id `categoryId` which we then use to filter the data being queried from the database as can be viewed in the code block above.

When you check [this source code file on the repository](https://github.com/turso-extended/app-turqw-store/blob/master/src/routes/category/%5B...all%5D/index.tsx#L16) you’ll see that we have `useCategoryLoader` as the `routeLoader$` within this page. We then get the data returned by this loader inside the Qwik page component as a reactive `Signal`as follows.

```ts
export default component$(() => {
  const pageData = useCategoryLoader();
  // other code
}
```

Afterwards, we can list the items on the template like this.

```ts
return (
  <!-- -->
  <ul class="mt-4 grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
    {
      pageData.value.products.length > 0
      ? pageData.value.products.map((product: Product) => (<ProductCard product={product} key={product.id} />))
      : <div class="p-8">No items</div>
    }
  </ul>
  <!-- -->
)
```

## The cart

For the most important feature of the shopping cart, we are aiming for two things

- Making sure that our cart data is persisted on the database so that users can easily resume where they left off their shopping sessions on any device.
- Enabling the users to be able to preview the cart from anywhere within the app in the form of a “mini cart”.

We handle persisting the data to Turso by adding a function that inserts the data whenever a user clicks on the “Add to cart” button.

```ts
// /src/components/product-card/ProductCard.tsx

const addToCart = $(async () => {
  try {
    await client.execute({
      sql: "insert into cart_items(product_id, user_id) values(?, ?)",
      args: [props.product.id, authenticatedUser.value.id]
    });
    const cartItem = await client.execute({
      sql: "select * from cart_items where product_id = ? and user_id = ?",
      args: [props.product.id, authenticatedUser.value.id]
    });
    
    appState.cart.items.push(cartDataAdapter(cartItem.rows)[0])
  } catch (error) {
    // TODO: Catch error and notify user
  }
})

return (
 <button
   class="block w-full rounded bg-yellow-400 p-4 text-sm font-medium transition hover:scale-105"
   onClick$={addToCart}
 >
   Add to Cart
 </button>
)
```

To enable users to preview the cart details wherever they are on the site, add a cart component (mini cart) under `/src/components` making it occupy a fixed position on the page, and add ability to toggle its visibility.

Here is how we’ve done this using Qwik: checking [this file](https://github.com/turso-extended/app-turqw-store/blob/master/src/components/cart/Cart.tsx) you’ll notice that we are toggling the app context’s `cart.show` value stored in the `appState` and also setting it to false whenever the `Esc` key is pressed.

```ts
const closeCart = $(() => {
  appState.cart.show = !appState.cart.show;
});

// hide cart when esc key is pressed
useOn("keydown", $((event: unknown) => {
  if((event as KeyboardEvent).key === "Escape") {
    appState.cart.show = false;
  }
}))
```

To list the cart items within this mini cart, fetch the items stored under the currently logged in user from the `cart_items` table as follows.

```ts
// /src/components/cart/Cart.tsx

useTask$(async () => {
  try {
    const storedCartItems = await client.execute({
      sql: "select cart_items.count, cart_items.id as cart_item_id, products.* from cart_items left join products on products.id = cart_items.product_id where user_id = ?",
      args: [authenticatedUser.value.id]
    });
    
    appState.cart.items = cartDataAdapter(storedCartItems.rows);
  } catch (error) {
    // TODO: Catch error and notify user
  }
})
```

The [cartDataAdapter](https://github.com/turso-extended/app-turqw-store/blob/master/src/utils/cartDataAdapter.ts) function helps us convert the collected data into the app’s `CartItem` type.

We are using Qwik’s `useTask$` hook in the above code block since it’s called at least once when the component is created. This is beneficial for our app as the request is made in the server layer when the page loads, hence reducing the number of requests made on the client side. If the app is not making a new page load the request will still be made on the client layer and we’ll have the mini cart populated with data anyway.

![The mini-cart](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q5qvvbumjfsgr21uvuf6.png)

For the cart page, the data fetch requests made are more or less the same as within the mini cart with the difference being that they are made inside a `routeLoader$` .

One good practice inside the cart page is the placement of recommendations right at the bottom of the cart on display. This enables users to either see alternatives or add items that they’d like to have together with the items within the cart. Use the data collected over time within your shopping cart to come up with an algorithm that better suits the items to be placed here.

In our shopping cart we have a [Recommendations](https://github.com/turso-extended/app-turqw-store/blob/master/src/components/recommendations/Recommendations.tsx) component placed inside the cart page that displays four items that exclude any of the ones existing in the cart, picked randomly from the products table.

```ts
// /src/components/recommendations/Recommendations.tsx

const placeholders = appState.cart.items.map(() => {
  return "?"
}).join(",")
const values = appState.cart.items.map(item => {
  return item.product.id;
})

try {
  const response = await client.execute({
    sql: `select * from products where id not in (${placeholders}) order by random() limit 4`,
    args: values
  });
  
  recommendedProducts.value = response.rows as unknown as Product[];
} catch (error) {
  // TODO: Catch error and notify user
}
```

![Cart recommendations](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5px18hyftznmigy3ix5q.png)

## Checkout

[17% of consumers abandon their carts](https://baymard.com/lists/cart-abandonment-rate) due to long or complicated checkout processes. To avoid making your users part of this statistic, go for the simplified one-page checkout process.

In [the checkout code of the shopping cart](https://github.com/turso-extended/app-turqw-store/blob/master/src/routes/checkout/index.tsx), you can observe that we’ve opted for the simplified one page checkout where the user is presented with all fill in forms from his contact information, shipping details, to payment cards.

Within the checkout, just like in the cart [we are fetching the cart data persisted](https://github.com/turso-extended/app-turqw-store/blob/e7c6329b8814a966fd2cd783e28055a5ffb8b958/src/routes/checkout/index.tsx#L19-L28) on Turso and listing it as the order summary of the items that the user is buying.

![Checkout page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wukciry25decvxxr48x.png)

## Order placement

After funnelling users through, you eventually want them to place orders and make your efforts in creating this whole thing worthwhile.

In this section I’m not going to give or suggest a payment option, choose your own payment service and plug it in. I will only explain and try to demonstrate what happens when a user clicks the “Pay Now” button we saw in the previous step.

After verifying and making sure the payment went through, submit the items currently in the user’s `cart_items` table as a new order, attaching this order with the customer contact information and address just as required by the `orders` table.

```ts
await client.execute({
  sql: "insert into orders(user_id, customer_name, amount, shipping_fees, discount_amt, final_amount, shipping_address, paid) values(?, ?, ?, ?, ?, ?, ?, ?)",
  args: [authenticatedUser.id, `${authenticatedUser.first_name} ${authenticatedUser.last_name}`, amount, calculatedShippingFees, discountAmount, finalAmount, `${shippingAddress.zipCode} ${shippingAddress.country}`, true]
});
```

Then clear the items from the `cart_items` table after having transferred them to the `order_items` table.

```ts
// /src/routes/checkout/index.tsx

const transaction = await client.transaction();
const newOrder = await transaction.execute({
  sql: "insert into orders(user_id, customer_name, amount, shipping_fees, discount_amt, final_amount, shipping_address, paid) values(?, ?, ?, ?, ?, ?, ?, ?)",
  args: [authenticatedUser.id, `${authenticatedUser.first_name} ${authenticatedUser.last_name}`, amount, calculatedShippingFees, discountAmount, finalAmount, `${shippingAddress.zipCode} ${shippingAddress.country}`, true]
});

for(const item of appState.cart.items){
  transaction.execute({
    sql: "insert into order_items(order_id, product_id, count) values(?, ?, ?);",
    args: [newOrder.lastInsertRowid as unknown as number, item.product.id, item.count]
  });
  transaction.execute({ 
    sql: "delete from cart_items where id = ?;",
    args: [item.id]
  }); 
}
await transaction.commit();
```

Inside the project this is done in Qwik’s `server$` function [as seen here](https://github.com/turso-extended/app-turqw-store/blob/master/src/routes/checkout/index.tsx#L10-L65).

And that’s how you create a shopping cart for your business using this Quick stack.

More features can be added to broaden the user experience but it ought to be done with functional relevance. Customer satisfaction is mainly what we are looking for when trying to get them to end up making purchases in e-commerce websites.

An example of a feature that could be added include user reviews and ratings which tend to foster more confidence in users since they can read about the experiences of others who’ve used the website before.

For more information on the stack used here, you can visit the following resources.

- [Turso docs](https://docs.turso.tech/)
- [Turso’s community](https://discord.com/invite/4B5D7hYwub)
- [Qwik docs](https://qwik.builder.io/docs/)
- [Qwik’s community](https://qwik.builder.io/chat)