Logo
⚠️ Unsaved
[M]:

🎓 LEITOR DE ACESSIBILIDADE APRIMORADO COM IA

Curso Completo de Backend em TypeScript

Projeto Integrador: Construir uma aplicação pronta para produção que extrai texto de páginas web, gera resumos com IA, persiste em bancos de dados e oferece API REST segura.

✨ O que você vai aprender:

Capítulo 1 - TypeScript (tipos, genéricos, tipos utilitários) ✅ Capítulo 2 - Assincronismo (Promises, async/await, Event Loop) ✅ Capítulo 3 - Node.js Core (filesystem, CLI, configuração) ✅ Capítulo 4 - NestJS Fundamentos (controllers, services, DI) ✅ Capítulo 5 - NestJS Avançado (streams, interceptors, middleware) ✅ Capítulo 6 - Testes Automatizados (Jest, cobertura) ✅ Capítulo 7 - Bancos Relacionais (SQL, Prisma, migrações) ✅ Capítulo 8 - NoSQL (MongoDB, Mongoose) ✅ Capítulo 9 - APIs REST (JWT, validação, Swagger) ✅ Capítulo 10 - Logging & Observabilidade (Winston, IDs de correlação) ✅ Capítulo 11 - Arquitetura (SOLID, Clean Code, DDD) ✅ Capítulo 12 - Processamento Assíncrono (Bull, filas, eventos) ✅ Capítulo 13 - Docker & Containerização ✅ Capítulo 14 - APIs Externas & Integrações ✅ Capítulo 15 - Git Flow & Versionamento ✅ Capítulo 16 - Projeto Final & Deploy

📊 Duração: 12 semanas (aproximadamente)

🎯 Objetivo Final: Aplicação pronta para produção


[M]:

CAPÍTULO 0️⃣: Setup do Ambiente

Objetivo

Preparar o ambiente de desenvolvimento com todas as dependências necessárias.

Pré-requisitos

  • Node.js 18+
  • npm ou yarn
  • Conhecimento básico de JavaScript
  • Terminal/CMD

O que vamos fazer

  1. ✅ Estrutura de um projeto TypeScript
  2. ✅ package.json com dependências
  3. ✅ Configuração de TypeScript
  4. ✅ Sistema de logging estruturado
  5. ✅ Configuração centralizada

[ ]:
// SETUP: Simulando dependências principais do projeto
// Em um projeto real, você rodaria: npm install

const dependenciasEsperadas = {
"dependencies": {
"express": "^4.18.0",
"@nestjs/common": "^10.0.0",
"@nestjs/core": "^10.0.0",
"axios": "^1.4.0",
"prisma": "^5.0.0",
"mongoose": "^7.0.0",
"jsonwebtoken": "^9.0.0",
"winston": "^3.8.0",
"bull": "^4.11.0",
"zod": "^3.22.0",
"reflect-metadata": "^0.1.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"typescript": "^5.0.0",
"@types/node": "^20.0.0",
"jest": "^29.0.0",
"@types/jest": "^29.0.0",
"ts-jest": "^29.0.0",
"ts-node": "^10.9.0",
"eslint": "^8.0.0",
"prettier": "^3.0.0"
}
};

console.log('✅ Dependências esperadas carregadas');
console.log(`📦 Total de pacotes: ${Object.keys(dependenciasEsperadas.dependencies).length + Object.keys(dependenciasEsperadas.devDependencies).length}`);
[ ]:
// SETUP: Configuração centralizada da aplicação

interface ConfiguracaoApp {
// Ambiente
NODE_ENV: 'development' | 'production' | 'test';
DEBUG: boolean;
// API
PORTA: number;
HOST: string;
// Banco de dados
DATABASE_URL: string;
MONGODB_URL: string;
// Integrações externas
CHAVE_OPENAI: string;
CHAVE_OCRDOTSPACE: string;
// Segurança
SEGREDO_JWT: string;
// Limites
TAMANHO_MAXIMO_MB: number;
TAMANHO_MAXIMO_TEXTO: number;
TIMEOUT_REQUISICAO_MS: number;
}

