Persoanlly I would go with 2, as that allows the user to switch computers or browsers, and just log in to see their cart again :) It also allows you to get the newest prices on load, so you don't show a wrong price, if the price changed since they put the item in the cart.
How would you implement a shopping cart with Laravel and InertiaJS?
I'm trying to building e-commerce website with Laravel, Vue and InertiaJS but I'm trying to figure out what is the best way to make a shopping cart. Here are my thoughts:
1- Make it a complete client-side shopping cart with Vue Pinia without even creating an eloquent model for it. This will work by storing the user products selection in their browser and fetch them whenever the route is visited.
2- Create a model and table for shopping cart and store the user product selection in the table and make it delete the cart maybe after 1 week if no purchase occured.
What are you thoughts?
Thank you so much for your time in advance :)
@Sinnbeck But what about guests? If a user isn't logged in and would like to make a purchase, would it be a problem in that case and I would need to process his order client-side?
@MooseSaid You can still use the backend. You can store a reference to the cart in the database in the session
@Sinnbeck You mean adding a column to the cart table that stores the session ID?
@MooseSaid No the other way around :) Have a carts table, that where the user_id is null
And then in the session you set the primary id of that column
$request->session()->put('cart_id', $cart->id);
//or
session(['cart_id' => $cart->id]);
@Sinnbeck So it goes like this..
1- Create a cart table for auth users where they can normally add products into the cart with their user_id
2- Create another cart table for unauthorized users where they can add products to the cart and store the cart ID in the session, in this way whenever unauthorized user visit the cart route my code supposedly will look for the cart_id in the session and retrieve the correct items
Did I get it right?
@MooseSaid Just 1 table. You can just have the user_id column be nullable, and if they sign in, you assign their user_id to the cart.
And have the cart items be a different table. The carts table is only for having a unique id for the cart.
@Sinnbeck Okaaay! So you mean that the carts table will work as the link between users and cart items, right?
1- Relationships will be Cart has many items and items belongs to a cart. 2- The code creates new cart in carts_table (cart_id, user_id, session_id) and items will be added to cart_items_table maybe something like (cart_id, product_id ..etc)
@Sinnbeck A unique ID for the cart plus the user ID – as well as anything else that applies to the cart as a whole, such as the sum total, VAT rate (assuming individual items don’t have distinct VAT rates), shipping costs (assuming you don’t ship items individually), recipient (referencing a separate tables where the recipient’s name and shipping address are logged so that addresses for previous orders don’t change if the user moves house), etc.
@MooseSaid No session_id column. The id is saved in session, not the other way around.
So 2 tables
carts
| id | user_id (nullable) |
cart_items
| id | cart_id | product_id | amount |
Something like that
You can of course also just use a package that handles all of this for you :) https://packagist.org/packages/darryldecode/cart
@Sinnbeck Would you use 8 years old package if you were making e-commerce project?
@Sinnbeck I mean would it be the same outcome if I used this package or created my own shopping cart logic?
@MooseSaid ah sorry bad example. Just took the first hit on packagist. But yes making your own is safer. But perhaps you can find some ideas in packages like this :)
@Sinnbeck Okay so whenever you have time to look at this please confirm if I'm right.
1- Create a Cart model that 'hasMany' CartItem.
2- CartItem model that 'belongsTo' a Cart.
3- carts table has only 'id and user_id(nullable)'
4- cart_items has 'id and cart_id and product and any other details for the product like quantity and price'
Once a user clicks on add to cart the code will:
Check if user has a cart in carts table or if the session has a cart id.
- A. If Cart not found:
1- Create a new cart.
2- Check if the user is auth or not, if yes then add user_id to carts table, if not then leave user_id empty and add the cart_id to the user session like you mentioned session(['cart_id' => $cart->id]);
3- Add the selected product_id to the cart_items table with it's options or attributes.
- B. If Cart found:
1- Add the product to the cart.
@MooseSaid Yeah sounds like how I would most likely do it. For the carts table you could also add the timestamps, so you can see when it was created :)
And be sure to transfer the cart to the user, if they log in :)
@Sinnbeck Awesome! Thank you for being patience with me :)
@MooseSaid Anytime :)
@Sinnbeck Will i need user cart relationship? something like a user hasOne cart and cart belongs to a user?
@MooseSaid Yeah I would probably add that :)
Then you can do
if (auth()->user()->has('cart')) {
//do stuff with users cart
}
Of course I will. Stupid question!
@Sinnbeck I was doing some reasearch and appearently I'm going to need to use Many To Many Relationship between the cart and the product so I went for something like this.
- Users.php
public function cart()
{
return $this->hasOne(Cart::class);
}
- Cart.php
public function user()
{
return $this->belongsTo(User::class);
}
public function products()
{
return $this->belongsToMany(Product::class);
}
- Product.php
public function carts()
{
return $this->belongsToMany(Cart::class);
}
- carts_table
public function up()
{
Schema::create('carts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable();
$table->timestamps('updated_at');
$table->timestamps('created_at');
});
}
- cart_product (instead of cart_items, it made more sense to me as cart_product)
public function up()
{
Schema::create('cart_product', function (Blueprint $table) {
$table->id();
$table->foreignId('cart_id');
$table->foreignId('product_id');
$table->integer('qty');
$table->timestamps('updated_at');
$table->timestamps('created_at');
});
}
Does this sound about right? I never user belongsToMany relationships before.
@MooseSaid Yeah that seems all fine :)
@Sinnbeck But the docs here left me with a question. In the above code, won't I need CartProduct model? I mean how would I assign the cart_id and product_id to the cart_product table if I don't have a CartProduct model? does Laravel handle such thing by it's own? would I need to create another model for cart_products table and figure out the relationships?
@MooseSaid Laravel figures it out for you. You can have a model for the pivot table, but it isn't necessary
@Sinnbeck I tried that but it didn't work. I guess I have to create new row in cart_product table and add cart_id and product_id manually on each 'add to cart' click. Am I missing something?
@Sinnbeck I have to do this:
if (!auth()->user()) {
$cart = Cart::create([]);
session(['cart_id' => $cart->id]);
CartProduct::creat([
'cart_id' => $cart->id,
'product_id' => $product->id,
]);
}
Is this how it's supposed to go or am i missing something?
I would do it this way:
- Signed => DB (so server side)
- Not Signed => LocalStorage or like u said use Pinia or VueX (so client side)
Why though? for a better user experience, you want the user to be able to see what he added on his laptop when using his phone (assuming in the future there will be a mobile version), or simply in a different device, somewhere else. So it will be up to you whether you want to keep it simple or give a better user experience.
@OussamaMater I thought about that and it sounds really nice approach, Thanks!
Definitely store it server-side. Allows users to use different devices and gives you the ability to analyse user patterns (which products are most frequently added to carts overall, which products are most frequently added to cart without a follow-through purchase, etc.).
Also, generally speaking, don’t delete the cart after a week – that serves virtually no purpose, except to potentially annoy users. Only remove unused carts if database space is an issue (which requires quite a lot of shoppers on your site!). Otherwise keep them around for as long as local laws allow (e.g., five years in the EU).
@kokoshneta But what about guests? If a user isn't logged in and would like to make a purchase, would it be a problem in that case and I would need to process his order client-side?
@kokoshneta I agree except a potential issue with never deleting carts. It depends on whether you reserve an item when its put into the cart. If you do, it can be a problem that items are never released. If you don't reserve them, then I agree :)
@Sinnbeck That makes a lot of sense
@sinnbeck That’s a good point! Yes, if you reserve limited-quantity items when users put them into their cart and others can’t purchase the same item while it’s reserved (for example plane tickets), then of course you don’t want to store carts as long as possible – you may even want to destroy inactive carts after only a few minutes (and of course let the user know with a countdown message like, “You have [05:00] minutes to complete your order”).
In such a scenario, of course, you would have to store everything server-side, since there’s no other way to make sure no one else can reserve the same item.
@MooseSaid Why would you need to process unauthenticated users’ carts client-side? You store a cookie in the browser which contains the shopping cart ID, regardless of whether the user is logged in or not. If the user is logged in, you store the user ID in the cart as well.
When a user visits, you check
- if they’re logged in or not
- if they are, check if there are any carts with their user ID, then load that
- if they have a cookie containing a cart ID, and if there is, load that
If an unauthenticated visitor has a cart ID in their cookies and then log in, you simply update the cart to add their user ID. Conversely, if an authenticated user has an active cart and logs out, you make sure the cart ID is stored in a cookie so they don’t lose their cart despite logging out.
The only potential problem is if the user visits on one device as a logged-in user, and adds something to their cart; then visit from a different device as an unauthenticated visitor, adds something to their cart, and then logs in. In that particular scenario, there will be two competing carts: the one already registered under their user ID and the one taken from the cookie. You’d have to work around that specific case by telling the user that they have a previous, stored cart and letting them decide whether they want to merge the carts (update old cart, change ID to match new cart), keep the old cart (delete new cart from database) or keep the new cart (delete old cart from database).
@kokoshneta Regarding the potential problem, why would it be a problem? Since they are two different devices they will be a 2 different carts because they have different session ID, right?
@MooseSaid Exactly – that is precisely the issue. I generally use cookies with cart IDs instead of session IDs, since I don’t want carts to disappear if the session expires, but the problem is the same in both cases.
In general, you want the cart to follow users from one device to another, which requires them to be logged in. So if they’re logged in on devices A and B, add a product to the cart on device A and then refresh the page on device B, the same product should appear in the cart on both devices.
But if they are not logged in on device B when they add a different product to the cart, they will have two different carts active: cart A on device A, and cart B on device B. What then happens when they log in on device B?
User IDs should be unique in the cart table (otherwise you’ll have no reliable way of knowing which cart to load for logged-in users), and this user’s ID is already saved in cart A, so you can’t add the user ID to cart B in the table.
If you stay with cart B on device B based on the cookie (or session), you’ve lost consistency: you now have the same logged-in user with two different carts, only one of which is actually tied to their user ID. That’s a recipe for user confusion. You can log in on as many devices as you want and always see cart A – except on one particular device, where you keep seeing cart B no matter what you do!
If you just load the cart based on the user ID and ignore the cookie/session, the user will suddenly see cart A instead of cart B with no warning (“What the… why did my cart just change by itself?”) – unless they log out again, in which case cart B will suddenly reappear.
Neither of those options is good UX.
The best way to keep the customer informed and not confused is to let them know that your records show that they’ve been looking at some other items previously, and then give them the option to choose how to proceed.
@kokoshneta Okay I got it. Thank you so much for the time and effort trying to help me out :) This has provided me with many new info and things to consider while building my project.
@moosesaid A shopping cart is a complex thing to build. You need to settle on the requirements before you start worrying about what technology you’re going to use to implement it. The requirements should influence what technology you use to build the solution, not the other way around.
Write down on paper exactly how the cart will work. Then you’ll be able to see what bits you should be implementing server-side and what bits you can add client-side niceties to.
@martinbean Well, you are absolutely right. Considering all the answers on my question it seems like a database is always the place to store the cart and it's items and this shouldn't be client side storing. Of course client side will hold frontend validation
Please or to participate in this conversation.