Skip to content

jonathankeys/ecom-example

Repository files navigation

Overview

This project is a simple, extendable ecommerce backend built with NestJS, running on Node.js, with all source files written in TypeScript. TypeORM is the ORM used to handle the interactions with the database which is SQLite.

The primary goal is to implement core ecommerce functionalities such as order creation, updating status, updating shipping, and cancelling an order while keeping the system modular and easy to extend.

Components

Modules

The project currently implements one main module: the Order Module. This receives two injected services, the Order Service for the interaction of the database for orders and the Inventory service, to receive product information at runtime.

The Order Controller provides endpoints to:

  • Create new orders
  • Update orders with shipping information
  • Update order status
  • Delete order

The controller interacts with an Order Service, which handles all database interactions related to order data.

API Endpoints

Method Endpoint Description
POST /orders Create a new order
PATCH /orders/:id/tracking Update tracking info for an order
PATCH /orders/:id/status Update the status of an order
PATCH /orders/:id/cancel Cancel an order, does not delete anything, but removes the shipping information and marks the order as cancelled
GET /orders/:id Retrieve a specific order
GET /orders Retrieve all orders (unauthenticated)

Middleware

A custom middleware extracts the "authenticated" user from the request. This is achieved by:

  • Reading the user_id from the request headers
  • Fetching the user from a mocked static array (User Module)

This middleware simplifies authenticated user retrieval across all endpoints, reducing duplication and making user data easily accessible for request handling. When a real authentication framework is added it should only need to be updated in one place.

Future Improvements

In the future, a production-ready authentication framework (e.g., OAuth2, JWT, or Cognito) should replace the current mock authentication.

Performance can be improved by caching session or token data using services like DynamoDB or MemoryDB, with TTLs (time-to-live) for automatic session expiration and cleanup.


Guards

A Guard is applied to all order-related routes involving a specific :id parameter. Its responsibilities include:

  • Verify that the order belongs to the authenticated user
  • Respond with Unauthorized if:
    • The order does not exist
    • The user does not own the order

This enforces basic security and ownership validation at the route level. This ensures each developer does not need to do their own verifications.


Database

The current implementation uses SQLite as the local development datastore. It is used to mimic the use of a larger relational database such as Postgres or MySQL. TypeORM synchronizes the schemas whenever the service is started based on the entity models. In the future, a more robust migration strategy would be better.

The SQLite database will be created at project root directory /db

Tables

There are three relational tables designed for basic order management.

Order

The orders table contains the general information of the order and has relations to related tables regarding the order.

An order has a one-to-one relationship with a shipment. A future improvement could allow orders to be split into multiple shipments based on the geolocation of products at the time of order or weight/size.

An order can have many order products, each representing an individual product for the given order.

Column Type Attributes
id number Primary Key, Auto-generated
userId number Owner of the order (would be one-to-one with user table if it existed)
orderDate Date Date the order was created
shipment OrderShipment (relation) One-to-one (nullable)
orderProduct OrderProduct[] (relation) One-to-many
status string Current status of the order

OrderProduct

An order product maps one product to an order; there can be many order products for an order.
The price field is stored here since product prices can fluctuate over time, ensuring the price at the time of purchase is recorded for billing purposes.

Currently, there is no relationship between an order product and an order shipment; this is stored at the order level. If features are added to ship products separately, a many-to-one relationship could be added between order products and order shipments.

Column Type Attributes
id number Primary Key, Auto-generated
order Order (relation) Many-to-one relation to Order
productId number ID of the product being ordered (would be one-to-one with product table if it existed)
price number Price of the product at time of purchase
quantity number Quantity of the product ordered

OrderShipment

An order shipment contains the tracking information for the given order, namely the shipping provider and tracking number.

Column Type Attributes
id number Primary Key, Auto-generated
order Order (relation) One-to-one relation to Order
shippingProvider string Name of the shipping provider
trackingNumber string Tracking number for the shipment

NoSQL Considerations

While NoSQL could technically be used for a small-scale implementation, relational databases are a better fit here due to:

  • Complex access patterns (e.g., joins between users, orders, and products)
  • Future features that would complicate NoSQL schema design and querying (ex: showing customers all their orders of specified times, querying orders across multiple columns, data analytics on customer order information)

