DEV Community

loading...
Cover image for Development of a simple shopping cart application

Development of a simple shopping cart application

darrellbor profile image Darrel Idiagbor ・Updated on ・8 min read

Problem Statement

The challenge today is to build the backend of a simple shopping cart application using node.js and typescript while using MySQL as the database and postman for documentation purposes.
This shopping cart is assumed to have categories and products having standard attributes such as (SKU, selling price, stock level, expiration date) etc... and should be able to allow a user add to cart, edit what's in the cart as well as remove from cart.

Thoughts and structure

The problem is simple enough to tackle on first glance but as I usually say nothing is ever really as it seems. So to up it up a bit, I would set a personal goal of completing the challenge in 3 days or less, I would also be implementing functionality for dynamically manipulating categories and products as well as unit testing all functionalities on this application and developing this article in real-time as I achieve it.
Now with a little extra difficulty, the first thing I'd do is to assign tasks for these 3 days;

  • Day 1: Folder structure and general functionalities.
  • Day 2: Unit testing of all functionalities; although this would most like come before each functionality is developed. That way the entire application would be built using TDD (test-driven-development)
  • Day 3: Deployment, documentation and editing of this article.

Folder Structure

Folder Structure Image

Although a small and simple application, I structured the application for scale. Below I would discuss the various folders and files and their purpose; were necessary I would include examples.

Files on the structure

Below I would give short descriptions of key files on the root of the folder and what they are meant for and what role they play. These definitions are by no way standard description of what they are officially but my way of describing what they are and the role they play in my codebase.

  1. .env: This is the environment variables file; basically all sensitive variables are kept here such as database URLs or keys both secret and public can be kept here.

  2. .env.example: This file simply provides a structure of how .env should be set up; it could contain just variables without values or consist of mock values and not the actual values

  3. .eslintrc.js: This holds configurations for eslint which is a linter for javascript and even typescript.

  4. .gitignore: This consist of files and folders that should be ignored by git(a version control system)

  5. generateKeyPair.js: This holds an algorithm to generate *.pem files i.e public and private key pairs for tokens generation and verification.

  6. nodemon: This file holds configurations for nodemon which handles spinning up and restarting the development server when specified changes are detected.

  7. package.json: This file holds information about the application and is generated by npm. It is generated with the npm init command

  8. README.md: This file tells anyone visiting the codebase what to do, how to do it or what the application is about.

  9. tsconfig.json: This file holds information about how typescript should work on the application and is generated by typescript. It is generated with the tsc init command.

Now, the onto the folders

Below I would describe the folders and the kind of files they are meant to hold; I would not necessarily go in-dept on the files within the folders.

  1. /prisma: This folder is generated by prisma, which is an ORM (object-relational mapping) for MySQL which is the database for this shopping cart application. This folder holds the schema and migrations folder which are generated and manipulated by Prisma. Subsequently, the seed.ts file also sits here and its purpose is to populate the database with data to get started. To get started with Prisma and generate the folder, run npx prisma init. Given that you have @prisma/cli installed.

Note: This walkthrough assumes you have nodejs installed on your local computer. Go to Nodejs if you don't have it.

  1. /src: This is the working directory of the application and all other paths below would be relative to this.

  2. /api/controllers: This folder holds controllers that routes link to; these controllers effect validations, perform validations of themselves and call the business logic from service folder.

  3. /api/database/repository: This folder holds repositories for the business logic and these are simply database connections and actions e.g a repository could have a create method that connects to the database and creates a new record or row with the information passed to it. This is done to enable future change in database. Although this is a small application, Imagine a gigantic application with several hundred endpoints and functionality and all of a sudden there is a need to move from an SQL database to a NoSQL database like MongoDB. It would be cumbersome to go to each page and change database connection. But this way, all the service knows is that it calls a create method and is returned the created record.

  4. /api/routes: This folder holds all routes/endpoints for the application in question.

  5. /api/validators: This folder holds request body validation rules using the express validator, other validations like query and params are done within the controller. Although these can also be done with the express validator, I did them within the controller to show different approaches to carrying out validation. Personally, I find using express validator relieving.

  6. /assets: This folder holds all assets on the application such as images, audios, videos or any type of asset.

  7. /config: This folder holds configuration files. e.g the index.ts file in this folder maps variables from .env. Such that access to the .env variables stem from a single source rather than scattered across the application.

  8. /core: This holds self-defined class-based functionality. For example, this folder holds classes that perform error and response handling among others.

  9. /helpers: This folder is similar to /core although rather than self-defined classes they are simple functions.

  10. /jobs: This folder holds cron jobs. Basically, pieces of code that have to be run periodically.

  11. /services: I have made reference to the folder above. This folder holds classes that handle the business logic of the application. Basically, the major functionality of applications is resolved within the classes in the services folder.

  12. /subscribers: This folder hold events. Events, in this case, are functions that can run in a non-blocking way. Example of this is the sending of a confirmation email once a user registers can be sent to an event within subscribers and it'll run even after a response has been sent to the client.

  13. /types: This holds all types, interfaces and namespaces to be used throughout the application. Since this is a typescript solution thus the need for types.

Process Flow

Process Flow Diagram

The process flow is fairly straightforward at first glance but I would walk you through the thought process of creating this and possible improvements below.

