Node js Logo Sticker

Introdução

Se você deseja elevar a qualidade do seu código JavaScript, utilizando uma linguagem tipada, Typescript é o caminho certo. Neste post, você aprenderá como configurar um projeto de um servidor de API Node.js do zero utilizando Typescript, abordando desde a instalação até as boas práticas que farão seu código mais robusto e escalável.

1. Por Que Usar Typescript com Node.js?

Typescript oferece tipagem estática, o que ajuda a prevenir erros comuns que ocorrem durante o tempo de execução em JavaScript. Além disso, ele melhora a experiência de desenvolvimento com recursos como autocompletar, verificação de tipos e refatoração mais segura. Isso é especialmente útil em projetos Node.js, onde a manutenção de um código limpo e sustentável é essencial.

2. Instalando Node.js e NPM

Antes de começar, você precisa garantir que tem o Node.js e o NPM instalados em sua máquina. Siga os passos abaixo:

Verifique se o Node está instalado em seu computador:

node -v

Instale o Node.js, se necessário:

Download Node.js

3. Inicializando o Projeto Node.js

Crie uma nova pasta para o seu projeto e inicialize um novo projeto Node.js:

mkdir api-node-ts
cd api-node-ts
npm init -y

O comando npm init -y cria um arquivo package.json com a configuração padrão.

4. Configurando o Typescript

Agora, vamos instalar o Typescript e algumas dependências essenciais:

npm install typescript ts-node @types/node --save-dev

Após a instalação, crie um arquivo de configuração do Typescript:

npx tsc --init

Esse comando gera um arquivo tsconfig.json na raiz do projeto, onde você pode configurar opções como a versão do ECMAScript e o diretório de saída.

Altere o conteúdo do arquivo tsconfig.json com o seguinte conteúdo:

{
    "compilerOptions": {
        "target": "es6",
        "module": "commonjs",
        "outDir": "./dist",
        "rootDir": "./src",
        "strict": true,
        "moduleResolution": "node",
        "esModuleInterop": true,
        "resolveJsonModule": true,
        "allowSyntheticDefaultImports": true
    },
    "include": [
        "src/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

A Seção compilerOptions contém as informações necessárias para realizar a compilação, dentro dela, temos as seguintes configurações:

  • target: Indica para qual versão do ECMAScript queremos que o código seja transpilado, ou seja, por mais que o código produzido seja Typescript, durante o processo de transpilação, o que realmente será publicado se trata de um código Javascript, nesse caso, estamos indicando que usaremos o es6 (ECMAScript 6).
  • module: Indica qual sistema de módulos será utilizado no projeto, basicamente isso afeta como os arquivos podem ser exportados e importados.
  • outDir: Indica em qual pasta queremos que o código transpilado (.js) seja colocado.
  • rootDir: Indica qual o diretório principal que contém a estrutura de pastas que será copiada dentro do diretório informado em outDir.
  • strict: Habilita um conjunto de verificações automáticas no código, que fornece mais segurança.
  • moduleResolution: Em conjunto com o parâmetro module, indica qual o algoritmo utilizado para a resolução dos módulos.
  • esModuleInterop: Permite importar módulos CommonJS de forma compatível com es6.
  • resolveJsonModule: Permite importar arquivos .json da mesma maneira que se importam módulos, sem a necessidade de utilizar fetch.
  • allowSyntheticDefaultImports: Permite a importação de módulos que não possuem export default

A Seção include indica quais diretórios e arquivos serão incluídos na transpilação.

Já a Seção exclude indica quais diretórios e arquivos devem ser excluídos da transpilação.

5. Criando a Estrutura do Projeto

Organize a estrutura de pastas e arquivos do seu projeto para facilitar a manutenção e a escalabilidade, mais adiante vamos nos aprofundar mais sobre os benefícios desta estratégia:

api-node-ts/
│
├── src/
│   └── app/
│   │   └── controllers/
│   │   │   └── teste.controller.ts
│   └── app.ts
│   └── routes.ts
│   └── server.ts
├── dist/
├── .env
├── .gitignore
├── package.json
└── tsconfig.json

6. Preparando o Servidor com Express

Express é um framework minimalista que facilita a criação de servidores HTTP. Vamos configurá-lo em Typescript.

Instale o Express e seus tipos:

npm install express
npm install @types/express --save-dev

7. Configurando Hot Reload

Na maior parte do tempo em que estiver desenvolvendo um projeto de API em node, é interessante que o servidor atualize automaticamente a cada vez que o código é alterado, caso contrário, seria necessário reiniciar o servidor a cada vez que quisesse ver as mudanças implementadas. Esse comportamento é chamado de Hot Reload e para que ele funcione é necessário instalar a seguinte dependência:

npm install ts-node-dev --save-dev

8. Configurando Variáveis de Ambiente Durante o Desenvolvimento

É uma boa prática separar as variáveis que dependem do ambiente em que a aplicação está sendo executada. Exemplo, imagina que você precisa configurar a porta onde sua aplicação vai estar disponível, é provável que esse valor seja diferente quando você estiver testando a aplicação no seu computador e quando a aplicação estiver rodando no ambiente de produção. Para esses casos podemos utilizar um arquivo chamado .env, onde, as variáveis utilizadas durante o desenvolvimento ficarão armazenadas, sendo assim, quando a aplicação for para o ambiente de produção, essa variável pode ser trocada sem a necessidade de criar condicionais no código.

Para dar esse suporte, é necessário instalar o pacote apropriado:

npm install dotenv

9. Dando Suporte a CORS (Cross Origin Resource Sharing)

Um problema muito conhecido e que todo mundo já sofreu com ele pelo menos uma vez é tentar acessar uma API e receber um erro de CORS, que significa que sua aplicação não permite o acesso de seus recursos por um outro domínio, porta ou protocolo.

Imagina uma aplicação frontend tentando acessar essa API, obviamente o frontend estará disponível em um domínio ou porta diferente, por esse motivo é preciso informar o node que queremos permitir esse tipo de acesso. Para isso, precisamos realizar o importe da biblioteca CORS:

npm install cors

10. Automatizando o Processo com npm Scripts

Para facilitar o desenvolvimento, podemos inserir dentro do arquivo packages.json alguns comandos que simplificam a execução e testes no código.

Abra o packages.json e localize a seção chamada scripts, caso não exista, basta adicionar, em seguida inclua os comandos a seguir:

"start": "ts-node-dev --respawn ./src/server.ts",
"build": "tsc -p ."

Com a seção esse trecho completo deve ficar assim:

  "scripts": {
    "start": "ts-node-dev --respawn ./src/server.ts",
    "build": "tsc -p ."
  },

Desse modo, quando quiser inicializar ser servidor, ao invés de escrever o comando completo, podemos digitar no terminal apenas o nome do script:

npm start

Para comandos que diferem do start, como por exemplo o build, precisamos usar a flag run, exemplo:

npm run build

O script build realiza apenas a transpilação da aplicação, mas não inicializa o servidor, isso é útil quando o desenvolvimento já foi concluído e queremos publicar a aplicação no ambiente de produção.

11. Preparando o Projeto Para Armazenamento em Repositório

Caso deseje salvar seu projeto em algum repositório de código como GitHub, GitLab, Azure Devops, etc, existe um passo muito importante que se não for feito agora pode trazer um pouco de dor de cabeça mais tarde, que é configurar o arquivo .gitignore.

No passo 5 foi mostrada a estrutura do projeto. Se você prestou atenção, foi colocado um arquivo chamado .gitignore, este arquivo serve para informar que existem arquivos que não queremos versionar no repositório, por exemplo, arquivos que são gerados automaticamente, no nosso caso estamos falando da pasta node_modules (que é gerada automaticamente quando instalamos algum pacote npm) ou ainda dos arquivos gerados pela transpilação na pasta dist.

Não faz sentido guardar esses arquivos pois a aplicação sempre irá gerá-los conforme o projeto for ampliado. Para evitar isso, dentro desse arquivo, coloque o seguinte conteúdo:

logs
*.log
npm-debug.log*
node_modules/
jspm_packages/
typings/
.npm
.eslintcache
.env
.env.test
.cache
dist

11. Escrevendo um Pouco de Código

Agora que tudo já está preparado, vamos escrever o código em si, lembrando que este projeto é um padrão para criação de APIs, sendo assim, você pode começar com ele e adaptar conforme as necessidades do seu projeto.

Arquivo teste.controller.ts

Este arquivo vai expor um endpoint de exemplo, você pode adicionar quantas controllers e quantos endpoints por controller você quiser. Mais adiante vamos mostrar como mapear as rotas para que cada controller atenda uma URI específica.

Começe adicionando o seguinte conteúdo no arquivo src/app/controllers/teste.controller.ts:

class TesteController {
    async get(req: any, res: any) {
        res.send('API Teste OK!')
    }
}

export default new TesteController();

Primeiro criamos a classe e em seguida criamos um endpoint chamado get, os parâmetros req e res são padrão, no req você consegue obter os parâmetros de entrada enquanto no res é onde iremos colocar a resposta da nossa API.

Repare que utilizamos o async para sinalizar que se trata de um método assíncrono e logo em seguida estamos enviando como retorno apenas uma mensagem: API Teste OK.

Por fim exportamos uma instância dessa controller como default para que possa ser importada por outro arquivo mais adiante.

Arquivo routes.ts

Agora que criamos a controller, precisamos informar o express que queremos mapear uma URI para o método get da controller, para isso, coloque o seguinte conteúdo no arquivo src/routes.ts:

import { Router } from 'express';
import testeController from './app/controllers/teste.controller';

const routes = Router();

routes.get('/', testeController.get);

export default routes;

Primeiro importamos o Router do express, em seguida importamos nossa controller recém criada, criamos uma instância do Router para armazenar todos os nossos endpoints, em seguida, criamos um método do tipo HTTPGET através do método routes.get e informamos que, quando o usuário acessar a URI /, queremos que a chamada seja processada pelo método get que criamos na nossa controller.

Por fim exportamos nosso objeto routes.

Arquivo app.ts

Precisamos juntar todas as peças que construímos até agora, para isso vamos usar o seguinte código no arquivo src/app.ts:

import express from 'express';
import cors from 'cors';

import routes from './routes';

export class App{
    public server;
    
    constructor() {
        this.server = express();
        this.middleware();
        this.routing();
    }

    private middleware() {
        this.server.use(express.json({limit: '1mb'}));
        this.server.use(express.urlencoded({ limit: '1mb' }));
        this.server.use(express.json());
        this.server.use((req, res, next) => {
            res.header("Access-Control-Allow-Origin", "*");            
            res.header("Access-Control-Allow-Methods", 'GET,PUT,POST,DELETE');  
            this.server.use(cors());
            next();
        });
    }

    private routing() {
        this.server.use(routes);
    }
}

Aqui importamos o express e o cors, assim como nosso arquivo routes.

Criamos a classe App e no seu construtor realizamos algumas configurações. Primeiro instanciamos o servidor do express e configuramos o limite de payload que a aplicação vai aceitar para 1 mb. Por padrão, o express limita tanto os payloads em json quanto url encoded em 100kb, mas as vezes esse valor não é o suficiente, neste projeto, essa configuração não é obrigatória, mas foi colocado no exemplo por se tratar de um problema bem comum.

Realizamos também algumas configurações no header para informar quais métodos HTTP queremos aceitar em nossa aplicação e quais as origens permitidas, repare que habilitamos o cors aqui também.

Centralizamos todas as configurações de request e response em um middleware, para deixar o código mais organizado, por padrão, todo o middleware precisa chamar o método next() ao final de sua execução.

Por último fazemos uso das nossas rotas, informando ao express que queremos usar nosso objeto routes.

Arquivo server.ts

Este arquivo é responsável por inicializar nosso servidor, coloque o seguinte conteúdo no arquivo src/server.ts:

import { App } from './app';

import * as http from 'http';
import dotenv from 'dotenv';

dotenv.config();

const PORT = process.env.PORT;

http.createServer(new App().server).listen(PORT, async () => {
  console.log(`==== Listening on port ${PORT}\n`);
});

Aqui nós utilizamos o módulo http que já vem por padrão no node para configurarmos tanto o protocolo (http) quanto a porta onde nossa aplicação vai aguardar pelas chamadas.

Repare que aqui estamos fazendo uso do pacote dotenv, mas não estamos chamando explicitamente o arquivo .env, pois isso é feito de forma automática.

Arquivo .env

Por último, abra o arquivo .env e coloque o seguinte conteúdo:

PORT=5005

Com isso, informamos para o dotenv que, ao rodar a aplicação, queremos que ele suba o servidor na porta 5005.

12. Compilando e Executando o Projeto

Para compilar o projeto, abra o terminal e digite:

npm run build

Perceba que foi adicionado no diretório dist os arquivos transpilados em .js:

Para rodar a aplicação, basta usar o comando:

npm start

Em seguida abra seu navegador digitando localhost:5005:

13. Download

Você pode baixar este projeto direto pelo nosso Github.

Conclusão

Você aprendeu como criar um projeto Node.js utilizando Typescript, configurando desde o ambiente até a estrutura do projeto, criação de um servidor com Express, e boas práticas. Com Typescript, você pode escrever um código mais seguro e fácil de manter, aproveitando o melhor de dois mundos: a flexibilidade do JavaScript e a robustez da tipagem estática.

Gostou do conteúdo? Deixe seu comentário e compartilhe suas experiências com Typescript e Express!

Tags: | | |

0 Comentários

Deixe um comentário

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