Configurando Typeorm + Primeiro CRUD
Vitor Silva Delfino
Posted on January 15, 2021
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
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 {
.
.
.
}
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),
},
};
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;
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
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/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'
Vamos subir o banco e iniciar a api e ver o que aparece no console.
docker-compose up -d
yarn start:dev
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;
}
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;
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
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();
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);
};
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;
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;
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"
}
Repare que vai ter uma label escrito Send Request
, clique nela e o request será realizado.
- Buscando o usuário pelo Id
requests.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á.
- Atualizando o nome
requests.http
.
.
.
PUT http://localhost:3000/api/users/6001abf43d4675bc1aa693bd HTTP/1.1
Content-Type: application/json
{
"name": "Vitor Delfino"
}
- Excluindo o usuário
requests.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
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.
Posted on January 15, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.