DEV Community

Krrish Paul
Krrish Paul

Posted on

Create an e-commerce backend API using Node.js(TypeScript) and MongoDB

In this tutorial, we’ll walk through building a robust e-commerce backend API using TyphoonTS, TypeORM, and MongoDB with TypeScript. We’ll cover initializing the project, setting up TypeORM with MongoDB, defining entities, creating services and controllers, and running the server with Nodemon for a seamless development experience. By the end, you’ll have a scalable backend ready for your e-commerce application, leveraging the power of TypeScript’s type safety and the flexibility of MongoDB. Let’s get started on this exciting journey!

TyphoonTS is chosen for its robust and type-safe framework, making it ideal for building scalable web applications in TypeScript. So, let’s get started…

1. Initialize Your Project

mkdir -p ecommerce-backend/src
cd ecommerce-backend
npm init -y
npm install typhoonts typeorm reflect-metadata mongodb joi
npm install -D @types/node nodemon ts-node ts-node-dev typescript
Enter fullscreen mode Exit fullscreen mode

2. Set Up TypeScript Configuration

Create a tsconfig.json file in the root directory:

{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "outDir": "./dist"
  },
  "include": ["src/**/*"]
}
Enter fullscreen mode Exit fullscreen mode

3. Configure TypeORM

In this step, we configure TypeORM to connect to our MongoDB database and manage our entities.

Create a data-source.ts file in the src directory:

import { DataSource } from "typeorm";
import Product from "./entities/Product";

