Leodario.com

Leodario.com – Tudo sobre Tecnologia

Aula 20 – API JavaScript, Node.js e Express – CommonJS vs ES Modules – Sistemas de módulos

Imagem destacada da aula de API

Introdução

Saudações, futuros mestres do desenvolvimento de APIs! Sou seu professor PHD e especialista mundial em APIs, e hoje vamos mergulhar em um tópico que é absolutamente central para a construção de qualquer aplicação Node.js moderna e escalável: os Sistemas de Módulos. Esta é a Aula 20, e o nível é básico, então prepare-se para aprender de forma prática e descomplicada.

Imagine o seguinte cenário no mundo real: você está construindo um grande prédio. Seria caótico tentar erguer a estrutura inteira com um único tipo de tijolo, sem divisões, sem a capacidade de encomendar peças pré-fabricadas, como janelas, portas ou sistemas elétricos. Cada especialista (encanador, eletricista, vidraceiro) precisa trabalhar em sua própria seção, mas com a capacidade de integrar seu trabalho ao resto da construção de forma harmoniosa.

No desenvolvimento de APIs e aplicações Node.js, os sistemas de módulos servem exatamente a esse propósito. Eles nos possibilitam organizar nosso código em arquivos separados e reutilizáveis, como se fossem as diferentes peças e especialistas do nosso prédio. Em vez de um único arquivo monolítico, temos módulos que cuidam de tarefas específicas (como lidar com rotas, conectar a um banco de dados, ou processar dados de usuários), tornando o código mais legível, manutenível e fácil de testar. Isso é vital para APIs modernas, pois elas precisam ser ágeis e robustas.

Nesta aula, você vai desenvolver e entender a diferença fundamental entre dois paradigmas de módulos no ecossistema Node.js/Express: o CommonJS (o modelo tradicional) e os ES Modules (o padrão mais recente do JavaScript). Você aprenderá a configurá-los, a importar e exportar funcionalidades, e a perceber o contexto em que cada um é mais apropriado. Isso é essencial para quem busca construir APIs eficazes e aderentes às melhores práticas do mercado.

Conceito Fundamental

Para compreendermos a fundo os sistemas de módulos, precisamos entender que eles são o mecanismo pelo qual o Node.js permite que você divida seu código em arquivos discretos. Cada arquivo pode ser considerado um “módulo” que encapsula funcionalidades específicas. Em vez de ter um código gigantesco e incontrolável em um único arquivo, você pode ter um módulo para as rotas da sua API, outro para a lógica de negócio, outro para a configuração do banco de dados, e assim por diante.

Existem, essencialmente, dois grandes “dialetos” ou terminologias corretas da indústria quando falamos de módulos no Node.js:

    • CommonJS: Este é o sistema de módulos original e padrão do Node.js. Ele foi criado especificamente para ambientes de servidor para suprir a falta de um sistema de módulos nativo no JavaScript antes da padronização de ES Modules. No CommonJS, você usa require() para importar módulos e module.exports ou exports para exportar funcionalidades. É um modelo síncrono, o que significa que, ao requisitar um módulo, o Node.js pausa a execução do script até que o módulo seja totalmente carregado.
      • Vantagens: Amplamente adotado em bases de código Node.js existentes, bem suportado por muitas ferramentas e bibliotecas mais antigas, e o carregamento síncrono pode ser mais simples de entender para alguns casos de uso.
      • Desvantagens: Carregamento síncrono pode impactar a performance em cenários complexos, sintaxe diferente do padrão moderno do JavaScript (ES Modules).
    • ES Modules (ESM): Também conhecidos como módulos JavaScript ou módulos ECMAScript, são o padrão oficial de módulos para JavaScript, tanto no navegador quanto no Node.js. Eles foram introduzidos no ES2015 (ES6). No ESM, você usa import para importar módulos e export para exportar funcionalidades. Ao contrário do CommonJS, o carregamento de módulos ESM é assíncrono por natureza, o que pode viabilizar otimizações e melhor desempenho. O Node.js começou a dar suporte total a ES Modules a partir da versão 12.
      • Vantagens: É o padrão oficial do JavaScript, o que facilita a transição entre ambientes (frontend/backend), suporta carregamento assíncrono e árvores de dependência estáticas (útil para otimizações como tree-shaking), e possui uma sintaxe mais moderna e limpa.
      • Desvantagens: Requer configuração explícita no Node.js (via "type": "module" no package.json ou usando a extensão .mjs), e algumas bibliotecas mais antigas podem não ter suporte nativo a ESM, exigindo adaptações.

