Continuing from where we left off in the previous tutorial, we shall be creating CRUD resources for our store in Nest.js. CRUD resources help us accelerate our application development time by autogenerating some much needed classes, such as entities, controllers and services that we will be using later on in our application development. It also bundles all the generated code into a single directory which allows it to be tested independently of other resources.

Entities are code representations of database objects, usually tables and views. They allow us to perform database operations, such as CRUD operations, but without having to write SQL code. Controllers handle requests and responses from calling applications such as web browsers, and map an incoming request, such as GET or POST request to our backend logic. A service in Nest.js does the “heavy lifting” by doing all the required processing brought in by the request and outputting a response back to the controller. All our resources will be contained within a modules directory.

In case of any issues, you can refer to my Github repository here.

Generate the CRUD resources

Let’s start by creating CRUD resources for each part of our application (authentication, orders, products and customers). In addition to these, for security reasons, we will create an additional authentication module to assist in handling customer authentication (registration, logins and logouts).

Within the project root run:

nest g resource modules/orders

Output:

? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/orders/orders.controller.spec.ts (576 bytes)
CREATE src/modules/orders/orders.controller.ts (915 bytes)
CREATE src/modules/orders/orders.module.ts (254 bytes)
CREATE src/modules/orders/orders.service.spec.ts (460 bytes)
CREATE src/modules/orders/orders.service.ts (623 bytes)
CREATE src/modules/orders/dto/create-order.dto.ts (31 bytes)
CREATE src/modules/orders/dto/update-order.dto.ts (173 bytes)
CREATE src/modules/orders/entities/order.entity.ts (22 bytes)
UPDATE src/app.module.ts (1180 bytes)
nest g resource modules/products

Output:

? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/products/products.controller.spec.ts (596 bytes)
CREATE src/modules/products/products.controller.ts (957 bytes)
CREATE src/modules/products/products.module.ts (268 bytes)
CREATE src/modules/products/products.service.spec.ts (474 bytes)
CREATE src/modules/products/products.service.ts (651 bytes)
CREATE src/modules/products/dto/create-product.dto.ts (33 bytes)
CREATE src/modules/products/dto/update-product.dto.ts (181 bytes)
CREATE src/modules/products/entities/product.entity.ts (24 bytes)
UPDATE package.json (2618 bytes)
UPDATE src/app.module.ts (1117 bytes)
✔ Packages installed successfully.
nest g resource modules/customers

Output:

? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/modules/customers/customers.controller.spec.ts (606 bytes)
CREATE src/modules/customers/customers.controller.ts (978 bytes)
CREATE src/modules/customers/customers.module.ts (275 bytes)
CREATE src/modules/customers/customers.service.spec.ts (481 bytes)
CREATE src/modules/customers/customers.service.ts (665 bytes)
CREATE src/modules/customers/dto/create-customer.dto.ts (34 bytes)
CREATE src/modules/customers/dto/update-customer.dto.ts (185 bytes)
CREATE src/modules/customers/entities/customer.entity.ts (25 bytes)
UPDATE src/app.module.ts (1120 bytes)
nest g resource modules/auth

Output:

CREATE src/modules/auth/auth.module.ts (81 bytes)
UPDATE src/app.module.ts (1121 bytes)
nest g controller modules/auth

Output:

CREATE src/modules/auth/auth.controller.spec.ts (478 bytes)
CREATE src/modules/auth/auth.controller.ts (97 bytes)
UPDATE src/modules/auth/auth.module.ts (240 bytes)

nest g service modules/auth

Output:

CREATE src/modules/auth/auth.service.spec.ts (446 bytes)
CREATE src/modules/auth/auth.service.ts (88 bytes)
UPDATE src/modules/auth/auth.module.ts (155 bytes)

For all the above resources and modules, I have created a spec directory in each where I have moved all .spec.ts files (spec files) for easier management. For reference, kindly check on my Github repository.

Create the entities

Under the respective CRUD resources, we shall start by creating all our entities, and entity relationships. The entities will be contained within an entities directory:

Customer entity:

