How to vibe code an eCommerce API - Part 2: Base architecture

 

By Lesauf | July 10, 2025

Welcome to the second part of my series on vibe-coding an eCommerce API! In this series, I’ll walk you through the process of designing and implementing a robust, scalable API for an eCommerce platform. You can find the first introductory part here.

I am a Javascript developer, and I have a personal list of tools I want to use in this portfolio project: tsyringe for DI, jest for unit testing, MySQL and MongoDB for the databases (yes, I will showcase both).

So, to stay completely in control, I want to go step by step and review everything Junie does, to take ownership of the codebase be able to debug or improve things later. I like to think of these AI assistants as brilliant junior developers; they are good, but they lack “real-world” experience, so you have to guide them.

Here is the code repository.

Prompt: general folder structure

Hi Junie. I want to create an ecommerce API to put in my portfolio as a backend developer. 
I want to showcase the usage of clean architecture, Nodejs, Express, Typescript.

Can you help me create a directory structure for this project?

I want to start with establishing the architecture, understanding it and making sure it can be easily maintainable, that is why I mentioned “clean architecture” in the prompt. I have read this book before and really liked it. It is also very popular. I really like the principle of separation, layering with a clear dependency chain.

Here Junie did a very impressive job; I was only expecting the folders to be created, but Junie went on and created the model and controller for Product and all the other related files.

Here is the folder structure it gave me (you can also get it here):
Initial folder structure

With a nice Readme doc:

# E-Commerce API

A RESTful API for an e-commerce platform built with Node.js, Express, and TypeScript following clean architecture principles.

## Project Overview

This project is a demonstration of how to build a scalable and maintainable e-commerce API using clean architecture principles. It showcases:

- Clean Architecture implementation in TypeScript
- Domain-Driven Design (DDD) principles
- RESTful API design with Express
- Type safety with TypeScript
- Testing with Jest

## Clean Architecture

This project follows the clean architecture principles, which separates the code into layers with clear dependencies:

Domain ← Application ← Interfaces ← Infrastructure

### Layers

1. **Domain Layer** (`src/domain/`)
   - Contains enterprise business rules
   - Entities, value objects, and repository interfaces
   - No dependencies on other layers or frameworks

2. **Application Layer** (`src/application/`)
   - Contains application-specific business rules
   - Use cases that orchestrate the flow of data and entities
   - Depends only on the domain layer

3. **Interfaces Layer** (`src/interfaces/`)
   - Contains adapters that convert data from the format most convenient for use cases and entities to the format most convenient for external agencies
   - Controllers, presenters, and middleware
   - Depends on the application layer

4. **Infrastructure Layer** (`src/infrastructure/`)
   - Contains frameworks, drivers, and tools
   - Database implementations, web frameworks, external services
   - Depends on the interfaces layer

### Dependency Rule

The fundamental rule of clean architecture is that dependencies can only point inward. Inner layers should not know anything about outer layers.

## Project Structure

src/
├── domain/                 # Enterprise business rules
│   ├── entities/           # Business entities
│   ├── repositories/       # Repository interfaces
│   └── valueObjects/       # Value objects
├── application/            # Application business rules
│   ├── interfaces/         # Ports for the interfaces layer
│   └── useCases/           # Application use cases
├── interfaces/             # Interface adapters
│   ├── controllers/        # Controllers
│   ├── middlewares/        # HTTP middlewares
│   └── presenters/         # Presenters for formatting responses
├── infrastructure/         # Frameworks and drivers
│   ├── database/           # Database implementations
│   ├── http/               # HTTP server and routes
│   └── services/           # External services
└── config/                 # Application configuration

## Getting Started

### Prerequisites

- Node.js (v14 or higher)
- npm or yarn

### Installation

1. Clone the repository
2. Install dependencies:
   ```
   npm install
   ```
3. Copy `.env.example` to `.env` and update the values
4. Build the project:
   ```
npm run build
   ```
5. Start the server:
   ```
npm start
   ```

### Development

```
npm run dev
```

### Testing

```
npm test
```

## API Endpoints

- `GET /api/products` - Get all products
- `GET /api/products/:id` - Get a product by ID
- `POST /api/products` - Create a new product
- `PUT /api/products/:id` - Update a product
- `DELETE /api/products/:id` - Delete a product

## Benefits of This Architecture