Em casos de uso reais em produção, você verá CommonJS predominantemente em projetos Node.js mais antigos ou legados. Já os ES Modules são a escolha preferida para novos projetos, especialmente aqueles que visam alinhar o backend com as práticas do frontend, ou que se beneficiam das otimizações de performance. A forma como isso se integra com outras tecnologias é direta: bibliotecas e frameworks como Express.js são projetados para funcionar perfeitamente com ambos os sistemas de módulos. A escolha impacta principalmente a sintaxe de como você organiza e relaciona seus arquivos de código.

Implementação Prática

Agora, vamos colocar as mãos na massa e ver como esses sistemas de módulos funcionam na prática. Vamos criar uma pequena aplicação Express.js para demonstrar a exportação e importação de rotas e funcionalidades com CommonJS e ES Modules.

Preparação do Ambiente

Primeiro, crie uma nova pasta para o seu projeto e inicialize um projeto Node.js. No seu terminal, execute:

mkdir modulos-aula20
cd modulos-aula20
npm init -y
npm install express --save

Isso criará um arquivo package.json e instalará o Express.js.

Exemplo 1: CommonJS (Padrão)

Vamos criar dois arquivos: app-cjs.js (o arquivo principal) e rotas-cjs.js (um módulo de rotas).

rotas-cjs.js

// rotas-cjs.js
// Este módulo exporta funções para lidar com diferentes rotas usando CommonJS.

const express = require('express'); // Importa o módulo Express, necessário para criar roteadores. const router = express.Router(); // Cria uma nova instância de roteador Express.

// Define uma rota GET para o caminho base ('/'). router.get('/', (req, res) => { // Envia uma resposta JSON simples para a requisição. console.log('Requisição recebida na rota CommonJS principal.'); // Logging profissional para monitoramento. res.json({ mensagem: 'Olá da API em CommonJS!', rota: '/' }); });

// Define uma rota GET para '/usuarios'. router.get('/usuarios', (req, res) => { // Envia uma resposta JSON com dados simulados de usuários. console.log('Requisição recebida na rota CommonJS de usuários.'); // Logging para a rota de usuários. const usuarios = [{ id: 1, nome: 'Alice' }, { id: 2, nome: 'Bruno' }]; res.json({ mensagem: 'Lista de usuários (CommonJS)', dados: usuarios }); });

// Define uma rota POST para '/produtos'. // Esta rota simula a criação de um produto e exige um corpo JSON na requisição. router.post('/produtos', (req, res) => { const { nome, preco } = req.body; // Extrai nome e preco do corpo da requisição.

// Validação de entrada robusta: verifica se os campos essenciais estão presentes. if (!nome || !preco) { console.error('Erro: Nome ou preço ausente na criação do produto CommonJS.'); // Log de erro. // Retorna um status 400 (Bad Request) com uma mensagem de erro. return res.status(400).json({ erro: 'Nome e preço são obrigatórios para criar um produto.' }); }

// Simula a criação de um novo produto com um ID fictício. const novoProduto = { id: Date.now(), nome, preco }; console.log(Produto CommonJS criado: ${JSON.stringify(novoProduto)}); // Log de sucesso. // Retorna um status 201 (Created) e os dados do novo produto. res.status(201).json({ mensagem: 'Produto criado com sucesso (CommonJS)', produto: novoProduto }); });

// Exporta o objeto router para que ele possa ser usado em outros arquivos. // Esta é a sintaxe de exportação padrão do CommonJS. module.exports = router;

app-cjs.js

// app-cjs.js
// Arquivo principal da aplicação Express usando CommonJS.

const express = require('express'); // Importa o framework Express. const bodyParser = require('body-parser'); // Importa o body-parser para lidar com JSON no corpo das requisições. const cjsRoutes = require('./rotas-cjs'); // Importa o módulo de rotas CommonJS que acabamos de criar.

