loading...
Cover image for Creating a Shopping App from Scratch (Part 2)

Creating a Shopping App from Scratch (Part 2)

samwsoftware profile image Sam Williams Originally published at Medium on ・7 min read

This is part 2 of a series on creating a shopping website from scratch for an upcoming interview. Click here to read part 1 to find out more!

Product Lists

I knew that this bit would end up taking a while. I had to create and populate a products model, create a get route for the API with searching capabilities, then create product list, product preview, product and filter criteria components.

The basic back end came together quickly and the front end list and preview components were done reasonable quickly too. I’d set it up so the URL was /products/:gender/:category which ended up regretting later on but it worked.

Filtering

This is where things started to slowly unwind. I created a filter component using multi-select drop downs, populated from the same store as the sub-header. The multi-selects worked well and I managed to get the values from them (with Google, console.log and trial-and-improvement).

I now had to work out how to turn that search query into a URL. This is where I realised that having a category as a parameter in the URL had a major problem — what if they were searching for more than one category? I had been trying to go with a /products/:gender/:category/:brand type of URL but that was not going to work now.

After some trial and error followed by a while of contemplative frustration, I decided to go with /products/:gender?filter-criteria. I could get the filter criteria and gender and pass them straight into the getProducts action… or not.

Converting Filtering into Queries

For some reason I had left the API functionality at getting all of the available products. Now I needed to take the filter from the front end and convert it into a query for MongoDB.

The problem with this is that the query comes in as a string and it needs to be compiled with $and and $or controls for multiple fields and multiple values for those fields respectively.

For example, requesting all women’s shoes in size 5 or 6 and in blue or white come as a string like this:

?gender=womens&stock=5&stock=6&colors=blue&colors=white

But the query to the mongo database needs to be in this format:

{
  $and: [
    { gender: "womens" },
    {  
      stock: [{ 
        $elemMatch: { 
          size: [5, 6],
          stock: { $gt: 0 } 
        }
      }]
    },
    { $or: [
      { colors: "blue" },
      { colors: "white" }
    ]}
  ]
}

By default, express parses the above query into this format:

{
    gender: "womens",
    stock: [5, 6],
    colors: ["blue", "white"]
}

There are 3 problems with this :

  • The stock parameter is actually an array of objects.
  • The colors parameter is an array of strings.
  • We need to have all three parameters in a product.

The stock objects look like this:

{ size: 4, stock: 13 },
{ size: 4, stock: 0 }

This means that we need to create an array of objects that have one of the sized requested and also have stock. To do this we use $elemMatch for an object with size in our size array and stock being greater than 0.

{  
  stock: [{ // create an array of elements
    $elemMatch: { // That match 
      size: [5, 6], // size of 5 or 6
      stock: { $gt: 0 } // and stock greater than 0
    }
  }]
}

To handle this, I created a handleStock function.

function handleStock(size) {
  return { 
    stock: { $elemMatch: { size: size, stock: { $gt: 0 } } } 
  };
}

The colors is slightly simpler. We need to find products that match blue or white. This was similarly handed by a handleColorsArray function

{ $or: [
  { colors: "blue" },
  { colors: "white" }
]}

function handleColorsArray(array) {
  return { $or: array.map(col => ({ colors: col })) };
}

The last issue is making sure the products contain all of the properties. To do this, if a query had more than one parameter I would pass it to convertToAnd(). This maps over every parameter. “stock” parameters are passed to handleStock. “colours” parameters are passed to handleColorsArray if they are a array. Everything else is just passed through as is.

Once each of the parameters has been mapped over, the array of queries is wrapped in an $and query.

function convertToAnd(query) {
  let q = Object.keys(query).map(param => {
    if (param === "stock") {
      return handleStock(query.stock);
    } else if (param === "colors" && typeof query.color !== "string") {
      return handleColorsArray(query.colors);
    } else {
      return { [param]: query[param] };
    }
  });
  return { $and: q };
}

Now if I did a call to the API with queries, it could process them and return the array of all of the matching products. Hooray!

Filter Criteria

Now with the API able to receive and processing the filtered queries, I had to allow the users to select and change them.

I created a FilterCriteria component that mapped over the filtering options and created a new Select dropdown for each. This Select uses MaterializeCSS multi-select dropdown boxes so that a user could select and unselect the options that they want. Because of MaterializeCSS these boxes worked well and looked great.

Now I had to get the resulting information from the form and somehow send this to the API. The solution I came up with was to make the search button a to the URL that corresponded to the queries.

I did this by mapping over the form and extracting all of the drop down values. I then mapped over these adding them to a query string. If a value had multiple selected options, I mapped each of them to another section of the query string.

FWith the completed query string, I generate the complete URL and push it into the history.

A few small hiccups

The way that I’d set things up, it currently worked like this:

  1. Search for trainers by Fred Perry in size 5
  2. Redirected to URL /products/womens?category=TRAINERS&brand=FRED%20PERRY&size=5

The problem with this is that TRAINERS doesn’t match trainers like in the database. The reason that it is TRAINERS in the URL is that I capitalised the strings as lower case looked really bad.

To try to fix this, I just converted all of the strings to lower case before processing them. This brought up two new problems.

  • The default values don’t equal themselves as the value from the select has been lower cased →(“category” === “Category”) is false. This means they are added to the query string as category=Category
  • The brands no longer match the database strings

After some over complicated attempts to capitalise certain parts I realised I could just change the original values in the database.

“red” became “Red”, “trainers” became “Trainers”. I think this also looks much better than block capitals.

This meant that I didn’t need to do any string manipulation and everything would work as it had done before.

Product Page

Now I had a fully filterable product list, I needed to send the customers somewhere when they clicked on a product.

This was a simple layout — image on the left — basic details on the right — full details below.

Basic wireframing

Building this component was simple to build and most of my time was spent styling it so that it looked good and worked well on desktop and on mobile.

I’ve still got some work to do with this page. The select boxes are being created before the product information has been received and aren’t updating their values with the correct information. The Add to Basket button currently doesn’t do anything as I need to create the user/basket update methods first.

Account Pages

The last UI thing I decided to tackle was building an account section. Again I borrowed the design from Asos, simple tabs on the right and information on the right. When on mobile, the menu was full screen width and opened a new page that had a back button.

Each of the options (Orders, Details, Addresses) have their own UI component. These components are either rendered to the right side of the menu when on desktop, or as a separate page when viewing on mobile.

These components are very simple, taking a prop of either auth (user) and rendering out the relevant data. Again these components currently don’t support updating the user information as I’ve decided to work on other parts of the prototype first.

Things to note

It may seem like I’m building lots of components without finishing some of them, even leaving some not working properly. You’d be right. I’m doing this for 3 reasons:

  • I only have 3 weeks to build it. Making 80% of it work will take 20% of the time. I’m just skipping the last 20% and saving myself a load of time.
  • This is a prototype. I’m not trying to make a perfect product, I want to show all of the ideas I have. As long as they work reasonably well and demonstrate the skills and ideas that I have, they’re meeting their requirements.
  • I’m actually on holiday. I’m doing this in the last few weeks of a 6 month road trip around South East Asia. I don’t want to be spending all day every day sat inside on a laptop when I could be outside climbing and having fum.

In the next section I’ll talk about creating a chat bot to accompany the website. I’ve never worked with chat bots before so come along for the ride!

If you liked this article, make sure to react and follow me to see the part 3!

Posted on by:

samwsoftware profile

Sam Williams

@samwsoftware

I've always loved problem solving and found that software development scratches my problem solving itch.

Discussion

pic
Editor guide