Once a request is sent to an endpoint on this shopping cart application e.g http://localhost:3500/auth/v1/register. It first hits the router for that endpoint and then if it is a public endpoint such as this one it goes to the controller that handles that. If it is a private endpoint like http://localhost:3500/product/v1/create it first advances to get you authenticated with a token that should be on the request header and if available and valid it proceeds to the controller for that route. Subsequently, if this is an admin protected route it further checks the authorized user if the type is that of Admin and if it is you then proceed to the controller.
All-access routes end up accessing the service class which holds the business logic or actual implementation of the route.

This implementation is sufficient for a simple application such as this but for a more verbose and production-ready application, it is advisable to have a process flow as that below.

Ideal Process Flow

Not much has changed from the last one except for the introduction of app keys which give all your route some level of security and preserves your server from any kind of misuse with authorization. App keys are usually issued when you register on the application and opt to use the APIs.

Unit Testing

  • run npx ts-jest config:init to initialize tests with typescript
  • run npx jest --init to initialize tests with javascript,

Testing is so important in the development of any application, be it large or small. I use testing to ensure that the core functionalities of each chunk of code are maintained no matter the implementation style or pattern. I also love it because when younger people join the team, it prevents from accidental non-working commits or deployments.

Let's talk about some why's

Why category has status and product doesn't

The way the application is structured is in such a way that you need to link a category to a product; if that is the case if a category were to be deleted what would happen to the link?
So to mitigate this, if you don't want a category you simply switch the status to Inactive. The product doesn't on the other hand because:

  1. It stores more data or has more fields
  2. It doesn't link to anything or rather no other table is dependent on it.

Why static methods over instance methods?

//Example of static method in a class
class animal {
  public static color() {
    // ... some functionality here
  }
}

// How you call this method like 
animal.color();
Enter fullscreen mode Exit fullscreen mode

Static methods give you direct access to the method without creating an instance of the class; one immediate benefit is it doesn't use memory to store the instance.
Static methods are used where the method has no interaction with class properties. Thus only one of such methods exists per class whereas instance methods you can create as many instances as needed.

//Example of instance method in a class
class animal {
  public color() {
    // ... some functionality here
  }
}

// How you call this method like 
const animalInstance = new animal();
animalInstance.color();
Enter fullscreen mode Exit fullscreen mode

One of the benefits you see immediately is that with instance methods you can access the constructor function when initializing the class and a draw back as we discussed is the extra pointers in memory to link these instances.

These were the thoughts I had in mind when deciding whether to use static or instance methods.

Why use abstract classes?

//Example of abstract class
 abstract class animal {
  public color() {
    // ... some functionality here
  }
}
Enter fullscreen mode Exit fullscreen mode

An abstract class is more or less a blueprint class i.e you cannot instantiate these classes, rather you extend them and work with them from a subclass. This is because as an abstract class it can have incomplete methods hence the term blueprint class.

Why Redis for the cart and not MySQL?

The choice of Redis for the cart system stems from a desire to improve the performance of the cart system. Redis insertion and retrieval big O notation is O(1) whereas that of MySQL is O(n). What this means is that anytime you try to retrieve or insert a record from a Redis server it would take a constant time, while on the MySQL it'll vary based on the number of records stored on the table.

Conclusion

If there is one thing I believe in it is constant change and evolution and as such, I make it a habit to learn something every time I develop applications. By no way is this a perfect solution to structuring backend applications but I simply collated knowledge from different sources and across time and experience. I am always open to improving on this. Although I initially set out to complete this in 3 days, I ended up completing it in 4 days. This isn't a bad feat; more to come πŸš€πŸš€πŸš€

Thank you for reading!
You can find the endpoints for this shopping cart application on this postman collection shopping cart postman
The base URL for this shopping cart application is here base url
I can be found on Twitter at https://twitter.com/idiagbordarrel or
please find out more about me on my website at https://idarrel.netlify.app.

Discussion (7)

pic
Editor guide
Collapse
arsh973 profile image
arsh973

can you share the github repo for same?
btw nice article.

Collapse
darrellbor profile image
Darrel Idiagbor Author

Thank you,
I can't share the repo right now as this is an assessment but once its done I would edit the article and add it in.

Collapse
arsh973 profile image
arsh973

Thanks.

Collapse
victorbruce profile image
Victor Bruce

Great article. Very understandable for technical and non-technical readers as well. I'm definitely adding this article to my bookmarks😊

Collapse
imsergiobernal profile image
Sergio

Thanks for the article Darrel.

I have one question. With MySQL and Indexed Scan, reads and writes are not O(1)?

Collapse
darrellbor profile image
Darrel Idiagbor Author

I made these assertions against the 'out of box' use case. Meaning the use of MySQL and Redis as they come against each other without indexing.

I also believe with indexing the best relational databases can achieve is O{log n}. More of this here (databases O-notation).
While Redis is an in-memory store typically meaning your Redis store sits on the RAM of your server, therefore allowing it access to data structures adapted to memory storage. Hence why Redis can achieve O-notations such as O{1 + n/k). More of this here (How Redis claims O(1) lookup).

Collapse
nwachuk46041677 profile image
Nwachukwu Ebuka

It's nice article pls share the github repo for it