const app = express(); // Cria uma instância da aplicação Express. const PORT = process.env.PORT || 3000; // Define a porta do servidor, padrão para 3000.

// Configurações enterprise básicas: app.use(bodyParser.json()); // Habilita o Express a interpretar corpos de requisição como JSON. app.use(express.urlencoded({ extended: true })); // Habilita o Express a interpretar dados de formulário.

// Adiciona o módulo de rotas importado à aplicação, prefixado com '/api-cjs'. // Todas as rotas definidas em 'rotas-cjs.js' estarão disponíveis em /api-cjs/alguma-rota. app.use('/api-cjs', cjsRoutes);

// Rota de fallback para qualquer caminho não encontrado. app.use((req, res) => { console.warn(Rota não encontrada: ${req.method} ${req.originalUrl}); // Logging de alerta para rotas inexistentes. res.status(404).json({ erro: 'Recurso não encontrado (CommonJS).' }); });

// Inicia o servidor Express. app.listen(PORT, () => { console.log(Servidor CommonJS rodando em http://localhost:${PORT}); console.log(Teste: Acesse http://localhost:${PORT}/api-cjs ou http://localhost:${PORT}/api-cjs/usuarios); });

Exemplo 2: ES Modules (Moderno)

Para usar ES Modules, precisamos fazer uma pequena alteração no package.json ou usar a extensão .mjs.

Configuração para ES Modules

Abra seu arquivo package.json e adicione a seguinte linha no nível superior do objeto JSON (logo abaixo do "name"):

{
  "name": "modulos-aula20",
  "version": "1.0.0",
  "description": "",
  "main": "app-cjs.js",
  "type": "module", // <-- ADICIONE ESTA LINHA
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start-cjs": "node app-cjs.js",
    "start-esm": "node app-esm.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  }
}

Com "type": "module" , todos os arquivos .js na sua pasta serão tratados como ES Modules por padrão. Se você quiser que algum arquivo continue sendo CommonJS, deverá usar a extensão .cjs para ele (ex: app-cjs.cjs). Nosso app-cjs.js ainda funcionará porque o Node.js é inteligente, mas para ser explícito, você poderia renomeá-lo para app-cjs.cjs.

Vamos criar agora app-esm.js e rotas-esm.js:

rotas-esm.js

// rotas-esm.js
// Este módulo exporta funções para lidar com diferentes rotas usando ES Modules.

import express from 'express'; // Sintaxe 'import' para módulos ES. const router = express.Router(); // Cria uma nova instância de roteador Express.

// Define uma rota GET para o caminho base ('/'). router.get('/', (req, res) => { console.log('Requisição recebida na rota ES Modules principal.'); // Logging profissional. res.json({ mensagem: 'Olá da API em ES Modules!', rota: '/' }); });

// Define uma rota GET para '/produtos'. router.get('/produtos', (req, res) => { console.log('Requisição recebida na rota ES Modules de produtos.'); // Logging para a rota de produtos. const produtos = [{ id: 101, nome: 'Teclado' }, { id: 102, nome: 'Mouse' }]; res.json({ mensagem: 'Lista de produtos (ES Modules)', dados: produtos }); });

// Define uma rota PUT para '/usuarios/:id'. // Esta rota simula a atualização de um usuário. router.put('/usuarios/:id', (req, res) => { const { id } = req.params; // Extrai o ID do usuário dos parâmetros da URL. const { nome } = req.body; // Extrai o novo nome do corpo da requisição.

// Validação de entrada robusta: verifica se o nome está presente. if (!nome) { console.error(Erro: Nome ausente para atualização do usuário ${id} em ES Modules.); // Log de erro. return res.status(400).json({ erro: 'O nome é obrigatório para atualizar o usuário.' }); }

// Simula a atualização de um usuário. const usuarioAtualizado = { id: parseInt(id), nome }; // Converte o ID para número. console.log(Usuário ES Modules atualizado: ${JSON.stringify(usuarioAtualizado)}); // Log de sucesso. res.json({ mensagem: Usuário ${id} atualizado com sucesso (ES Modules), usuario: usuarioAtualizado }); });

// Exporta o objeto router. // Esta é a sintaxe de exportação padrão dos ES Modules. export default router;

app-esm.js

// app-esm.js
// Arquivo principal da aplicação Express usando ES Modules.

import express from 'express'; // Importa o Express usando sintaxe ES Modules. // Note: body-parser é frequentemente integrado ao Express 4.16+, então pode ser omitido. // Ou você pode importá-lo se preferir: import bodyParser from 'body-parser'; import esmRoutes from './rotas-esm.js'; // Importa o módulo de rotas ES Modules. // É boa prática incluir a extensão do arquivo (.js, .mjs) ao importar módulos locais em ESM.

const app = express(); // Cria uma instância da aplicação Express. const PORT = process.env.PORT || 3001; // Define uma porta diferente para o servidor ESM.

// Configurações enterprise básicas: app.use(express.json()); // Usa o middleware embutido do Express para JSON. app.use(express.urlencoded({ extended: true })); // Para dados de formulário.

// Adiciona o módulo de rotas importado à aplicação, prefixado com '/api-esm'. app.use('/api-esm', esmRoutes);

// Rota de fallback para qualquer caminho não encontrado. app.use((req, res) => { console.warn(Rota não encontrada: ${req.method} ${req.originalUrl} (ES Modules)); // Logging de alerta. res.status(404).json({ erro: 'Recurso não encontrado (ES Modules).' }); });

// Inicia o servidor Express. app.listen(PORT, () => { console.log(Servidor ES Modules rodando em http://localhost:${PORT}); console.log(Teste: Acesse http://localhost:${PORT}/api-esm ou http://localhost:${PORT}/api-esm/produtos); });

Configurações Específicas para HostGator Plano M

Prezados alunos, é relevante destacar um ponto crucial aqui. O HostGator Plano M é um ambiente de hospedagem compartilhada, otimizado primariamente para PHP e Apache. Rodar Node.js diretamente em um ambiente como este pode ser um desafio significativo e não é o cenário ideal para aplicações Node.js em produção.

Os sistemas de módulos (CommonJS e ES Modules) são características do runtime do Node.js, não dependem diretamente da configuração do servidor web (Apache, Nginx). Se o seu HostGator Plano M permitisse a execução de Node.js (o que geralmente requer um plano VPS ou dedicado, ou uma configuração manual complexa como o uso de Passenger ou um proxy .htaccess para uma porta específica), a forma como você importa e exporta módulos seria a mesma demonstrada acima, pois isso é uma funcionalidade interna do Node.js.

Para um ambiente de produção enterprise com Node.js, a recomendação é utilizar um Servidor Privado Virtual (VPS), plataformas como AWS, Azure, Google Cloud, ou serviços especializados em hospedagem Node.js como Heroku, Vercel ou Railway. Nestes ambientes, você teria controle total sobre a versão do Node.js, as configurações de sistema e a execução da sua aplicação, tornando a implementação de CommonJS ou ES Modules transparente.

Em suma, a escolha entre CommonJS e ES Modules se define no seu package.json ("type": "module") ou nas extensões dos arquivos (.js vs .mjs ou .cjs), e o Node.js interpretará o código corretamente, independentemente do host, desde que ele suporte o ambiente Node.js.

Testes Básicos

Para testar nossas aplicações, adicione scripts ao seu package.json:

{
  "name": "modulos-aula20",
  "version": "1.0.0",
  "description": "",
  "main": "app-cjs.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start-cjs": "node app-cjs.js", // Script para iniciar a aplicação CommonJS
    "start-esm": "node app-esm.js"  // Script para iniciar a aplicação ES Modules
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2",
    "body-parser": "^1.20.2" // Adicione body-parser se não estiver já
  }
}

Agora, no seu terminal:

# Para iniciar o servidor CommonJS
npm run start-cjs

Abra outro terminal para testar, ou use um cliente REST (Postman, Insomnia, curl)

📚 Informações da Aula

Curso: API Completo - Node.js & Express

Tempo estimado: 25 minutos

Pré-requisitos: JavaScript básico

Teste GET:

curl http://localhost:3000/api-cjs curl http://localhost:3000/api-cjs/usuarios

Teste POST (CommonJS):

curl -X POST -H "Content-Type: application/json" -d '{"nome":"Laptop Gamer","preco":1500}' http://localhost:3000/api-cjs/produtos

Para iniciar o servidor ES Modules (em outra porta, então pode rodar em paralelo)

npm run start-esm

Teste GET:

curl http://localhost:3001/api-esm curl http://localhost:3001/api-esm/produtos

Teste PUT (ES Modules):

curl -X PUT -H "Content-Type: application/json" -d '{"nome":"Carlos Silva"}' http://localhost:3001/api-esm/usuarios/3

Observe os logs no console para ver o funcionamento do logging profissional que implementamos e as mensagens de sucesso ou erro.

Exercício Hands-On

Excelente! Agora é sua vez de solidificar o conhecimento.

Desafio Prático

Seu desafio é o seguinte: crie um novo módulo chamado utilidades.js (ou utilidades.mjs para ES Modules e utilidades.cjs para CommonJS) que contenha duas funções simples:

    • Uma função que retorne a data e hora atual formatada.
    • Uma função que capitalize (torne a primeira letra maiúscula) uma string.

Em seguida, integre este módulo em ambas as suas aplicações (CommonJS e ES Modules) e use as funções em uma nova rota. Por exemplo, uma rota /data que exiba a data formatada, e uma rota /saudacao/:nome que capitalize o nome recebido e retorne uma saudação.

Solução Detalhada Passo a Passo

1. Crie o arquivo utilidades.js (para uso em ambos os contextos, usaremos a sintaxe de ES Modules e importaremos via import em app-esm.js e via dynamic import ou transpilação em app-cjs.js para simplificar, ou criaremos duas versões, uma .cjs e uma .mjs para ser mais claro para iniciantes). Para manter a clareza e o nível básico, faremos uma versão .cjs e uma .mjs.

utilidades.cjs (Para CommonJS)
// utilidades.cjs
// Módulo de utilidades usando CommonJS.