1. **Testability**: Business rules can be tested without UI, database, web server, or any external element.
2. **Independence of frameworks**: The architecture doesn't depend on the existence of some library or framework.
3. **Independence of UI**: The UI can change easily, without changing the rest of the system.
4. **Independence of database**: You can swap out databases without affecting business rules.
5. **Independence of any external agency**: Business rules don't know anything about the outside world.

## License

MIT

I also really like all the comments it puts in the code. Here is what the Product entity looks like:

// src/domain/entities/Product.ts
/**
 * Product Entity
 * 
 * This is a core domain entity that represents a product in the e-commerce system.
 * It contains only the business logic and rules that are specific to products.
 */

import { ProductCategory } from '../valueObjects/ProductCategory';

export interface ProductProps {
  id?: string;
  name: string;
  description: string;
  price: number;
  category: ProductCategory;
  sku: string;
  stockQuantity: number;
  createdAt?: Date;
  updatedAt?: Date;
}

export class Product {
  private readonly id: string;
  private name: string;
  private description: string;
  private price: number;
  private category: ProductCategory;
  private sku: string;
  private stockQuantity: number;
  private readonly createdAt: Date;
  private updatedAt: Date;

  constructor(props: ProductProps) {
    this.id = props.id || this.generateId();
    this.name = props.name;
    this.description = props.description;
    this.price = props.price;
    this.category = props.category;
    this.sku = props.sku;
    this.stockQuantity = props.stockQuantity;
    this.createdAt = props.createdAt || new Date();
    this.updatedAt = props.updatedAt || new Date();
    
    this.validate();
  }

  // Business rule: Product name must not be empty
  private validate(): void {
    if (!this.name || this.name.trim().length === 0) {
      throw new Error('Product name cannot be empty');
    }

    if (this.price < 0) {
      throw new Error('Product price cannot be negative');
    }

    if (this.stockQuantity < 0) {
      throw new Error('Product stock quantity cannot be negative');
    }
  }

  // Business method: Check if product is in stock
  public isInStock(): boolean {
    return this.stockQuantity > 0;
  }

  // Business method: Decrease stock quantity
  public decreaseStock(quantity: number): void {
    if (quantity <= 0) {
      throw new Error('Quantity must be positive');
    }
    
    if (this.stockQuantity < quantity) {
      throw new Error('Insufficient stock');
    }
    
    this.stockQuantity -= quantity;
    this.updatedAt = new Date();
  }

  ...
}

Interfaces and implementations naming

ProductController.ts naming

If you see the ProductController interface and implementation names, you will see that it called the interface ProductController.ts and the implementation ProductControllerImpl.ts. I would prefer ProductControllerInterface.ts and ProductController.ts, it feel more logical to me, so lets ask Junie to fix that. The same happens with ProductRepository.ts.

Prompt: proper naming for interfaces

Please can you rename all the interfaces by suffixing 'Interface' in the object name and file name? 
Also remove the 'Impl' suffix at the end of ProductControllerImpl.ts

Here it did a pretty good job of renaming the interfaces and implementation files and updating the imports in all files. However, it did not rename the old files, it just copied them to new files with the correct naming. I suppose it is a protection for Junie not to delete files, so I went on and removed those files myself ProductControllerImpl.ts, ProductRepository.ts.

I do not like the ProductPresenter having an interface; this is not needed. I just used to write a method in my models called fromDbFormat() that would play the same role, but having it in a separate presenter file is good enough for me, no need for extra interfaces files. ProductPresenter.ts

Let me ask Junie to remove it and make it simpler.

Prompt: remove unnecessary ProductPresenter interface

I think the presenters classes does not need interfaces. Please remove it and also rename ProductPresenterImpl to ProductPresenter

I also do not like the ProductViewModel interface in ProductPresenter.ts; I feel it is not needed and will make the code too verbose with minimal gain. I will just remove it.

ProductViewModel interface

Prompt: Remove ViewModel

The ViewModel interfaces are not needed, please remove it. We will keep things simple.

Now the ProductPresenter.ts class is cleaner.

ProductPresenter

It also forgot to create an index.ts file at the root of the src folder. Let it fix that.

Prompt: create the index.ts file

I see that you forgot to create an index.ts file at the root of the src folder. 
Can you fix that?

Folder architecture with renamed interfaces

Here is the new folder architecture I get:

Folder architecture with renamed interfaces

Next steps

I am satisfied at this level; all this took me about 5 minutes, pretty nice! Here are the next steps:

  • Integrate DI with Tsyringe
  • Improve logging by using Winston to save logs in a /logs folder for easier consultation
  • Add unit tests using Jest
  • Containerize the backend project using Docker