System Design Considerations

There are many ways to implement this system depending on the use cases being targeted. For an initial launch of the service, using a low cost and low complexity approach would serve best to be able to monitor the usage and customer patterns on the system. After hitting some initial bottlenecks such as compute concurrency, a more highly available and scalable approach would be better with knowledge of patters from the initial approach.

Note: All these services described have strong support for L2 constructs in CDK and could easily be modeled into the diagrams provided below.

Lower Cost, Low Complexity

For a low-cost, low-complexity option, services that are pay-per-usage and have minimal idle cost are preferred.

  • Compute: Lambda
  • Database: Aurora Serverless

Lambda is fronted by API Gateway to serve REST endpoints. Lambda can handle up to 1,000 concurrent executions by default, though this can be adjusted with requests to AWS. Aurora Serverless can automatically pause and resume based on usage, allowing significant cost savings during idle periods (still pay for storage).

Note: DynamoDB could be an alternative, but would require major access pattern adjustments.

Higher Cost, Highly Available

For highly available, high-traffic scenarios:

  • Compute: ECS (Fargate) or EC2 instances
  • Database: RDS or Aurora (with Multi-AZ or Multi-Region replication)
  • Load Balancing: Route 53 ➔ ALB ➔ NLB (for private networking)

ECS Fargate tasks can quickly scale with traffic.
ALB handles public traffic, while NLB enables AWS PrivateLink for secure and fast, internal service to service communication.

Data Resiliency can be improved with:

  • Automatic snapshots
  • Multi-AZ deployments
    • With multi-az there can be performance hits for read as this is a synchronous process
  • Read replicas
    • No performance hit as this is an asynchronous process

Order Processing Resiliency:
Switch from synchronous to asynchronous order fulfillment:

  • Record order immediately and return success to customer that order is received
  • Place message onto a queue (e.g., SQS)
  • Backend services pick up orders and process asynchronously
  • Notify users via email upon fulfillment and shipping
  • Need to ensure orders are not duplicated (idempotent service) as SQS has at least once delivery

Regionalized Stores

To support regionalized stores:

  • Extend database schemas to store the marketplace region
  • Route users via Route 53 based on geographic location, or allow users to select their marketplace manually
  • Separate infrastructure per region if scaling internationally
  • Look into usage of global tables such as Aurora DSQL or pull RDS instances out of application VPC into its own account and VPC. This would separate the storage layer from the business layer, and ensure each application is accessing the database in its own private cloud rather than sharing with other applications.
  • Investigate pros and cons of multi tenant models versus a global store which the application code handles the separate marketplaces.
  • Products are global, but have marketplace specific representations to handle price differences, language differences, model differences.

Running the Project

The server will start on http://localhost:3000.
You can interact with the API using any REST client (e.g., Postman, Insomnia) or standard curl commands.

Setup

# Install dependencies
npm install

Compile and Run

# Development mode
npm run start

# Watch mode (auto-reload on changes)
npm run start:dev

# Production mode
npm run start:prod

Example Request

Create Order

curl --location '127.0.0.1:3000/orders' \
--header 'user_id: 1' \
--header 'Content-Type: application/json' \
--data '{
    "products": [
        {
            "id": 9,
            "quantity": 7
        },
        {
            "id": 1,
            "quantity": 1
        }
    ]
}'

Get All Orders

curl --location '127.0.0.1:3000/orders' \
--header 'user_id: 1'

Update Order Tracking

curl --location --request PATCH '127.0.0.1:3000/orders/1/tracking' \
--header 'user_id: 1' \
--header 'Content-Type: application/json' \
--data '{
    "shippingProvider": "USPS",
    "trackingNumber": "23987239825"
}'

# Unauthorized 
curl --location --request PATCH '127.0.0.1:3000/orders/1/tracking' \
--header 'user_id: 999' \
--header 'Content-Type: application/json' \
--data '{
    "shippingProvider": "USPS",
    "trackingNumber": "23987239825"
}'

Cancel Order

curl --location --request PATCH '127.0.0.1:3000/orders/1/cancel' \
--header 'user_id: 1'

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors