Como criar uma WebApi com Node.js, Express e TypeScript

Node.js

Como criar uma WebApi com Node.js, Express e TypeScript

Luiz Duarte
Escrito por Luiz Duarte em 16/02/2023
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Hoje vou ensinar como que você pode usar Node.js com Express e TypeScript para criar uma WebAPI. Não vou entrar no detalhe aqui de algum banco de dados em específico pois já tem outros tutoriais abordando bancos de dados aqui no blog, basta conectar as duas pontas. Vamos usar a memória do processo Node.js como armazenamento temporário, nos focando mais nos aspectos web da API mesmo.

Veremos neste artigo (pule seções à vontade):

  1. Criando o projeto
  2. Programando servidor e aplicação
  3. Criando a camada de dados
  4. Roteamento e Controle

Um ponto importante é que vou usar TypeScript aqui, mas que este não é um tutorial para quem nunca programou TypeScript antes. Se for o seu caso, recomendo que tenha um primeiro contato com este tutorial aqui ou com o vídeo abaixo.

Então vamos lá!

Livro Node.js

#1 – Criando o projeto

O primeiro passo é criar uma pasta na sua máquina para guardar os arquivos do projeto. A minha eu chamei de webapi e dentro dela deve rodar o comando para inicialização de projeto Node.js, como abaixo.

O segundo passo é instalarmos as dependências que vamos precisar, divididas em dois grupos: dependências de produção e dependências de desenvolvimento. A primeira lista é essa:

A saber:

  • Express: webserver que vamos utilizar para a webapi;
  • CORS: pacote de segurança necessário para permitir comunicação futura com frontend;
  • Helmet: pacote de segurança para dar uma blindada básica na nossa webapi;
  • DotEnv: pacote de configuração para cuidar das variáveis de ambiente;
  • Morgan: pacote para logging de requisições no terminal;
  • Express Async Errors: pacote para conseguir capturar erros assíncronos;

A segunda lista é essa:

A saber:

  • Typescript: pacote para suporte à typescript no projeto;
  • TS-Node: pacote para execução de arquivos TS sem precisar de pré-transpilação;
  • @types/…: types dos pacotes que vamos usar, para reconhecimento na ferramente;

O terceiro passo é inicializarmos o TypeScript em nosso projeto, o que devemos fazer com o comando abaixo.

Isso irá criar o arquivo tsconfig.json padrão, que eu recomendo que seja personalizado como abaixo. Explico na sequência as alterações.

Algumas explicações sobre propriedades alteradas:

  • target: diz respeito a versão do JS a ser usada na transpilação;
  • rootDir: onde estão os arquivos TS;
  • outDir: onde estarão os arquivos JS após transpilação;
  • ts-node: configurações específicas do TS-Node, onde coloquei uma configuração que reduz consumo de memória;

O quarto passo é criar um arquivo .env na raiz da sua aplicação, para colocar nossas variáveis de ambiente, sendo que vamos precisar somente de uma por enquanto.

Lembre-se de colocar .env no seu .gitignore para não versioná-lo.

O quinto passo é criar dentro do seu projeto uma pasta src para guardar os fontes TS da nossa webapi. Dentro dela, crie uma pasta controllers, outra routers, outra repositories e outra models. Fora delas, crie dois arquivos TS, um server.ts e um app.ts. Sua estrutura final deve ficar como abaixo.

  • /webapi/
    • /src/
      • /controllers/
      • /models/
      • /repositories/
      • /routers/
      • app.ts
      • server.ts
    • .env
    • package.json
    • tsconfig.json

Especificamente sobre as 4 pastas e 2 arquivos que criamos por último, segue uma breve explicação da responsabilidade de cada um.

  • server.ts: módulo de inicialização do servidor web onde nossa webapi estará hospedada, módulo de infraestrutura;
  • app.ts: módulo de configuração da webapi, módulo de aplicação;
  • routers: pasta onde guardaremos os módulos de roteamento, que mapeiam os endpoints para as funções de controle;
  • controllers: pasta onde guardaremos os módulos de controle, que recebem as requisições roteadas e fazem os processamentos necessários;
  • models: pasta onde guardaremos os módulos de entidades, que contém a especificação delas;
  • repositories: pasta onde guardaremos os módulos de repositório, que contém as funções de leitura, exclusão, inserção, etc das entidades;

E por fim, vamos abrir nosso package.json e configurar os scripts que usaremos no projeto.

Assim, quando estivermos desenvolvendo, nós deixaremos a aplicação rodando com o comando “npm run dev”, enquanto que em produção, usaremos o “npm run compile” para transpilar TS para JS e depois “npm start” para subir a aplicação. Repare que estou usando Nodemon em ambiente de dev para manter o processo sempre rodando e que ele automaticamente detecta que você possui o TS-Node instalado e que os arquivos TS devem ser executados com ele.

E com isso finalizamos o setup inicial do nosso projeto de webapi.

Curso Node.js e MongoDB

#2 – Programando servidor e aplicação

Agora que você já tem o projeto minimamente configurado, vamos fazer um primeiro fluxo de teste para garantir que está tudo funcionando, além de já deixar a estrutura central da webapi pronta.

O primeiro passo é ir no seu app.ts e começar a trabalhar nele, pela importação dos módulos que vamos precisar. Nada de especial aqui, apenas os imports dos pacotes e alguns types do Express.

Com os módulos carregados, agora é hora de criar a aplicação Express e configurar os middlewares nela.

A primeira linha inicializa a aplicação Express, enquanto que as demais pluga os middlewares. O primeiro é o Morgan, com configuração minimalista de logs. O segundo é CORS, com configuração aberta para receber requisições de qualquer frontend. O terceiro é o Helmet, com a configuração padrão que nos protege de 11 vulnerabilidades comuns na web. O quarto middleware é o express.json() que é um body-parser para que nossa API possa receber dados JSON.

E os dois últimos e mais importantes middlewares neste momento são o de tratamento global onde qualquer requisição que chegar será respondida com um Hello World e o middleware de erro (último) que será ativado caso qualquer middleware anterior dispare uma exceção não tratada (expliquei melhor sobre error handling neste artigo).

Agora temos de ir no módulo server.ts e configurar nele a inicialização da nossa aplicação, como abaixo.

Aqui começamos carregando as variáveis de ambiente com o pacote dotenv. Usaremos a variável PORT para, com a app importada, inicializar ela na porta configurada, imprimindo no console quando esse processo terminar.

Experimente agora subir a sua aplicação com npm run dev e testar ela via Postman. Qualquer request que fizer para localhost:3000 terá um Hello World como resposta. Caso nunca tenha usado o Postman antes, o vídeo abaixo pode ajudar.

Se você já consegue colocar para rodar a aplicação e fazer um GET ou POST nela obtendo um Hello World como resposta, esta etapa do desenvolvimento foi concluída.

Curso FullStack

#3 – Criando a camada de dados

Agora que temos a estrutura inicial pronta podemos fazer a funcionalidade que quisermos na API seguindo a seguinte lógica: a requisição chega no app.ts e é roteada por algum router. O roteamento da requisição a leva até um controller que fará seu processamento. Caso ele precise de dados, usará repository da entidade que é o módulo responsável pelo acesso a dados, especificados pelo model. A imagem abaixo exemplifica esse fluxo, que é bem popular em webapis.

O primeiro passo é irmos na pasta models e dentro dela criarmos um model de exemplo. Usarei aqui um pseudo-cliente que você pode modificar à vontade. Ele terá apenas 3 campos, um id, um nome e seu CPF, como abaixo.