const criarConfiguracao = (): ConfiguracaoApp => {
const env = process.env.NODE_ENV || 'development';
return {
NODE_ENV: env as any,
DEBUG: env === 'development',
PORTA: parseInt(process.env.PORT || '3000'),
[ ]:
// SETUP: Sistema de Logging Estruturado

interface EntradaLog {
timestamp: string;
nivel: 'DEBUG' | 'INFO' | 'AVISO' | 'ERRO';
mensagem: string;
contexto?: string;
dados?: any;
idRequisicao?: string;
}

class Logger {
private contexto: string;
constructor(contexto: string) {
this.contexto = contexto;
}
private registrar(nivel: EntradaLog['nivel'], mensagem: string, dados?: any) {
const entrada: EntradaLog = {
timestamp: new Date().toISOString(),
nivel,
mensagem,
contexto: this.contexto,
...(dados && { dados })
};
if (configuracao.DEBUG || nivel === 'ERRO') {
console.log(JSON.stringify(entrada, null, 2));
}
}
info(mensagem: string, dados?: any) {
this.registrar('INFO', mensagem, dados);
}
[M]:

CAPÍTULO 1️⃣: Fundamentos do TypeScript

Módulo 1 - Base do TypeScript

Duração: ~3-4 horas

🎯 Objetivos de Aprendizagem:

  • Entender Types vs Interfaces
  • Criar componentes reutilizáveis com Genéricos
  • Transformar tipos com Tipos Utilitários
  • Definir o modelo de domínio do leitor de acessibilidade
  • Implementar guardiões de tipo

📚 Tópicos Abordados:

  • Union types e tipos literais
  • Types vs Interfaces
  • Genéricos (simples e restritos)
  • Tipos utilitários: Partial, Pick, Omit, Record, Exclude, Extract
  • Decoradores (prévia para NestJS)
  • Guardas de tipo e predicados de tipo

🔗 Conexão com IA:

Este capítulo é a base para estruturar os dados que o modelo de IA vai processar (textos extraídos, resumos gerados, metadados).


[M]:

1.1 - TIPOS vs INTERFACES: Escolhendo a ferramenta certa

Regra prática:

  • Type: Melhor para unions, primitivos, tuplas, tipos complexos
  • Interface: Melhor para contratos de objetos, extensibilidade
[ ]:
// 1.1: TIPOS BASE DO PROJETO

// Type: Melhor para unions
type FonteTexto = 'textoPlano' | 'ocr' | 'paginaHtml';
type Idioma = 'pt-BR' | 'en-US' | 'es-ES';
type ResultadoExtracao =
| { sucesso: true; texto: string; confianca: number }
| { sucesso: false; erro: string };

// Interface: Melhor para contratos
interface MetadadosConteudo {
id: string;
fonteTexto: FonteTexto;
idioma: Idioma;
extraidoEm: Date;
urlOrigem?: string;
tamanhoCaracteres: number;
}

interface Resumo {
id: string;
conteudoId: string;
textoOriginal: string;
textoResumido: string;
porcentagemReducao: number;
geradoEm: Date;
modeloIA: string;
}

// Exemplos de uso
const resultado: ResultadoExtracao = {
sucesso: true,
texto: 'Este é o texto extraído da página',
confianca: 0.95
};

[M]:

1.2 - GENÉRICOS: Reutilização de código com type-safety

[ ]:
// 1.2: GENÉRICOS

// Generic Result wrapper
interface Resultado<T, E = Error> {
ok: boolean;
dados?: T;
erro?: E;
}

function criarResultado<T, E = Error>(
ok: boolean,
dados?: T,
erro?: E
): Resultado<T, E> {
return { ok, dados, erro };
}

// Generic Task para pipeline
interface Tarefa<Entrada, Saida> {
id: string;
entrada: Entrada;
processar: (x: Entrada) => Promise<Saida>;
saida?: Saida;
status: 'pendente' | 'processando' | 'concluido' | 'erro';
}

// Exemplos de uso
const resultadoTexto = criarResultado(true, 'Texto extraído com sucesso');
const resultadoFalho = criarResultado(false, undefined, new Error('OCR falhou'));

const tarefaExtracao: Tarefa<string, string> = {
id: 'task-001',
entrada: 'Conteúdo da página...',
processar: async (conteudo) => conteudo.toUpperCase(),
status: 'pendente'
};
[M]:

1.3 - TIPOS UTILITÁRIOS: Transformando tipos existentes

[ ]:
// 1.3: TIPOS UTILITÁRIOS

interface UsuarioCompleto {
id: string;
email: string;
senha: string; // 🔒 NUNCA expor
papel: 'admin' | 'usuario' | 'leitor';
nomeCompleto: string;
criadoEm: Date;
atualizadoEm: Date;
}

// Partial: Todos os campos opcionais
type AtualizacaoUsuario = Partial<UsuarioCompleto>;
const atualizacao: AtualizacaoUsuario = {
email: 'novo@email.com',
nomeCompleto: 'João Silva'
};

// Pick: Selecionar apenas alguns
type UsuarioPublico = Pick<UsuarioCompleto, 'id' | 'email' | 'nomeCompleto' | 'papel'>;
const usuarioPublico: UsuarioPublico = {
id: '123',
email: 'user@example.com',
nomeCompleto: 'João Silva',
papel: 'usuario'
};

// Omit: Excluir campos sensíveis
type UsuarioDTO = Omit<UsuarioCompleto, 'senha'>;
const dto: UsuarioDTO = {
id: '123',
email: 'user@example.com',
papel: 'usuario',
nomeCompleto: 'João Silva',
criadoEm: new Date(),
[M]:

1.4 - REPOSITORY PATTERN: Abstração de Dados

[ ]:
// 1.4: REPOSITORY PATTERN

interface Repositorio<T> {
criar(item: Omit<T, 'id' | 'criadoEm' | 'atualizadoEm'>): Promise<T>;
obterPorId(id: string): Promise<T | null>;
obterTodos(): Promise<T[]>;
atualizar(id: string, atualizacoes: Partial<T>): Promise<T | null>;
deletar(id: string): Promise<boolean>;
}

// Entidade de Resumo
interface ResumoIA {
id: string;
conteudoId: string;
textoOriginal: string;
textoResumido: string;
porcentagemReducao: number;
modeloUsado: string;
criadoEm: Date;
atualizadoEm: Date;
}

// Implementação em memória (para testes)
class RepositorioResumoMemoria implements Repositorio<ResumoIA> {
private dados: Map<string, ResumoIA> = new Map();
private proximoId = 1;
async criar(item: Omit<ResumoIA, 'id' | 'criadoEm' | 'atualizadoEm'>): Promise<ResumoIA> {
const resumo: ResumoIA = {
id: String(this.proximoId++),
...item,
criadoEm: new Date(),
atualizadoEm: new Date()
};
this.dados.set(resumo.id, resumo);
return resumo;
[M]:

🏋️ EXERCÍCIO 1.1: Construir Tipos de Domínio

TODO: Criar tipos para o leitor de acessibilidade:

  1. Criar EntradaResumo que exclui id, criadoEm, atualizadoEm
  2. Criar RespostaAPI como union (sucesso OU erro)
  3. Criar ResultadoPaginado para respostas paginadas
  4. Testar com dados reais
[ ]:
// ✍️ SOLUÇÃO EXERCÍCIO 1.1

// TODO 1: Tipo utilitário para entrada
type EntradaResumo = Omit<ResumoIA, 'id' | 'criadoEm' | 'atualizadoEm'>;

// TODO 2: Resposta da API
type RespostaAPI<T> =
| { sucesso: true; dados: T; erro?: undefined }
| { sucesso: false; dados?: undefined; erro: { codigo: string; mensagem: string } };

// TODO 3: Paginação
interface ResultadoPaginado<T> {
itens: T[];
total: number;
pagina: number;
itensPorPagina: number;
temMais: boolean;
}

// Testar
const entradaResumo: EntradaResumo = {
conteudoId: 'content-001',
textoOriginal: 'Texto original longo',
textoResumido: 'Texto resumido',
porcentagemReducao: 80,
modeloUsado: 'gpt-4'
};

const respostaSucesso: RespostaAPI<ResumoIA> = {
sucesso: true,
dados: {
id: '1',
...entradaResumo,
criadoEm: new Date(),
atualizadoEm: new Date()
}
[M]:

✅ CHECKPOINT DO CAPÍTULO 1

O que você aprendeu:

  • Diferença entre Types e Interfaces
  • Genéricos para componentes reutilizáveis
  • Tipos Utilitários: Partial, Pick, Omit, Record
  • Repository Pattern com genéricos
  • Construir modelos de domínio seguros

💡 Conceitos-chave:

  1. Strong typing previne erros em compile-time, não em runtime
  2. Genéricos permitem reutilizar tipos com segurança
  3. Utility types transformam tipos existentes sem duplicação
  4. Repository pattern desacopla dados da lógica

🚀 Próximo: Capítulo 2 - Assincronismo & Promises

  • Entender o Event Loop
  • Dominar Promises e async/await
  • Implementar retry logic
  • Paralelizar operações

⏱️ Tempo até aqui: ~3-4 horas


[M]:

CAPÍTULO 2️⃣: Assincronismo & Promises

Módulo 2 - Assincronismo + POO

Duração: ~4-5 horas

🎯 Objetivos de Aprendizagem:

  • Dominar Promise states (pending → resolved/rejected)
  • Usar async/await para código assíncrono moderno
  • Entender o Event Loop (microtasks vs macrotasks)
  • Implementar retry logic com exponential backoff
  • Paralelizar vs sequenciar operações

📚 Tópicos Abordados:

  • Estados da Promise (pendente, resolvida, rejeitada)
  • .then(), .catch(), .finally() encadeamento
  • Sintaxe async/await e tratamento de erros
  • Event Loop: pilha de chamadas, fila de callback, microtarefas, macrotarefas
  • Promise.all(), Promise.race(), Promise.allSettled()
  • Lógica de repetição com backoff exponencial
  • Execução paralela versus sequencial

🔗 Conexão com IA:

O modelo de IA geralmente é lento (chamadas a APIs externas). Você aprenderá a:

  • Fazer múltiplas extrações em paralelo
  • Retentar automaticamente se a API de IA cair
  • Não bloquear o servidor enquanto aguarda respostas

[M]:

2.1 - PROMISES: Estados e Transições

[ ]:
// 2.1: PROMISES BÁSICO

// Simulando uma API externa de OCR
function extrairComOCR(caminhoImagem: string): Promise<string> {
return new Promise((resolve, reject) => {
setTimeout(() => {
const sucesso = Math.random() > 0.3; // 70% sucesso
if (sucesso) {
resolve('Texto extraído com sucesso da imagem');
} else {
reject(new Error('Timeout no serviço OCR'));
}
}, 800);
});
}

// Promise chaining
extrairComOCR('documento.png')
.then((texto) => {
console.log('✅ Extraído:', texto.substring(0, 40) + '...');
return texto.length;
})
.then((tamanho) => {
console.log('✅ Tamanho:', tamanho, 'caracteres');
})
.catch((erro) => {
console.log('❌ Erro:', erro.message);
})
.finally(() => {
console.log('✅ Promise concluída (limpeza)');
});

await new Promise(resolve => setTimeout(resolve, 1500));
[M]:

2.2 - ASYNC/AWAIT: Sintaxe Moderna

[ ]:
// 2.2: ASYNC/AWAIT

async function resumirComIA(texto: string): Promise<string> {
await new Promise(resolve => setTimeout(resolve, 600));
return texto.split(' ').slice(0, 5).join(' ') + '...';
}

async function extrairEResumirComIA(caminhoImagem: string) {
const inicio = Date.now();
try {
console.log('📷 Extraindo texto...');
const textoExtraido = await extrairComOCR(caminhoImagem);
console.log('✅ Extração concluída');
console.log('🤖 Enviando para IA de resumo...');
const resumo = await resumirComIA(textoExtraido);
console.log('✅ Resumo gerado');
const tempoProcessamento = Date.now() - inicio;
return { textoExtraido, resumo, tempoProcessamento };
} catch (erro) {
console.error('❌ Pipeline falhou:', erro instanceof Error ? erro.message : 'Erro');
throw erro;
}
}

// Executar
const resultado = await extrairEResumirComIA('documento.png');
console.log('\n✅ Pipeline async/await concluído');
[M]:

2.3 - EVENT LOOP: Ordem de Execução

[ ]:
// 2.3: EVENT LOOP

console.log('\n=== DEMONSTRAÇÃO DO EVENT LOOP ===\n');
console.log('1️⃣ Código Síncrono: Início');

// Macrotask
setTimeout(() => {
console.log('4️⃣ setTimeout (MACROTASK)');
}, 0);

// Microtask
Promise.resolve()
.then(() => {
console.log('3️⃣ Promise.then (MICROTASK)');
});

console.log('2️⃣ Código Síncrono: Fim\n');

// Ordem esperada: 1, 2, 3, 4
// Porque: Sincronismo → Microtasks → Macrotasks

await new Promise(resolve => setTimeout(resolve, 100));
console.log('\n💡 Event Loop:');
console.log(' 1. Executa STACK (código síncrono)');
console.log(' 2. Esvazia MICROTASK QUEUE (promises)');
console.log(' 3. Executa 1 MACROTASK (setTimeout)');
[M]:

2.4 - PARALELIZAÇÃO: Promise.all() vs Sequencial

[ ]:
// 2.4: PARALELIZAÇÃO

async function extrairSequencial() {
console.time('Sequencial');
console.log('\n⏳ Processamento SEQUENCIAL...');
const texto1 = await extrairComOCR('doc1.png');
const texto2 = await extrairComOCR('doc2.png');
const texto3 = await extrairComOCR('doc3.png');
console.timeEnd('Sequencial');
return [texto1, texto2, texto3];
}

async function extrairParalelo() {
console.time('Paralelo');
console.log('\n⚡ Processamento PARALELO...');
const promises = [
extrairComOCR('doc1.png'),
extrairComOCR('doc2.png'),
extrairComOCR('doc3.png')
];
const resultados = await Promise.all(promises);
console.timeEnd('Paralelo');
return resultados;
}

// Comparar
await extrairSequencial();
await extrairParalelo();

console.log('\n📊 Resumo de Performance:');
console.log(' Sequencial: ~2.4 segundos (3 × 800ms)');
[M]:

2.5 - RETRY LOGIC: Lidar com Falhas Transientes

[ ]:
// 2.5: RETRY LOGIC

async function buscarComRetentativa<T>(
funcao: () => Promise<T>,
maxRetentativas: number = 3,
delayBaseMs: number = 1000
): Promise<T> {
for (let tentativa = 1; tentativa <= maxRetentativas; tentativa++) {
try {
console.log(`🔄 Tentativa ${tentativa}/${maxRetentativas}...`);
return await funcao();
} catch (erro) {
const ehUltimaTentativa = tentativa === maxRetentativas;
if (ehUltimaTentativa) {
console.error('❌ Todas as retentativas falharam');
throw erro;
}
// Exponential backoff: 1s, 2s, 4s, 8s...
const delay = delayBaseMs * Math.pow(2, tentativa - 1);
console.log(`⏳ Retentando em ${delay}ms...`);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}

console.log('✅ Retry logic com exponential backoff ready');
console.log('\n📈 Padrão de Retry:');
console.log(' Tentativa 1: imediato');
console.log(' Tentativa 2: espera 1000ms');
console.log(' Tentativa 3: espera 2000ms');
[M]:

🏋️ EXERCÍCIO 2.1: Pipeline Assíncrono Completo

TODO: Construir um pipeline real que:

  1. Extrai texto de 2 imagens EM PARALELO
  2. Resume cada texto EM PARALELO também
  3. Trata erros gracefully em cada passo
  4. Retorna resultados com estrutura clara
  5. Encapsula em try/catch para resiliência
[ ]:
// ✍️ SOLUÇÃO EXERCÍCIO 2.1

async function pipelineExtrairEResumirCompleto() {
try {
console.time('Pipeline Completo');
// TODO 1: Extrair 2 imagens em paralelo
console.log('\n📷 Extraindo de 2 imagens em PARALELO...');
const promessasExtracao = [
extrairComOCR('documento1.png'),
extrairComOCR('documento2.png')
];
const textosExtraidos = await Promise.all(promessasExtracao);
console.log('✅ Extração concluída');
// TODO 2: Resumir cada um em paralelo
console.log('\n🤖 Resumindo ambos em PARALELO...');
const promessasResumo = textosExtraidos.map(texto => resumirComIA(texto));
const resumos = await Promise.all(promessasResumo);
console.log('✅ Resumos gerados');
console.timeEnd('Pipeline Completo');
// TODO 3: Retornar resultados
return {
sucesso: true,
dados: {
extracao: textosExtraidos,
resumos: resumos
}
};
} catch (erro) {
// TODO 4: Tratar erros
console.error('❌ Pipeline falhou:', erro);
return {
[M]:

✅ CHECKPOINT DO CAPÍTULO 2

O que você aprendeu:

  • Estados de Promise (pending → resolved/rejected)
  • .then().catch().finally() e seu encadeamento
  • async/await e tratamento de erros
  • Event Loop: microtasks executam antes de macrotasks
  • Promise.all(), Promise.race(), Promise.allSettled()
  • Paralelização vs execução sequencial
  • Retry logic com exponential backoff

💡 Conceitos-chave:

  1. Asincronismo é essencial em Node.js
  2. Paralelização faz sistemas rápidos
  3. Error handling em boundaries assíncronas
  4. Retry logic torna sistemas resilientes

🚀 Próximo: Capítulo 3 - Node.js Core & CLI

⏱️ Tempo até aqui: ~7-9 horas


[M]:

⚠️ CAPÍTULOS 3-16: ESTRUTURA

Capítulo 3️⃣: Node.js Core (filesystem, CLI, env)

Capítulo 4️⃣: NestJS Fundamentos (controllers, services, DI)

Capítulo 5️⃣: NestJS Avançado (streams, interceptors, middleware)

Capítulo 6️⃣: Testes Automatizados (Jest, cobertura)

Capítulo 7️⃣: Bancos Relacionais (SQL, Prisma, migrações)

Capítulo 8️⃣: NoSQL (MongoDB, Mongoose)

Capítulo 9️⃣: REST APIs (JWT, validação, Swagger)

Capítulo 🔟: Logging & Observabilidade

Capítulo 1️⃣1️⃣: Arquitetura (SOLID, DDD, Clean Code)

Capítulo 1️⃣2️⃣: Processamento Assíncrono (Bull, filas)

Capítulo 1️⃣3️⃣: Docker & Containerização

Capítulo 1️⃣4️⃣: APIs Externas & Integrações

Capítulo 1️⃣5️⃣: Git Flow & Versionamento

Capítulo 1️⃣6️⃣: Projeto Final & Deploy

📝 Cada capítulo segue a mesma estrutura:

  1. Objetivos de aprendizagem
  2. Tópicos abordados
  3. Explicações em Markdown
  4. Código completo e executável
  5. Exemplos práticos
  6. Exercícios com soluções
  7. Checkpoint de validação
  8. Roadmap para próximo capítulo

🎯 ROADMAP COMPLETO - 12 SEMANAS

Semana 1: Fundações ✅

  • ✅ Cap. 1 - TypeScript
  • ✅ Cap. 2 - Async/Promises
  • 📝 Cap. 3 - Node.js Core

Semana 2: Framework

  • 📝 Cap. 4 - NestJS Basics
  • 📝 Cap. 5 - NestJS Avançado

Semana 3: Qualidade

  • 📝 Cap. 6 - Testes

Semanas 4-5: Persistência

  • 📝 Cap. 7 - Bancos Relacionais
  • 📝 Cap. 8 - NoSQL

Semana 6: APIs

  • 📝 Cap. 9 - REST APIs

Semana 7: Produção

  • 📝 Cap. 10 - Logging & Observabilidade

Semana 8: Arquitetura

  • 📝 Cap. 11 - Arquitetura
  • 📝 Cap. 12 - Background Jobs

Semana 9: Deployment

  • 📝 Cap. 13 - Docker
  • 📝 Cap. 14 - Integrações
  • 📝 Cap. 15 - Git Flow

Semanas 10-12: Projeto Final

  • 📝 Cap. 16 - Integração & Deploy

📚 Recursos Recomendados


🎓 Critérios de Sucesso Final

Ao completar este notebook, você será capaz de:

✅ Escrever TypeScript com tipos avançados e genéricos ✅ Criar pipelines assíncronos com tratamento de erros ✅ Criar aplicações NestJS com múltiplos módulos ✅ Projetar bancos de dados relacionais e NoSQL ✅ Implementar APIs REST com autenticação ✅ Elaborar testes abrangentes ✅ Configurar logging e observabilidade ✅ Aplicar princípios de arquitetura limpa ✅ Containerizar aplicações com Docker ✅ Integrar com APIs externas de forma segura ✅ Seguir Git Flow e DevOps ✅ Implantar em produção


🚀 Bora começar!

Parabéns por completar os Capítulos 1 e 2! Você agora tem as fundações sólidas em TypeScript e Asincronismo.

Próximo passo: Capítulo 3 - Node.js Core (filesystem, CLI, variáveis de ambiente)

Boa sorte! 💪

Sign in to save your work and access it from anywhere