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 142 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:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.