Configurando Typeorm + Primeiro CRUD
Tags: node, braziliandevs, typescript, javascript
Published: 2021-01-15 20:41:25

Dando continuidade ao [post anterior](https://dev.to/vitordelfino/criando-e-testando-uma-api-com-node-typescript-typeorm-jest-swagger-part-1-di7), hoje vamos configurar o Typeorm e escrever o primeiro crud.

>OBS: Sugiro que para o bom entendimento do tutorial, o caro leitor já esteja familiarizado com uso do [Docker](https://www.docker.com/get-started)

## Typeorm

Como o próprio nome ja diz, o [Typeorm](https://typeorm.io/#/) é o cara que vai nos ajudar a conectar no banco e manipular seus dados.

Sem muita enrolação, bora pro código.

### Instalações

Começamos instalando algumas dependências:

```bash
yarn add typeorm reflect-metadata mongodb && yarn add @types/mongodb -D
```

Após o fim da instalação, precisamos importar o reflect-metadata em um arquivo global da nossa aplicação.

### Configurações

`src/app.ts`
```typescript
.
.
.
import 'reflect-metadata';

class App {
 .
 .
 .
}

```

Vamos atualizar o nosso arquivo global de environments com alguns novos dados:

`src/config/index.ts`
```typescript
import { config } from 'dotenv';

const envfile = `.env.${process.env.NODE_ENV}`;
const envdir = process.cwd();

config({ path: `${envdir}/${envfile}` });

export const server = {
  port: process.env.PORT,
  env: process.env.NODE_ENV,
};

// dados de conexão com o banco
export const dbConnections = {
  mongo: {
    name: 'mongo',
    conn: String(process.env.DATABASE_MONGO_CONN),
  },
};
```

Agora criamos as nossas configurações de conexão com o banco:

`src/config/db/index.ts`
```typescript
import { createConnections } from 'typeorm';

import { dbConnections, server } from '../index';

const connection = createConnections([
  {
    name: dbConnections.mongo.name,
    type: 'mongodb',
    url: dbConnections.mongo.conn,
    entities: [],
    useNewUrlParser: true,
    useUnifiedTopology: true,
    synchronize: server.env === 'dev', // Se o ambiente for dev, o typeorm se incarrega de gerar e alterar as tabelas
  },
]);

export default connection;
```

Após todas as configurações feitas, precisamos alterar o start da nossa aplicação e também adicionar a url de conexão nas variaveis de ambiente.

`.env.dev`
```
PORT=3000
DATABASE_MONGO_CONN=mongodb://localhost:27017/example
```

Primeiro conectamos na base, e no sucesso da conexão startamos a API.

`src/server.ts`
```typescript
import connection from '@config/db';
import { server } from '@config/index';

import logger from '@middlewares/logger';

connection.then(() => {
  logger.info(`Database connected`);
  // precisamos importar o express somente após a conexão com a base, ou então o typeorm vai reclamar que alguns repositories não existem
  require('./app').default.app.listen(server.port, () => {
    logger.info('Server running', { port: server.port, mode: server.env });
  });
});

```

E agora para testar nossa conexão, vamos utilizar do [Docker](https://www.docker.com/get-started)/[Docker Compose](https://docs.docker.com/compose/install/) para subir uma imagem do MongoDB

`docker-compose.yml`
```yaml
version: '3'

volumes:
  mongo_volume:
    driver: local

services:
  mongo:
    image: mongo
    container_name: mongo_example
    ports:
      - '27017:27017'
```

Vamos subir o banco e iniciar a api e ver o que aparece no console.

```bash
docker-compose up -d
yarn start:dev
```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/a2tgw60et1928zq5feuy.png)

## Primeira Entidade

Já estamos conectando na base de dados, mas ainda não temos nenhuma entididade definida.

Vamos escrever nossa primeira entidade.

>Com o typeorm, nós definimos uma classe com alguns [@decorators](https://www.typescriptlang.org/docs/handbook/decorators.html) e a mágica acontece:

Adicionamos uma nova pastinha na nossa estrutura: `src/apps/Users`

>A única regra na nossa collection de usuários é que não pode existir dois com o mesmo documento

`src/apps/Users/Users.entity.ts`
```typescript
import {
  BaseEntity,
  Column,
  CreateDateColumn,
  Entity,
  Index,
  ObjectIdColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity()
export class Users extends BaseEntity {
  @ObjectIdColumn({
    type: 'uuid',
  })
  _id!: string;

  @Column()
  name!: string;

  @Column()
  @Index({ unique: true })
  document!: string;

  @Column()
  password!: string;

  @CreateDateColumn({
    type: 'timestamp',
  })
  createdAt!: Date;

  @UpdateDateColumn({
    type: 'timestamp',
    nullable: true,
  })
  updatedAt?: Date;
}

```

E por final vamos passar nossas entidades nas configurações do typeorm:

`src/config/db/index.ts`
```typescript
import { createConnections } from 'typeorm';

import { Users } from '@apps/Users/Users.entity';

import { dbConnections, server } from '../index';

const connection = createConnections([
  {
    name: dbConnections.mongo.name,
    type: 'mongodb',
    url: dbConnections.mongo.conn,
    entities: [Users],
    useNewUrlParser: true,
    useUnifiedTopology: true,
    synchronize: server.env === 'dev', // Se o ambiente for dev, o typeorm se incarrega de gerar e alterar as tabelas
  },
]);

export default connection;
```

Após o restart da aplicação, já conseguimos ver a collection de usuários criada, para isso faça o download da extension [MongoDB for VS Code](https://marketplace.visualstudio.com/items?itemName=mongodb.mongodb-vscode)

Clique no ícone do mongodb e configure a url de conexão

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/25mz8hbr46v2d6cadvz0.png)

Com a base conectada e a collection criada, vamos escrever nossa classe de serviço e as rotas.

## CRUD

Vamos escrever o [crud](https://pt.stackoverflow.com/questions/359961/o-que-caracteriza-um-projeto-crud-b%C3%A1sico#:~:text=CRUD%20%C3%A9%20o%20acr%C3%B4nimo%20da,em%20Banco%20de%20Dados%20Relacionais.) de usuários

`src/apps/Users/UserService.ts`
```typescript
import { CustomError } from 'express-handler-errors';
import { ObjectID } from 'mongodb';
import { getConnection, MongoRepository } from 'typeorm';

import { dbConnections } from '@config/index';

import { Users } from './Users.entity';

class UserService {
  private readonly repository: MongoRepository<Users>;

  constructor() {
    this.repository = getConnection(
      dbConnections.mongo.name
    ).getMongoRepository(Users);
  }

  async create(user: Users): Promise<Users> {
    try {
      const response = await this.repository.save(user);
      return response;
    } catch (e) {
      if (e.code === 11000)
        throw new CustomError({
          code: 'USER_ALREADY_EXISTS',
          message: 'Usuário já existente',
          status: 409,
        });
      throw e;
    }
  }

  async findOne(_id: string): Promise<Users> {
    const user = await this.repository.findOne(_id);
    if (!user)
      throw new CustomError({
        code: 'USER_NOT_FOUND',
        message: 'Usuário não encontrado',
        status: 404,
      });

    return user;
  }

  async update(_id: string, name: string): Promise<Users> {
    await this.repository.updateOne(
      {
        _id: new ObjectID(_id),
      },
      {
        $set: {
          name,
        },
      }
    );
    return this.findOne(_id);
  }

  async delete(_id: string): Promise<Users> {
    const user = await this.findOne(_id);
    await this.repository.deleteOne({
      _id: new ObjectID(_id),
    });
    return user;
  }
}

export default new UserService();
```

`src/apps/Users/UsersController.ts`
```typescript
import { Request, Response } from 'express';

import UserService from './UserService';

export const create = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const response = await UserService.create(req.body);
  return res.json(response);
};

export const findOne = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const response = await UserService.findOne(req.params.id);
  return res.json(response);
};

export const update = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const response = await UserService.update(req.params.id, req.body.name);
  return res.json(response);
};

export const deleteOne = async (
  req: Request,
  res: Response
): Promise<Response> => {
  const response = await UserService.delete(req.params.id);
  return res.json(response);
};

```

`src/apps/routes.ts`
```typescript
import { Router } from 'express';

import * as controller from './UserController';

const route = Router();

route.post('/', controller.create);
route.get('/:id', controller.findOne);
route.put('/:id', controller.update);
route.delete('/:id', controller.deleteOne);

export default route;
```

E por final configuramos a rota de usuários, no arquivo global das rotas;

`src/routes.ts`
```typescript
import { Router } from 'express';

import UserRoutes from '@apps/Users/routes';

const route = Router();

route.use('/users', UserRoutes);

export default route;
```

## Testando o CRUD

Instale a extension [REST Client](https://marketplace.visualstudio.com/items?itemName=humao.rest-client).

Na raiz do projeto crie um arquivo `requests.http`

- Criando usuário

`requests.http`
```http
POST http://localhost:3000/api/users HTTP/1.1
Content-Type: application/json

{
  "name": "Vitor",
  "document": "42780908890",
  "password": "1234"
}
```

Repare que vai ter uma label escrito `Send Request`, clique nela e o request será realizado.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/3tu0g5c4dnwv3v9zbhf8.png)

- Buscando o usuário pelo Id

`requests.http`
```http
.
.
.
GET http://localhost:3000/api/users/6001abf43d4675bc1aa693bd HTTP/1.1

```

Se atualizarmos a aba do mongodb, também podemos buscar o usuário lá.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/okpmuajdtzo6v623srg7.png)

- Atualizando o nome

`requests.http`
```http
.
.
.
PUT http://localhost:3000/api/users/6001abf43d4675bc1aa693bd HTTP/1.1
Content-Type: application/json

{
  "name": "Vitor Delfino"
}


```

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/el22wlpjwq1ou65bm2if.png)

- Excluindo o usuário

`requests.http`
```http
.
.
.
DELETE http://localhost:3000/api/users/6001abf43d4675bc1aa693bd HTTP/1.1
```

## Error Handler

E como ficaram os responses caso o usuário não exista ou o documento ja tenha sido cadastrado

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/bn658zookaowe0f9ot0i.png)

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pn3kztrpj8vrhvcq2crw.png)


## Considerações finais

Hoje configuramos o primeiro serviço da nossa aplicação.

Na estrutura sugerida, os serviços ficam dentro de uma pasta apps e caso a aplicação aumente muito, e cada serviço precise se tornar uma aplicação a parte, está fácil fazer a quebra do nosso mini monolito.

Só precisamos fazer a configuração básica do ultimo post para cada serviço desaclopado.

## O que está por vir

No próximo post, vamos fazer algumas validações com [Yup]() antes de cadastrar usuários, e também escrever um swagger.

E pra facilitar o teste das nossas rotas, vamos configurar um [Insomnia](https://insomnia.rest/download/).