A especificação dos campos e a implementação do constructor dispensam explicações, mas o esquema que usei para gerar ids auto incrementais vale uma explicação. A ideia aqui é simular o comportamento dos bancos de dados que geralmente criam um id auto incremental a cada novo registro inserido. Como não usaremos banco de dados neste tutorial, usei uma variável static como gerador global de ids dos clientes, assim a cada novo “new Customer” que você chamar, o id será diferente.

Agora vamos criar o repository de customers, responsável pela gestão dos dados. Em uma situação real aqui você teria algum ORM como Sequelize por exemplo, mas como vamos simular essa parte dos dados, vou resolver tudo como sendo um array em memória. Para que fique mais próximo da experiência que terá com banco de dados real, que é sempre async, vou usar promises o que é algo completamente desnecessário nesse cenário simplificado, mas que vai te ajudar mais tarde quando plugar o banco. Assim, todas as funções retornam uma Promise, geralmente de Customer (para leituras) ou boolean (para escritas).

Abaixo, o início do CRUD de customers em nosso customerRepository.ts, explicado logo adiante.

As duas primeiras funções são nossas funções de leitura/retorno dos dados (o R do CRUD). A primeira retorna um customer por id e a outra retorna todos. Em um cenário real, essa função que retorna todos não existiria e no lugar ela esperaria a quantidade ou página a ser retornada, já que facilmente os registros são na casa dos milhares ou milhões em bancos reais e retornar todos não seria nada performático e prático. Atenção especial ao uso que faço nesse módulo para algumas High Order Functions já que estamos lidando com um array. Se quiser aprender mais sobre o assunto, o vídeo abaixo pode ajudar.

Agora vamos para as próximas funções, de escrita, explicadas na sequência.

A função de adição é bem simples e apenas adicionei uma validação porque em um banco real isso poderia acontecer e também para ter um pouco mais de diversão codificando. Também me certifiquei de inicializar um objeto do tipo Customer, a fim de que a nossa geração automática de id funcione.

Na sequência, a função de atualização de cliente, onde eu verifico se o mesmo existe e, se ele existir, altero somente os campos que mudaram. Como estamos usando apenas objetos em memória, repare que procurei acessar a posição sempre por índice, para termos certeza que estamos alterando o objeto original.

E por fim, deixei como exemplo uma função de exclusão de cliente sem nada muito de diferente em relação ao que fizemos antes.

Com a camada de dados pronta, agora é hora de fazermos a camada de roteamento e controle. O ideal é que você teste bem esta camada antes de partir para a próxima, mas para que esse tutorial não fique extenso demais, recomendo que estude a parte de testes com esta guia de estudos aqui.

#4 – Roteamento e Controle

Agora o que temos de fazer é criar o customerRouter.ts e o customerController.ts. A responsabilidade do primeiro é tratar os endpoints, roteando eles para as funções corretas no controller. Já o controller deve receber a request e devolver a response, conforme o processamento necessário, o que geralmente envolve o uso de algum repository.

Vou começar pelo customerController.ts, abaixo, importando os módulos e types que usaremos e definindo as duas funções de leitura, explicadas mais adiante.

No controller basicamente entra o tratamento das requests, pegando por exemplo os parâmetros de URL e o body (mais à frente). Esses inputs são usados para fazer as chamadas corretas ao repository e o retorno é devolvido como corpo da response HTTP (JSON) ou como status HTTP, dependendo do caso. As próximas funções não são muito diferentes.

Resolvi nomear as funções de acordo com o verbo HTTP usado para chamar cada uma, mas isso não é obrigatório, qualquer nome vai funcionar. Nas funções de post e patch (adição e atualização, respectivamente), eu pego o body que veio na request (tipificado como customer) e passo ele para o repository. Opcionalmente é uma boa prática você validar esses bodys antes de passar para a próxima camada, mas não incluí neste tutorial para ele não ficar muito extenso. Se quiser se aprofundar neste tema recomendo este tutorial de validação c0m Joi.

