Build Your Own E-Commerce Keystone.js-Based System — Requirements and Architecture
A basic overview of our e-commerce system
Photo by Markus Winkler on Unsplash
Introduction
Not so long ago I was working on a system based on Keystone.js CMS. But there it was used much more sophisticated way than just as basic headless CMS. I was easily able to extend it with search engine (Rust-based Meilisearch) and connect to external APIs.
After that, I had a couple of ideas for projects that could be built with Keystone.js and one of them was an e-commerce system. The idea was so appealing that I’ve decided to give it a try and actually build it. This article is the first of the series about this topic.
After brief research, I found one similar project, but it’s using the previous version of Keystone.js (details). The project is great, but there are some things that I believe I will do differently.
First, it uses MongoDB, and I am not pretty sure this is the best solution for e-commerce (and also I am an SQL man). Additionally, from my point of view, some features are missing, like delivery handling and so on.
Another similar project comes from another Node.js headless CMS — Strapi. But it’s a rather simplified, tutorial type (details) and what’s more, uses Nuxtjs. It’s not my cup of tea; I am a React type of guy.
In my professional career, I worked on one rather large e-commerce system for a couple of years, so I believe I have some ideas how it should look like (and how it should not). Let’s start with our goals here.
The Goal
OK then, but what should this e-commerce system look like? First, scalability is not my main concern here, so I won’t pay much attention to it. For sure, I will leave some doors open to allow some basic scaling, but that’s all.
With that out of the way, let’s assume our system will be constructed with the three-layered approach. The first one is the user-facing, presentational layer, the second is the business/logic layer, and the last one is the data layer.
Presentational layer
Contains the frontend part of application, which should allow users to browse a catalog of products. They can also filter them by category, search them, add them to their cart, and finalize their order with payment and delivery handling.
Additionally, it should allow the user to view the status and history of orders.
Logic layer
The next layer contains all the backend processes of the system: handling products, their stocks, orders processing, payments, and delivery integrations.
Also, users authorization and authentication with access control are crucial here. Basically, its goal is to transform and process data from presentational or data layer and pass further in both directions.
Data layer
The last layer is responsible for data storage and its persistence. Basically, it’s the database of our system. Its goal is to maintain data availability and consistency and ensure that we follow ACID principles.
So basically, I’m going to create an e-commerce system that contains all that layers. To sum up, this system should contain:
- frontend layer allowing users to browse products and process orders
- logic layer responsible for processing data and allowing to manage orders, products, and so on
- data layer holding whole necessary data in an ACID way
I believe there’s a need for a bit of clarification. This three-layered model does not relate to software architecture. We can still create microservices architecture using it, and we can stick to old but gold monolithic model.
My main goal here is to exercise skills in the design and implementation of a working system (and write about it), not to get involved in the theoretical discussion of the superiority of one approach over another.
System Elements
Now it’s time to talk about all the elements building each separate layer but also about connections between them. Although I’m going to give a brief analysis and description of each layer in this article, I’ll mainly focus on the logic layer.
Presentational layer elements
The frontend layer will be built from three main elements: main app, Next.js application in this case, and CDN service and Nginx web server.
It’s a rather straightforward solution: the main app built in React with the help of the server site rendering from Next.js creates a kind of sweet spot with single page application interactivity — and has all of the SEO advantages and faster initial loads from server-side rendering.
The role of the CDN here is to help with serving static files, mostly images. Nginx works like a reversed proxy and load balancer. Nothing too complicated. All the communication with the logic layer happens via GraphQL.
Logic layer elements
This main layer basically contains only two elements: The Keystone.js app working as main processing, and the command unit and GraphQL resolver, which act as an extension to the built-in one and work as a wrapper for text search capabilities for the underlying search engine.
But this statement is a huge simplification, so let’s get to the details.
Keystone manages three models fundamental for the whole system: User, Product, and Order.
The rest of the models can be directly or indirectly derived from them. First, User holds the basic information of customers and staff of our e-commerce system, mainly authorization info and assigned roles.
Also User has one Cart and can have multiple Order models and multiple Address models. Each Order model has one connected Payment and Shipment and holds multiple Product models.
Product has one Stock model (for the sake of simplicity, we assume we won’t use a multi-warehouse setup) and multiple ProductImage models.
Lastly, it has a connection to multiple Category models, and each one of them can be related to the parent Category forming tree.
It looks complicated, but it’s not (yet).
Also, the role of Keystone is to manage the content of basic pages for the main frontend app. But more important, its role is also to manage side effects and external integrations.
An example of this side effect may be e-mail notifications associated with order processing steps and informing clients about it or indexing products data in search engines after changes.
On the other hand, examples of external integrations contain the usage of Stripe in order to process payments or connect to external API handling delivery (e.g., DHL API).
These functionalities will mainly harness the power of hooks in Keystone schemas. But not only that, we will need to create additional rest endpoints as webhooks in order to handle async responses from these integrations.
Lastl, Keystone admin UI works here as a kind of dashboard for staff to manage orders and process them.
Data layer elements
The last layer of the system's main responsibility is to collect data and ensure its persistence. Like in the previous one, there will be two elements here: PostgreSQL database and Meilisearch search engine.
For the moment, I’m writing this with no choice of the database. Keystone.js supports only PostgreSQL or SQLite, and I believe the second one is a bit too small for e-commerce system.
For the search engine, we need custom integration, so the choice here is much bigger, but I’m most familiar with Elasticsearch and Meilisearch.
I decided to choose the second one because I feel its JavaScript integration is a little better, and querying using facet filters is easier. Additionally, its footprint is a couple of times smaller than Elastic.
That concludes all the elements of our yet to be built e-commerce system:
Summary
Ok, let’s sum up. Now we have the basic overview of what we are going to build in the next part of this series.
Building an e-commerce system may look hard and daunting, but when we split this task into small, well-planned chunks, it’s not so terrifying.
Next time, we will write a little code and start with the project’s setup.
I hope you liked it. If you have any questions or comments, feel free to ask them.
Have a nice day!
Top comments (0)