DEV Community

Cover image for Configurando Typeorm + Primeiro CRUD
Vitor Silva Delfino
Vitor Silva Delfino

Posted on

Configurando Typeorm + Primeiro CRUD

Dando continuidade ao post anterior, 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

Typeorm

Como o próprio nome ja diz, o Typeorm é 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:

yarn add typeorm reflect-metadata mongodb && yarn add @types/mongodb -D
Enter fullscreen mode Exit fullscreen mode

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

.
.
.
import 'reflect-metadata';

class App {
 .
 .
 .
}

Enter fullscreen mode Exit fullscreen mode

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

src/config/index.ts

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),
  },
};
Enter fullscreen mode Exit fullscreen mode

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

src/config/db/index.ts

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;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

src/server.ts

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 });
  });
});

Enter fullscreen mode Exit fullscreen mode

E agora para testar nossa conexão, vamos utilizar do Docker/Docker Compose para subir uma imagem do MongoDB

docker-compose.yml

version: '3'

volumes:
  mongo_volume:
    driver: local

services:
  mongo:
    image: mongo
    container_name: mongo_example
    ports:
      - '27017:27017'
Enter fullscreen mode Exit fullscreen mode

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

docker-compose up -d
yarn start:dev
Enter fullscreen mode Exit fullscreen mode

Alt Text

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 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

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;
}

Enter fullscreen mode Exit fullscreen mode

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

src/config/db/index.ts

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;
Enter fullscreen mode Exit fullscreen mode

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

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

Alt Text

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

CRUD

Vamos escrever o crud de usuários

src/apps/Users/UserService.ts

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();
Enter fullscreen mode Exit fullscreen mode

src/apps/Users/UsersController.ts

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);
};

Enter fullscreen mode Exit fullscreen mode

src/apps/routes.ts

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;
Enter fullscreen mode Exit fullscreen mode

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

src/routes.ts

import { Router } from 'express';

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

const route = Router();

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

export default route;
Enter fullscreen mode Exit fullscreen mode

Testando o CRUD

Instale a extension REST Client.

Na raiz do projeto crie um arquivo requests.http

  • Criando usuário

requests.http

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

{
  "name": "Vitor",
  "document": "42780908890",
  "password": "1234"
}
Enter fullscreen mode Exit fullscreen mode

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

Alt Text

  • Buscando o usuário pelo Id

requests.http

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

Enter fullscreen mode Exit fullscreen mode

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

Alt Text

  • Atualizando o nome

requests.http

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

{
  "name": "Vitor Delfino"
}


Enter fullscreen mode Exit fullscreen mode

Alt Text

  • Excluindo o usuário

requests.http

.
.
.
DELETE http://localhost:3000/api/users/6001abf43d4675bc1aa693bd HTTP/1.1
Enter fullscreen mode Exit fullscreen mode

Error Handler

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

Alt Text

Alt Text

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.

Discussion (0)