Olá! Boas-vindas! Neste curso, vamos nos aprofundar no TypeScript.
Meu nome é Emerson Laranja, sou instrutor de back-end.
Audiodescrição: Emerson se descreve como um homem negro de rosto arredondado. Ele tem barba e cabelos curtos e escuros. Usa óculos retangulares e veste uma camiseta lisa na cor azul. Ao fundo, uma parede branca iluminada em tons de verde e azul.
Ao longo desse curso, você aprenderá alguns assuntos, como criar Objetos de Transferência de Dados (DTOs), utilizando o conceito de tipos utilitários do TypeScript. Também vamos validar os dados da nossa API através da Biblioteca Yup. Aprenderemos a criar erros personalizados e além de uma série de boas práticas.
O que iremos aprender:
- Criar DTOs
- Biblioteca Yup
- Criar erros personalizados
- Boas práticas
É recomendável que você já tenha algum conhecimento de JavaScript. Para isso, você pode acessar a formação Aprenda a programar em JavaScript com foco no back-end, na nossa plataforma. Lembramos que este é o segundo curso de uma sequência de cursos de TypeScript. Portanto, é essencial que você tenha concluído o curso TypeScript: construção de uma API com tipagem segura, que é o primeiro curso de TypeScript para back-end.
Vamos explorar tudo isso no nosso projeto Adopet, um sistema de gerenciamento de animais. Convidamos você a não apenas assistir aos vídeos do curso, mas também realizar os exercícios, tirar suas dúvidas no fórum do curso e interagir com outras pessoas na comunidade Alura do Discord.
Vamos começar?
Vamos dar continuidade ao nosso projeto do curso 1. Não se preocupe, vamos disponibilizar uma atividade com a configuração inicial do projeto. Além disso, adicionei ao projeto uma pasta chamada "docs", onde você encontrará os arquivos do Insomnia (software para fazer requisições) para o nosso projeto Adopet. Também disponibilizei o diagrama de entidade de relacionamento do Adopet.
Vamos começar pensando na segurança dos nossos dados no Adopet. Se acessarmos "src > controller > AdotanteController.ts
", percebemos que, nesse arquivo, estamos recebendo todas as informações do req.body
e retornando-as após criar um novo adotante.
Portanto, criamos um adotante, registramos no nosso banco de dados e retornamos essas mesmas informações. Isso pode ser um problema, já que estamos retornando também a senha, expondo a senha desse usuário. O ideal é conseguirmos criar o que se chama de DTO (Data Transfer Objects ou Objetos de Transferência de Dados), onde decidimos o que queremos receber e o que queremos retornar. Ou seja, vamos armazenar a senha no banco de dados, mas não vamos retornar isso para a pessoa usuária.
Para isso, vamos usar o conceito de tipos. Vamos começar abrindo o nosso menu Explorer (Explorar), no lado esquerdo do VS Code, e selecionaremos a pasta "tipos". Em seguida, clicaremos no ícone de criar um novo arquivo, clicando no ícone do canto superior direito da coluna, e criaremos o arquivo tipoAdotantes.ts
.
Nesse novo arquivo, criaremos o nosso primeiro tipo, que é o tipo da nossa requisição do nosso body
, o que estamos recebendo do usuário. Vamos chamá-lo de TipoRequestBodyAdotante
, então escreveremos, na primeira linha, type TipoRequestBodyAdotante = {}
.
Poderíamos usar o próprio AdotanteEntity
. Se pesquisarmos na barra centro-superior por AdotanteEntity
e acessarmos esse código, percebemos que a AdotanteEntity
temos todos os campos do nosso adotante.
//código omitido
@Entity()
export default class AdotanteEntity {
@PrimaryGeneratedColumn()
id!: number;
@Column()
nome: string;
@Column()
senha: string;
@Column()
celular: string;
@Column({ nullable: true })
foto?: string;
//código omitido
Mas não queremos passar o id
, então, voltaremos ao tiposAdotante.ts
, onde criaremos um novo tipo que omite esse id
. Para isso, utilizaremos um tipo do TypeScript chamado Omit<>
. Entre os sinais de menor que e maior que, passamos a entidade como referência, que no caso é o nosso AdotanteEntity
, e qual campo queremos omitir, que no caso é o id
.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
Nessa linha, criamos um tipo que vai ter todos os campos dentro do nosso AdotanteEntity
, menos id
, porque não precisamos receber isso do usuário. Em seguida, duplicaremos essa linha de código e mudaremos de "Request" para "Response", no nome da variável, fazendo referência ao nosso objeto de retorno.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoResponseBodyAdotante = Omit<AdotanteEntity, "id">;
Na resposta, ao invés de omitirmos vários campos, queremos selecionar quais campos queremos retornar. Por exemplo, retornar apenas o id
, o nome
e o celular
. Para isso, ao invés de usar o Omit<>
, poderíamos usar o Pick<>
(Escolha), com o qual selecionamos, passando os campos desejados, separando os valores com o pipe (|
).
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoResponseBodyAdotante = Pick<AdotanteEntity, "id" | "nome" | "celular">;
Agora criamos um tipo que possui apenas id
, nome
e celular
, tendo como referência nosso AdotanteEntity
. Precisamos agora exportar esses dois tipos para aplicá-los ao nosso AdotanteController
.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoResponseBodyAdotante = Pick<AdotanteEntity, "id" | "nome" | "celular">;
export { TipoRequestBodyAdotante, TipoResponseBodyAdotante };
Voltando para o nosso AdotanteController.ts
, iremos para a função criaAdotante()
, por volta da linha 8. Se colocarmos o mouse sobre esse Request
, com um R maiúsculo, surge uma mensagem informando que o Express nos fornece, como parâmetros, os Params
, um ResBody
e um ReqBody
, que é justamente o corpo da nossa requisição.
Então, após o Request
, adicionaremos um sinal de menor que e maior que passando um objeto vazio no primeiro e no segundo parâmetro, porque o terceiro parâmetro e o corpo da nossa requisição. No nosso caso, o TipoRequestBodyAdotante
, então codamos req: Request<{},{},TipoRequestBodyAdotante>
.
export default class AdotanteController {
constructor(private repository: AdotanteRepository) {}
async criaAdotante(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response
) {
//código omtido
}
}
Para entender porque eu fiz isso, dentro das chaves da função criaAdotante()
, vamos escrever req.
. Agora temos acesso a todos os métodos do Express. Se escrevermos req.body
, teremos acesso ao body apenas com os campos que definimos. Portanto, temos a vantagem de poder utilizar as duas coisas, então faremos o mesmo com o Response
, onde o nosso primeiro parâmetro é o nosso ResBody
, que é o nosso TipoResponseBodyAdotante
.
export default class AdotanteController {
constructor(private repository: AdotanteRepository) {}
async criaAdotante(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response<TipoResponseBodyAdotante>
) {
//código omtido
}
}
Faremos uma pequena modificação no nosso tipo, porque precisamos retornar erro em alguns casos. Por exemplo, na linha 37 do AdotanteController
, estamos retornando um erro em uma mensagem. Em outros casos, precisamos retornar uma informação ou um dado, como na linha 26, onde retornamos o novoAdotante
.
Então, voltaremos ao tiposAdotante.ts
. Na linha onde criamos o TipoResponseBodyAdotante
, recortaremos toda a informação que passamos, selecionando-a e pressionando "Ctrl + X", e atribuiremos um objeto que possui um data
como campo opcional, porque vamos querer retornar um dado ou um erro.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoResponseBodyAdotante = {
data?: Pick<AdotanteEntity, "id" | "nome" | "celular">;
}
export { TipoRequestBodyAdotante, TipoResponseBodyAdotante };
Voltando no nosso AdotanteController.ts
, precisamos fazer uma modificação no retorno da função criaAdotante
, aproximadamente na linha 27. Em .json(novoAdotante)
, ao selecionarmos o novoAdotante
e substituirmos por chaves ({}
), o VS Code nos auxilia, mostrando que ele espera um data
.
Ao escrevermos data:{}
, ele indica o que precisamos passar, no caso, id, nome, celular
. Ele está mercado o id
com um erro, porque não recebemos mais isso da pessoa usuária. Então, temos que passar o id
que estamos criando para o nosso novo adotante, escrevendo id:novoAdotante.id
.
//código omitido
async criaAdotante(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response<TipoResponseBodyAdotante>
) {
//código omitido
await this.repository.criaAdotante(novoAdotante);
return res.status(201).json({ data: { id: novoAdotante.id, nome, celular } });
}
//código omitido
O servidor está funcionando e parece que está tudo certo através do VS Code, portanto, vamos testar no Insomnia.
Ao abrirmos o Insomnia, na coluna da esquerda acessaremos "Adotante > Cria Adotante". Na requisição, manteremos os dados de nome, senha e celular.
{
"nome":"Emerson",
"senha":"Exemplo1!",
"celular":"27999999999",
}
Preview:
{ "data": { "nome":"Emerson", "celular":"27999999999", } }
Notamos que, quando enviamos a requisição, recebemos apenas as informações de nome e celular, então não expomos mais nossa senha para todo mundo. Desse modo, conseguimos utilizar tipos utilitários do TypeScript: o Omit, para omitir informações, e o Pick, para selecionar os campos que queremos retornar.
Criamos uma segurança dos dados que recebemos e os dados que retornamos. Na sequência, aprenderemos como replicar isso para todo o nosso CRUD. Até lá!
O que nós fizemos até o momento foi criar uma validação da nossa entrada e nossa saída na nossa função criaAdotante()
. Porém, precisamos fazer isso para o restante do nosso CRUD, para quando formos atualizar, deletar e assim em diante.
Para isso, dentro do nosso arquivo AdotanteController.ts
, copiaremos as linhas de definição do req
e do res
, dentro dos parâmetros de criaAdotante()
. Em seguida, faremos a substituição de parâmetros de todas as funções que têm req
e res
, ou seja, atualizaAdotante()
, listaAdotantes()
e deletaAdotante()
.
Vamos até uma das funções, selecionaremos o trecho req: Request, res: Response
e pressionaremos "Ctrl + D" para fazer a seleção múltipla de todos os locais com esse trecho. Após selecionarmos os parâmetros em todas as funções, pressionaremos "Ctrl + V" para colar, substituindo todos os parâmetros.
Recebemos diversos erros após essa substituição, e a nossa missão agora é resolver cada um deles. Começando pela função atualizaAdotante()
, se deixarmos o mouse por cima do id
, recebemos a informação de que a propriedade id
não existe no tipo Objeto Vazio ({}
). Isso acontece porque, no nosso Request
, como dissemos tem os params
como primeiro parâmetro, e estamos passando um objeto vazio.
Podemos fazer como fizemos com o nosso body
, que é criar um tipo para ele. Para isso, abriremos novamente o arquivo tiposAdotante.ts
. Copiaremos a linha TypeRequestBodyAdotante = Omit<AdotanteEntity, "id">;
, pressionaremos "Enter" após essa linha e colaremos o trecho copiado no espaço abaixo. Nessa segunda linha, mudaremos o Body
para Params
, e podemos adicionar mais linhas em branco para melhorar a visualização.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoRequestParamsAdotante = Omit<AdotanteEntity, "id">;
type TipoResponseBodyAdotante = {
data?: Pick<AdotanteEntity, "id" | "nome" | "celular">;
}
export { TipoRequestBodyAdotante, TipoResponseBodyAdotante };
Os parâmetros que estamos recebendo dentro de Req.Params
são, basicamente o id
. Para isso, atribuiremos a essa nova variável um objeto que recebe o id: string
. Feito isso, podemos exportar o nosso TipoRequestParamsAdotante
.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoRequestParamsAdotante = {id: string };
type TipoResponseBodyAdotante = {
data?: Pick<AdotanteEntity, "id" | "nome" | "celular">;
}
export {
TipoRequestBodyAdotante,
TipoResponseBodyAdotante,
TipoRequestParamsAdotante
};
Voltaremos ao arquivo AdotanteController.ts
e precisaremos fazer uma modificação selecionando novamente todas as aparições de req
e res
que possuem esse primeiro objeto vazio, que é basicamente, todo o nosso código.
Portanto, selecionaremos a primeira aparição de req
e res
como parâmetro e pressionaremos "Ctrl + D" para selecionar as outras três. Feito isso, apagaremos o objeto vazio ({}
) depois do req
e substituiremos por TipoRequestParamsAdotante
.
Arquivo
AdotanteController.ts
com o exemplo da funçãocriaAdotante()
após a alteração. Todas as demais funções comreq
eres
terão os mesmos parâmetros.
async criaAdotante(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response<TipoResponseBodyAdotante>
)
Agora o problema do nosso id
foi resolvido, mas temos outro problema na função atualizaAdotante()
. No caso de não ter sucesso, ela retorna uma mensagem (message
), mas não temos esse tipo message
dentro do nosso JSON.
Retornando ao tiposAdotante.ts
, nosso objeto de retorno tem apenas um data
, para retornar os nossos dados. No caso da atualizaAdotante()
, queremos retornar um erro. Para isso, adicionaremos um novo campo. Então, ao final da linha onde declaramos o data
, pressionaremos "Enter" e, abaixo, vamos declarar o error
que também será opcional (?
).
Poderíamos declarar o erro sendo do tipo any
, porque não sabemos qual que é esse erro. Porém, ao usar o any
perdemos a validação. Existe um outro tipo que é o unknown (desconhecido). Usamos esse tipo quando não conhecemos o erro, mas queremos de alguma forma validá-lo. Deixaremos uma atividade que mostra a diferença do unknown
para o any
, mas, por enquanto, deixaremos o erro como unknown
.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoRequestParamsAdotante = {id: string };
type TipoResponseBodyAdotante = {
data?: Pick<AdotanteEntity, "id" | "nome" | "celular">;
error?: unknown;
}
export {
TipoRequestBodyAdotante,
TipoResponseBodyAdotante,
TipoRequestParamsAdotante
};
Retornando ao nosso AdotanteController.ts
, onde tínhamos o .json({message})
, substituiremos o conteúdo do objeto por error : message
. Ou seja, queremos essa mensagem que estamos recebendo como conteúdo do erro.
//código omitido
async atualizaAdotante(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response<TipoResponseBodyAdotante>
) {
const { id } = req.params;
const { success, message } = await this.repository.atualizaAdotante(
Number(id),
req.body as AdotanteEntity
);
if (!success) {
return res.status(404).json({ error: { error: message } });
}
return res.sendStatus(204);
}
//código omitido
Copiaremos a linha return res.status(404).json({ error: { error: message } });
, porque esse código se repete na nossa função deletaAdotante()
e na atualizaEnderecoAdotante()
. Portanto, colaremos esse padrão em todos os pontos que tinha o .json({message})
.
Agora estamos recebendo um erro na listaAdotantes
, porque deveríamos retornar um array, mas no TipoResponseBodyAdotante
, retornamos apenas uma aparição. Para resolvermos, selecionaremos todo o código do Pick<>
e pressionaremos "Ctrl + C" para copiá-lo. Em seguida, pressionaremos "Enter" e, na linha abaixo, colaremos o código e escreveremos colchetes ([]
), para informar que também queremos que seja possível retornar um array desse tipo de AdotanteEntity
.
//código omitido
type TipoResponseBodyAdotante = {
data?:
| Pick<AdotanteEntity, "id" | "nome" | "celular">
| Pick<AdotanteEntity, "id" | "nome" | "celular">[];
error?: unknown;
};
//código omitido
Quando voltarmos para o AdotanteController.ts
, o erro persiste, porque precisamos fazer alguns ajustes. O primeiro ajuste é porque estamos buscando essa lista com o nosso TypeORM
, que traz todas as informações, e não queremos isso.
Como já entendemos, queremos retornar só o ID, o nome e o celular. Além disso, nosso .json()
espera um campo que seja do tipo data
ou error
. Então, já podemos corrigir para .json({data})
. Definiremos esse data
como sendo um map
da nossa nova listaDeAdotantes
, onde obteremos o que recebemos da lista de adotantes e passaremos apenas ID, nome e celular.
Para isso, dentro da função listaAdotantes()
, antes do return
, criaremos uma nova linha com o código const data = listaDeAdotantes.map(adotante=>{})
. Portanto, construiremos um map()
onde, para cada adotante, retornaremos um objeto com id
desse adotante, o nome
do adotante e o celular
do adotante. Esse data
que retornamos com essa função.
async listaAdotantes(
req: Request<TipoRequestParamsAdotante, {}, TipoRequestBodyAdotante>,
res: Response<TipoResponseBodyAdotante>
) {
const listaDeAdotantes = await this.repository.listaAdotantes();
const data = listaDeAdotantes.map((adotante) => {
return {
id: adotante.id,
nome: adotante.nome,
celular: adotante.celular,
};
});
return res.json({ data });
}
Corrigimos esse erro, mas na função atualizaEnderecoAdotante()
apresenta outro erro, porque não podemos converter esse TipoRequestParams
, que é o nosso req.body
, para um tipo EnderecoEntity
. Queremos que o endereco
em req.body.endereco
, seja uma entidade de endereço (EnderecoEntity
). E, ao abrirmos nosso terminal, percebemos que possuímos apenas mais um erro.
Retornando ao arquivo tiposAdotante.ts
, nem todas as nossas rotas precisam do parâmetro, ou seja, do TipoRequestParamsAdotante
, na hora de criar o adotante. Como observamos, não precisamos passar o id
, então ele também precisa ser também opcional, ou seja, id?: string
.
import AdotanteEntity from "../entities/AdotanteEntity";
type TipoRequestBodyAdotante = Omit<AdotanteEntity, "id">;
type TipoRequestParamsAdotante = {
id?: string;
};
type TipoResponseBodyAdotante = {
data?:
| Pick<AdotanteEntity, "id" | "nome" | "celular">
| Pick<AdotanteEntity, "id" | "nome" | "celular">[];
error?: unknown;
};
export {
TipoRequestBodyAdotante,
TipoRequestParamsAdotante,
TipoResponseBodyAdotante,
};
Salvando esse arquivo e abrindo o Terminal, notamos que o nosso servidor não tem mais nenhum problema, então podemos testar no Insomnia. Ao abrirmos o Insomnia, tentaremos listar os nossos adotantes. Para isso, na coluna da esquerda, acessaremos "Adotante > Lista Adotantes". Quando mandamos uma solicitação, clicando em "Send", recebemos um objeto contendo um tipo data
, que é um array contendo objetos com apenas ID, nome e celular de todos os adotantes do nosso Adopet.
{
"data": [
{
"id": 1,
"nome": "Lucas",
"celular": "99991234"
},
{
"id": 2,
"nome": "Emerson",
"celular": "99991234"
},
{
"id": 3,
"nome": "Emerson",
"celular": "27999999999"
}
]
}
Assim, conseguimos estender esse comportamento de proteger o que recebemos e o que retornamos, não apenas na hora de criar, mas para todo o nosso adotante. O que vamos fazer na sequência é replicar esse comportamento para o nosso pet, com algumas diferenças.
O curso TypeScript: desenvolvendo validações e tratando erros possui 132 minutos de vídeos, em um total de 51 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.
Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.
Emitimos certificados para atestar que você finalizou nossos cursos e formações.
Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.
Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.
Emitimos certificados para atestar que você finalizou nossos cursos e formações.
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.
Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.
Acesso completo
durante 1 ano
Estude 24h/dia
onde e quando quiser
Novos cursos
todas as semanas