Olá, bem-vindo ao terceiro post da série Como criar uma API REST com Nestjs!

Nessa etapa, vamos continuar o projeto que definimos no último post, conectando-o ao banco de dados e definindo as nossas entidades.

1 - Conectando o projeto ao banco de dados

Vamos começar criando um novo banco de dados no DBeaver, na conexão criada na etapa 1:

Nomeie o banco como my_pricing e coloque como Owner o usuário root

Com o banco de dados criado, vamos conectar o projeto a ele. Para isso, utilizaremos o TypeORM, um módulo de gerenciamento de relações.

2 - Configurando a conexão do banco de dados no projeto

Primeiramente, no Visual Code, vamos instalar as dependências necessárias para integrar nosso projeto ao TypeORM:

yarn add  @nestjs/typeorm typeorm 

e

yarn add pg

Feita a instalação, vamos criar um arquivo ormconfig.json na raiz do projeto, e nele colocar os dados da nossa conexão e configurar o diretório onde iremos armazenar as migrations que serão geradas ao longo do projeto:

{
    "type": "postgres",
    "host": "localhost",
    "port": 5432,
    "user": "root",
    "username": "root",
    "password": "root",
    "database": "my_pricing",
    "synchronize": false,
    "logging": false,
    "entities": [],
    "migrations": [],
    "cli": {
        "migrationsDir": "/src/database/migrations"
    }
}

Após configurar o arquivo, em app.module iremos importar o TypeOrmModule:‌

No arquivo package.json, em scripts, adicione o seguinte comando:

"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js" 

Feito! Nosso projeto está configurado com o banco de dados e poderemos agora criar as nossas entidades.

3 - Criando os modelos

Nesse projeto, tudo o que for relacionado ao banco de dados vamos armazenar em uma pasta database dentro de src. Crie também uma pasta models e migrations dentro da nossa pasta database.

Agora vamos começar a criar os models de acordo com o diagrama abaixo:

User:

import { PrimaryGeneratedColumn, Column, Entity, OneToMany } from 'typeorm';
import { Order } from 'src/database/models/order.model';
import { Product } from 'src/database/models/product.model';

@Entity('user')
export class User {
  @PrimaryGeneratedColumn({ name: 'id', type: 'bigint' })
  id!: string;

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

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

  @Column({ name: 'password', type: 'varchar', select: false })
  password!: string;

  @OneToMany(() => Product, (product) => product.user)
  products: Product[];

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

Input (Insumo):

import {
  Column,
  Entity,
  JoinColumn,
  ManyToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { Product } from 'src/database/models/product.model';

@Entity('input')
export class Input {
  @PrimaryGeneratedColumn({ name: 'id', type: 'bigint' })
  id!: string;

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

  @Column({
    name: 'total_price',
    type: 'decimal',
    default: '0.00',
    precision: 11,
    scale: 2,
  })
  totalPrice!: string;

  @Column({ name: 'used_percentage', type: 'int' })
  usedPercentage!: string;

  @Column({ name: 'product_id', type: 'bigint' })
  productId!: string;

  @ManyToOne(() => Product, (product) => product.inputs)
  @JoinColumn({ name: 'product_id' })
  product?: Product;
}

Product:

import {
  Column,
  Entity,
  JoinColumn,
  ManyToMany,
  ManyToOne,
  OneToMany,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { Input } from 'src/database/models/input.model';
import { Order } from 'src/database/models/order.model';
import { User } from 'src/database/models/user.model';

@Entity('product')
export class Product {
  @PrimaryGeneratedColumn({ name: 'id', type: 'bigint' })
  id!: string;

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

  @Column({
    name: 'total_price',
    type: 'decimal',
    default: '0.00',
    precision: 11,
    scale: 2,
  })
  totalPrice!: string;

  @Column({
    name: 'inputs_price',
    type: 'decimal',
    default: '0.00',
    precision: 11,
    scale: 2,
  })
  inputsPrice!: string;

  @Column({ name: 'profit_percentage', type: 'int' })
  profitPercentage!: string;

  @Column({ name: 'is_avaible', type: 'boolean' })
  isAvaible!: boolean;

  @OneToMany(() => Input, (input) => input.product)
  inputs?: Input[];

  @ManyToMany(() => Order, (order) => order.products, { cascade: true })
  orders?: Order[];

  @Column({ name: 'user_id', type: 'bigint' })
  userId!: string;

  @ManyToOne(() => User, (user) => user.products)
  @JoinColumn({ name: 'user_id' })
  user: User;
}

Order:

import {
  Column,
  CreateDateColumn,
  Entity,
  JoinColumn,
  JoinTable,
  ManyToMany,
  ManyToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { Product } from 'src/database/models/product.model';
import { User } from 'src/database/models/user.model';

@Entity('order')
export class Order {
  @PrimaryGeneratedColumn({ name: 'order_id', type: 'bigint' })
  id!: string;

  @CreateDateColumn({ name: 'created_at', precision: 3 })
  createdAt!: Date;

  @Column({
    name: 'total_price',
    type: 'decimal',
    default: '0.00',
    precision: 11,
    scale: 2,
  })
  totalPrice!: string;

  @Column({
    name: 'inputs_price',
    type: 'decimal',
    default: '0.00',
    precision: 11,
    scale: 2,
  })
  inputsPrice!: string;

  @Column({ name: 'is_avaible', type: 'boolean' })
  isAvaible!: boolean;

  @ManyToMany(() => Product, (product) => product.orders)
  @JoinTable({
    name: 'order_product_order',
    joinColumn: { name: 'order_id' },
    inverseJoinColumn: { name: 'product_id' },
  })
  products?: Product[];

  @Column({ name: 'user_id', type: 'bigint' })
  userId!: string;

  @ManyToOne(() => User, (user) => user.products)
  @JoinColumn({ name: 'user_id' })
  user: User;
}

‌Pronto! Com os models criados vamos agora criar um arquivo index.ts no mesmo diretório para facilitar a exportação dessas classes:

import { Product } from 'src/database/models/product.model';
import { Order } from 'src/database/models/order.model';
import { Input } from 'src/database/models/input.model';
import { User } from 'src/database/models/user.model';

export const entities = [User, Input, Order, Product];

Vamos adicionar um index.ts na pasta de migrations também:

export const migrations = []; 

Não tem problema que nosso array de migrations esteja vazio por enquanto, vamos alimentá-lo no próximo passo.

Por fim, vamos voltar ao nosso arquivo ormconfig.jsonna raiz do projeto e vamos adicionar os diretórios dos models e das migrations:

entities: ["src/database/models/index.ts"], 
"migrations": ["src/database/migrations/index.ts"], 

Feito isso, podemos seguir para a geração e execução da nossa migration!

4 - Gerando uma migration

Para gerar uma migration, basta executar o seguinte comando:

yarn typeorm migration:generate -n InitialMigration 

Feito! Na nossa pasta de migrations deve existir um novo arquivo agora.

O último passo é importar esse arquivo no index da pasta de migrations e executar o comando a seguir:

yarn typeorm migration:run

Agora é só atualizar a conexão no DBeaver que você verá a estrutura do nosso banco já pronta!

Com o banco configurado vamos poder trabalhar com as requisições e começar a adicionar lógica no nosso projeto!

Aguardo você no próximo post!