src/modules/customers/entities/customer.entity.ts view raw
import { Exclude } from 'class-transformer';
import { Order } from '../../orders/entities/order.entity';
import {
  Column,
  CreateDateColumn,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity('customers')
export class Customer {
  constructor(intialData: Partial<Customer> = null) {
    if (intialData !== null) {
      Object.assign(this, intialData);
    }
  }

  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ type: 'varchar', unique: true })
  email: string;

  @Column({ name: 'phone_number', type: 'varchar' })
  phoneNumber: string;

  @Exclude()
  @Column({ name: 'password_digest', type: 'varchar' })
  password: string;

  @Exclude()
  confirmPassword: string;

  @CreateDateColumn({
    name: 'created_at',
    type: 'timestamptz',
    default: 'now()',
    readonly: true,
  })
  createdAt: string;

  @UpdateDateColumn({
    name: 'updated_at',
    type: 'timestamptz',
    default: 'now()',
  })
  updatedAt: string;

  @OneToMany(() => Order, (order) => order.customer)
  orders: Order[];
}

Product entity:

src/modules/products/entities/product.entity.ts view raw
import { OrderItem } from '../../orders/entities/order-item.entity';
import {
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity('products')
export class Product {
  constructor(intialData: Partial<Product> = null) {
    if (intialData !== null) {
      Object.assign(this, intialData);
    }
  }

  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ type: 'varchar' })
  name: string;

  @Column({ name: 'unit_price', type: 'numeric' })
  unitPrice: number;

  @Column({ type: 'text' })
  description: string;

  @Column({ type: 'varchar' })
  image: string;

  @CreateDateColumn({
    name: 'created_at',
    type: 'timestamptz',
    default: 'now()',
    readonly: true,
  })
  createdAt: string;

  @UpdateDateColumn({
    name: 'updated_at',
    type: 'timestamptz',
    default: 'now()',
  })
  updatedAt: string;

  @DeleteDateColumn({
    name: 'deleted_at',
    type: 'timestamptz',
  })
  deletedAt: string;

  @OneToMany(() => OrderItem, (orderItem) => orderItem.product)
  orderItems: OrderItem[];
}

Order entity:

src/modules/orders/entities/order.entity.ts view raw
import { Customer } from '../../customers/entities/customer.entity';
import {
  Column,
  CreateDateColumn,
  Entity,
  JoinColumn,
  ManyToOne,
  OneToMany,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';
import { OrderItem } from './order-item.entity';
import { PaymentStatus } from '../../../common/enums/payment-status.enum';

@Entity('orders')
export class Order {
  constructor(intialData: Partial<Order> = null) {
    if (intialData !== null) {
      Object.assign(this, intialData);
    }
  }

  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ name: 'customer_id', type: 'uuid' })
  customerId: string;

  @Column({ name: 'total_amount', type: 'numeric' })
  totalAmount: number;

  @Column({
    name: 'payment_status',
    type: 'varchar',
    default: PaymentStatus.Created,
  })
  paymentStatus: PaymentStatus;

  @CreateDateColumn({
    name: 'created_at',
    type: 'timestamptz',
    default: 'now()',
    readonly: true,
  })
  createdAt: string;

  @UpdateDateColumn({
    name: 'updated_at',
    type: 'timestamptz',
    default: 'now()',
  })
  updatedAt: string;

  @ManyToOne(() => Customer, (customer) => customer.orders)
  @JoinColumn({ name: 'customer_id', referencedColumnName: 'id' })
  customer: Customer;

  @OneToMany(() => OrderItem, (orderItem) => orderItem.order, { cascade: true })
  orderItems: OrderItem[];
}

Order item entity:

src/modules/orders/entities/order-item.entity.ts view raw
import { Product } from '../../products/entities/product.entity';
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
import { Order } from './order.entity';

@Entity('order_items')
export class OrderItem {
  constructor(intialData: Partial<OrderItem> = null) {
    if (intialData !== null) {
      Object.assign(this, intialData);
    }
  }

  @PrimaryColumn({ name: 'order_id', type: 'uuid' })
  orderId: string;

  @PrimaryColumn({ name: 'product_id', type: 'uuid' })
  productId: string;

  @Column({ name: 'unit_price', type: 'numeric' })
  unitPrice: number;

  @Column({ type: 'numeric' })
  quantity: number;

  @ManyToOne(() => Product, (product) => product.orderItems)
  @JoinColumn({ name: 'product_id', referencedColumnName: 'id' })
  product: Product;

  @ManyToOne(() => Order, (order) => order.orderItems)
  @JoinColumn({ name: 'order_id', referencedColumnName: 'id' })
  order: Order;
}

In all the above entities, I have also modelled the 1 to many, as well as many to 1 relationships that we had in our entity relationship diagram.

SOURCES