A única função mais diferente é a de delete que somente pega o parâmetro que virá na URL, mas nada demais. Atenção aqui aos Status HTTP usados, que são os mais adequados para cada situação, a saber:

  • 200: sucesso genérico (default);
  • 201: sucesso em criação/adição;
  • 204: sucesso sem retorno (geralmente exclusão);
  • 404: não encontrado;

Agora que temos o controller finalizado, é hora de criar o customerRouter.ts para levar as requisições até as funções apropriadas, o que chamamos de roteamento.

Repare que o router é bem mais simples que os demais, é praticamente um mapeamento de cada endpoint com cada verbo HTTP para cada uma das funções do controller. Repare também o uso correto dos verbos HTTP para cada situação, a saber:

  • GET: retorno de dados;
  • POST: salvamento de nova entidade;
  • PATCH: atualização parcial de dados;
  • DELETE: exclusão de entidade;

Agora o ajuste final é incluir este router no app.ts, para que as requisições a /customers/ sejam enviadas pro router que acabamos de criar. Repare que a URL final de cada endpoint é a soma do path que está no app.ts + o path que está no router.

E com isso finalizamos o desenvolvimento da nossa webapi com Node.js, TypeScript e Express. Pode testá-la usando o postman agora e todas as rotas do CRUD devem estar funcionando. O próximo passo que recomendo que você faça agora é a adição de algum banco de dados como MongoDB, MySQL, PostgreSQL, MS SQL Server ou SQLite.

Até a próxima!

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

8 Replies to “Como criar uma WebApi com Node.js, Express e TypeScript”

Rodrigo

Colocar versão do node seria bem mais útil!

Luiz Duarte

Não entendi seu comentário.

Giovanny

Como poderia fazer as integrações TypeScript Sequelize? Estou meio confuso, vi seu artigo integrando NodeJS com Sequelize mas seria muito legal ver sua abordagem de como fazer com TypeScript.

Luiz Duarte

Sequelize com TypeScript é ensinado no curso Web Full Stack JS: https://www.luiztools.com.br/curso-fullstack

Dimas Souza

Muito bom seu conteúdo!! Parabéns.

Luiz Duarte

Fico feliz que tenha gostado Dimas!

Flávia

Muito bom! Só que ao rodar npm run dev, não consegue achar o modulo customerController:

> npx nodemon src/server.ts –watch ‘src/’ -e ts

[nodemon] 3.0.2
[nodemon] to restart at any time, enter rs
[nodemon] watching path(s): ‘src\’
[nodemon] watching extensions: ts
[nodemon] starting ts-node src/server.ts
Error: Cannot find module ‘src/controllers/customerController’
Require stack:
– \src\routers\customerRouter.ts
– \src\app.ts
– \src\server.ts
at Function.Module._resolveFilename (node:internal/modules/cjs/loader:1039:15)
at Function.Module._resolveFilename.sharedData.moduleResolveFilenameHook.installedValue [as _resolveFilename] (\node_modules\@cspotcode\source-map-support\source-map-support.js:811:30)
at Function.Module._load (node:internal/modules/cjs/loader:885:27)
at Module.require (node:internal/modules/cjs/loader:1105:19)
at require (node:internal/modules/cjs/helpers:103:18)
at Object. (\src\routers\customerRouter.ts:2:1)
at Module._compile (node:internal/modules/cjs/loader:1218:14)
at Module.m._compile (\node_modules\ts-node\src\index.ts:1618:23)
at Module._extensions..js (node:internal/modules/cjs/loader:1272:10)
at Object.require.extensions. [as .ts] (\node_modules\ts-node\src\index.ts:1621:12) {
code: ‘MODULE_NOT_FOUND’,
requireStack: [
‘\\src\\routers\\customerRouter.ts’,
‘\\src\\app.ts’,
‘\\src\\server.ts’
]
}
[nodemon] app crashed – waiting for file changes before starting…

Luiz Duarte

Arrume o caminho, não use src no início dos imports, comece sempre com ponto ou com barra até chegar no arquivo.