function obterDataHoraAtual() { const data = new Date(); // Formato de data e hora legível. return data.toLocaleString('pt-BR', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); }

function capitalizarString(str) { if (!str || typeof str !== 'string') { return ''; // Trata entradas inválidas. } return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); }

// Exporta as funções usando CommonJS. module.exports = { obterDataHoraAtual, capitalizarString };

utilidades.mjs (Para ES Modules)
// utilidades.mjs
// Módulo de utilidades usando ES Modules.

export function obterDataHoraAtual() { const data = new Date(); return data.toLocaleString('pt-BR', { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); }

export function capitalizarString(str) { if (!str || typeof str !== 'string') { return ''; } return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase(); }

// Em ES Modules, as exports são diretas na declaração da função, // ou exportadas como um objeto no final (export { obterDataHoraAtual, capitalizarString };) // No caso acima, usamos exportações nomeadas.

2. Integre em app-cjs.js e rotas-cjs.js

Atualize rotas-cjs.js para importar e usar as utilidades:

rotas-cjs.js (atualizado)
// rotas-cjs.js (Atualizado com utilidades)

const express = require('express'); const router = express.Router(); const { obterDataHoraAtual, capitalizarString } = require('./utilidades.cjs'); // Importa as utilidades CJS.

// ... (rotas existentes) ...

// Nova rota para data router.get('/data', (req, res) => { const dataHora = obterDataHoraAtual(); console.log(Requisição para data em CommonJS. Data: ${dataHora}); res.json({ mensagem: 'Data e Hora Atual (CommonJS)', dataHora }); });

// Nova rota para saudação com capitalização router.get('/saudacao/:nome', (req, res) => { const nomeOriginal = req.params.nome; const nomeCapitalizado = capitalizarString(nomeOriginal); console.log(Requisição para saudação em CommonJS. Nome original: ${nomeOriginal}, Capitalizado: ${nomeCapitalizado}); res.json({ mensagem: Olá, ${nomeCapitalizado}! Seja bem-vindo(a) (CommonJS). }); });

module.exports = router;

3. Integre em app-esm.js e rotas-esm.js

Atualize rotas-esm.js para importar e usar as utilidades:

rotas-esm.js (atualizado)
// rotas-esm.js (Atualizado com utilidades)

import express from 'express'; import { obterDataHoraAtual, capitalizarString } from './utilidades.mjs'; // Importa as utilidades ESM. const router = express.Router();

// ... (rotas existentes) ...

// Nova rota para data router.get('/data', (req, res) => { const dataHora = obterDataHoraAtual(); console.log(Requisição para data em ES Modules. Data: ${dataHora}); res.json({ mensagem: 'Data e Hora Atual (ES Modules)', dataHora }); });

// Nova rota para saudação com capitalização router.get('/saudacao/:nome', (req, res) => { const nomeOriginal = req.params.nome; const nomeCapitalizado = capitalizarString(nomeOriginal); console.log(Requisição para saudação em ES Modules. Nome original: ${nomeOriginal}, Capitalizado: ${nomeCapitalizado}); res.json({ mensagem: Olá, ${nomeCapitalizado}! Seja bem-vindo(a) (ES Modules). }); });

export default router;

Como Testar e Validar o Resultado

1. Inicie o servidor CommonJS:

npm run start-cjs

Testes:

    • curl http://localhost:3000/api-cjs/data
    • curl http://localhost:3000/api-cjs/saudacao/joao
    • curl http://localhost:3000/api-cjs/saudacao/maria

2. Inicie o servidor ES Modules (em outro terminal):

npm run start-esm

Testes:

    • curl http://localhost:3001/api-esm/data
    • curl http://localhost:3001/api-esm/saudacao/pedro
    • curl http://localhost:3001/api-esm/saudacao/ana

Verifique as respostas JSON e os logs no terminal para confirmar que as funções foram executadas corretamente.

Troubleshooting dos Erros Mais Comuns

    • require is not defined ou import / export erros em arquivos .js: Se você configurou "type": "module" no package.json, todos os arquivos .js são ES Modules. Se tentar usar require() neles, terá um erro. Solução: use import / export ou renomeie o arquivo para .cjs.
    • Cannot use import statement outside a module (ES Modules): Isso significa que o Node.js está tratando seu arquivo como CommonJS, mas você está usando sintaxe import. Solução: adicione "type": "module" ao seu package.json ou use a extensão .mjs para o arquivo.
    • Cannot find module './utilidades' (CommonJS): Certifique-se de que o caminho do arquivo utilidades.cjs está correto e que a extensão .cjs foi especificada no require().
    • Cannot find module './utilidades.mjs' (ES Modules): Lembre-se que em ES Modules, é essencial incluir a extensão do arquivo (.mjs ou .js) para módulos locais. O Node.js não faz a inferência automática como no CommonJS.
    • Servidor não inicia na porta esperada: Verifique se a porta não está em uso por outro processo (rode netstat -ano | findstr :3000 no Windows ou lsof -i :3000 no Linux/macOS) ou se você digitou npm run start-cjs ou npm run start-esm corretamente.

Próximos Passos Sugeridos

Parabéns por completar este exercício! Para aprofundar ainda mais seu domínio sobre sistemas de módulos, eu sugiro:

    • Explorar Módulos Nativo do Node.js: Experimente importar módulos nativos como path, fs ou http usando ambas as sintaxes (require('path') vs import path from 'path').
    • Trabalhar com Módulos de Terceiros: Importe bibliotecas como axios ou lodash (que geralmente suportam ambos os formatos) para ver como eles se integram.
    • Refatorar um Projeto Existente: Se você tiver um pequeno projeto Node.js em CommonJS, tente refatorá-lo para usar ES Modules, lidando com os desafios que podem surgir.
    • Compreender o package.jsonexports field: Para projetos mais avançados, este campo no package.json permite definir como seu próprio pacote exporta módulos para serem consumidos por CommonJS ou ES Modules.

Este conhecimento é fundamental para você se tornar um desenvolvedor de APIs de alto nível, capaz de trabalhar com qualquer base de código e construir soluções robustas e performáticas. Continue praticando, e o domínio será seu!

🚀 Pronto para a próxima aula?

Continue sua jornada no desenvolvimento de APIs e domine Node.js & Express!

📚 Ver todas as aulas