export const AppDataSource = new DataSource({
  type: "mongodb",
  url: "mongodb://127.0.0.1:27017/ecommerce",
  synchronize: true,
  useUnifiedTopology: true,
  entities: [Product],
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • DataSource: This is TypeORM’s primary configuration object for database connections.
  • type: Specifies the database type; here, it’s MongoDB.
  • url: The connection URL to the MongoDB server.
  • synchronize: Automatically synchronizes the database schema with the entity definitions.
  • useUnifiedTopology: Enables the new unified topology layer for MongoDB.
  • entities: Lists the entities to be managed by TypeORM. Here, it’s the Product entity.

This configuration initializes the connection to the MongoDB database and sets up TypeORM to manage the specified entities.

4. Define Your Product Entity

Create an entities directory inside src and add Product.ts:

import { Column, Entity, ObjectId, ObjectIdColumn } from "typeorm";

@Entity({ name: "products" })
class Product {
  @ObjectIdColumn()
  id!: ObjectId;

  @Column()
  name!: string;

  @Column()
  price!: number;

  @Column()
  description!: string;
}

export default Product;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • @Entity: Marks the class as a database entity, with the table name “products”.
  • @ObjectIdColumn: Specifies the id field as the primary key and stores MongoDB ObjectIds.
  • @Column: Indicates that the following fields are columns in the database table.
  • id: Unique identifier for each product.
  • name: Name of the product.
  • price: Price of the product.
  • description: Description of the product.

This defines the Product entity structure in TypeORM, mapping it to a MongoDB collection.

5. Create Product DAO(Data Access Object)

Create a dao directory and add ProductDao.ts:

interface ProductDao {
  name: string;
  price: number;
  description: string;
}

export default ProductDao;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ProductDao Interface: Defines the data structure for Product data access objects.
  • name: Represents the name of the product.
  • price: Represents the price of the product.
  • description: Represents the description of the product.

This interface outlines the shape of the product data, ensuring consistency in how product information is accessed and manipulated throughout the application.

6. Create Product DTO(Data Access Object)

Create a dto directory and add ProductDto.ts:

export interface ProductListDto {
  id: string;
  name: string;
  price: number;
}
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ProductListDto Interface: Defines the data transfer object structure for listing products.
  • id: Represents the unique identifier of the product.
  • name: Represents the name of the product.
  • price: Represents the price of the product.

This DTO specifies the shape of the product data to be transferred between processes or over the network, ensuring consistency and type safety.

7. Create Product Service

Create a services directory and add ProductService.ts:

import { ObjectId } from "mongodb";
import { Service } from "typhoonts";
import ProductDao from "../dao/ProductDao";
import { AppDataSource } from "../data-source";
import Product from "../entities/Product";

@Service()
class ProductService {
  private productRepository = AppDataSource.getMongoRepository(Product);

  async create(product: ProductDao) {
    return this.productRepository.save(product);
  }

  async getAll() {
    return (await this.productRepository.find()).map((product: Product) => ({
      id: product.id.toString(),
      name: product.name,
      price: product.price,
    }));
  }

  async get(id: string) {
    const productId = new ObjectId(id);
    return this.productRepository.findOne({ where: { _id: productId } });
  }

  async update(id: string, product: ProductDao) {
    return this.productRepository.update(id, product);
  }
}

export default ProductService;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ProductService Class: Provides methods to manage product data.
  • productRepository: Accesses the MongoDB collection for products.
  • create: Saves a new product to the database.
  • getAll: Retrieves all products, mapping them to a simplified structure.
  • get: Retrieves a single product by its ID.
  • update: Updates an existing product with new data. This service encapsulates the business logic for handling product data, interacting with the database through TypeORM.

8. Create Product Controller

Create a controllers directory and add ProductController.ts:

import Joi from "joi";
import {
  Body,
  Controller,
  Get,
  Inject,
  Param,
  Post,
  Put,
  ResponseBody,
} from "typhoonts";
import ProductDao from "../dao/ProductDao";
import { ProductListDto } from "../dto/ProductDto";
import Product from "../entities/Product";
import ProductService from "../services/ProductService";

@Controller("/products")
class ProductController {
  @Inject(ProductService)
  private productService!: ProductService;

  @Post("/")
  @ResponseBody()
  async create(@Body() productReqDao: ProductDao) {
    const productSchema = Joi.object({
      name: Joi.string().min(3).max(30).required(),
      price: Joi.number().positive().required(),
      description: Joi.string().optional(),
    });
    const productValidate = await productSchema.validate(productReqDao);

    if (productValidate?.error) {
      return { status: 400, error: productValidate.error.details };
    }

    const product: Product = await this.productService.create(productReqDao);

    return {
      message: `${product.name} has been created`,
    };
  }

  @Get("/")
  @ResponseBody()
  async getAll() {
    const products: ProductListDto[] = await this.productService.getAll();
    return products;
  }

  @Get("/:id")
  @ResponseBody()
  async get(@Param("id") id: string) {
    const product: Product | null = await this.productService.get(id);
    return (
      product || {
        message: "Product not found",
      }
    );
  }

  @Put("/:id")
  @ResponseBody()
  async update(@Param("id") id: string, @Body() productReqDao: ProductDao) {
    const productSchema = Joi.object({
      name: Joi.string().min(3).max(30).required(),
      price: Joi.number().positive().required(),
      description: Joi.string().optional(),
    });
    const productValidate = await productSchema.validate(productReqDao);

    if (productValidate?.error) {
      return { status: 400, error: productValidate.error.details };
    }

    await this.productService.update(id, productReqDao);

    return {
      message: `${productReqDao.name} has been updated`,
    };
  }
}

export default ProductController;
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • ProductController Class: Manages HTTP requests for product operations.
  • @Inject(ProductService): Injects the ProductService into the controller.
  • create: Validates and creates a new product using ProductService.
  • getAll: Retrieves all products via ProductService.
  • get: Retrieves a specific product by ID via ProductService.
  • update: Validates and updates an existing product using ProductService.
  • Validation: Uses Joi for request data validation.
  • ResponseBody: Ensures the responses are in JSON format.

This controller handles the endpoints for creating, retrieving, and updating products, utilizing the ProductService for business logic and Joi for request validation.

9. Set Up the Server

Create a server.ts file in the src directory:

import "reflect-metadata";
import { Server } from "typhoonts";
import ProductController from "./controllers/ProductController";
import { AppDataSource } from "./data-source";

const server = new Server({
  useBodyParser: true,
  useCookieParser: true,
  useSession: true,
  sessionOptions: { secret: "my_secret_key" },
});

server.registerController(ProductController);

server.listen(8000, () => {
  console.log("server is running on http://localhost:8000");

  AppDataSource.initialize()
    .then(async () => {
      console.log("DB initialize");
    })
    .catch((error) =>
      console.log("Error during Data Source initialization:", error)
    );
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  • Imports and Initialization: Imports necessary modules and initializes the server.
  • Server Configuration: Configures the server to use body parser, cookie parser, and session with a secret key.
  • Register Controller: Registers the ProductController to handle product-related routes.
  • Start Server: Starts the server on port 8000 and logs the URL.
  • Initialize Database: Initializes the database connection using AppDataSource and logs success or error messages.

This sets up and runs the server, connecting it to the database and registering the product routes.

10. Set Up Nodemon

Add a nodemon.json file in the root of your project:

{
  "watch": [
    "src"
  ],
  "ext": "ts",
  "ignore": [
    "src/**/*.spec.ts"
  ],
  "exec": "ts-node-dev src/server.ts"
}
Enter fullscreen mode Exit fullscreen mode

11. Update package.json Scripts

Add a dev script to your package.json:

"scripts": {
  "start": "ts-node src/server.ts",
  "build": "tsc",
  "dev": "nodemon"
 },
Enter fullscreen mode Exit fullscreen mode

12. Run the Project

npm run dev
Enter fullscreen mode Exit fullscreen mode

Conclusion

You’ve now set up a basic e-commerce backend API using TyphoonTS, TypeORM, and MongoDB with TypeScript. This setup includes entity definitions, service layers, and controllers, all while leveraging TypeORM for database operations and TyphoonTS for server and routing management. Happy coding!

Github

Feel free to reach out to me if you have any questions or need further assistance. Connect with me on LinkedIn.

Top comments (0)