Node.js além do backend: criando scripts de automação poderosos

Node.js revolucionou o desenvolvimento web, mas seu potencial vai muito além de servidores HTTP. Com seu modelo assíncrono e não bloqueante, rico ecossistema de pacotes e portabilidade entre sistemas operacionais, Node.js se tornou uma ferramenta excepcional para criar scripts de automação robustos e eficientes. Neste artigo, exploraremos como transformar tarefas repetitivas em scripts automatizados usando Node.js, desde manipulação de arquivos até agendamento de tarefas complexas.

1. Por que Node.js é ideal para automação?

A principal vantagem do Node.js para automação reside em sua natureza assíncrona. Diferente de linguagens como Python ou Bash, que executam operações sequencialmente, Node.js permite que múltiplas tarefas sejam processadas simultaneamente sem bloquear a execução. Isso é particularmente útil para scripts que precisam lidar com muitas operações de I/O, como leitura de arquivos, requisições de rede ou execução de comandos do sistema.

O ecossistema npm oferece milhares de bibliotecas prontas para automação, desde manipulação de arquivos até integração com APIs de terceiros. Além disso, scripts escritos em Node.js funcionam perfeitamente em Windows, Linux e macOS, eliminando a necessidade de reescrever código para diferentes plataformas.

2. Manipulação de arquivos e diretórios com fs

O módulo nativo fs (File System) é a base para qualquer script de automação envolvendo arquivos. Vamos criar um script que organiza automaticamente arquivos de download por extensão:

const fs = require('fs');
const path = require('path');

const downloadDir = './downloads';
const organizedDir = './organized';

// Criar diretório organizado se não existir
if (!fs.existsSync(organizedDir)) {
  fs.mkdirSync(organizedDir);
}

// Ler arquivos do diretório de download
fs.readdir(downloadDir, (err, files) => {
  if (err) throw err;

  files.forEach(file => {
    const ext = path.extname(file).slice(1);
    const extDir = path.join(organizedDir, ext);

    // Criar subdiretório para extensão
    if (!fs.existsSync(extDir)) {
      fs.mkdirSync(extDir);
    }

    // Mover arquivo
    const oldPath = path.join(downloadDir, file);
    const newPath = path.join(extDir, file);
    fs.rename(oldPath, newPath, err => {
      if (err) console.error(`Erro ao mover ${file}: ${err}`);
      else console.log(`Movido: ${file} -> ${ext}/`);
    });
  });
});

Para processar grandes volumes de dados, use streams:

const fs = require('fs');
const zlib = require('zlib');

const readStream = fs.createReadStream('grande-arquivo.log');
const writeStream = fs.createWriteStream('grande-arquivo.log.gz');
const gzip = zlib.createGzip();

readStream.pipe(gzip).pipe(writeStream)
  .on('finish', () => console.log('Compressão concluída'));

3. Automação de processos do sistema operacional

O módulo child_process permite executar comandos do shell diretamente de scripts Node.js. Exemplo de script de deploy:

const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

async function deploy() {
  try {
    console.log('Iniciando deploy...');

    const { stdout: gitStatus } = await execPromise('git status --porcelain');
    if (gitStatus) {
      console.log('Há mudanças não commitadas. Commitando...');
      await execPromise('git add .');
      await execPromise('git commit -m "Deploy automático"');
    }

    console.log('Enviando para repositório remoto...');
    await execPromise('git push origin main');

    console.log('Executando build...');
    await execPromise('npm run build');

    console.log('Deploy concluído com sucesso!');
  } catch (error) {
    console.error('Erro no deploy:', error.message);
  }
}

deploy();

4. Trabalhando com APIs e web scraping automatizado

Para requisições HTTP, axios é uma escolha popular. Exemplo de monitoramento de preços:

const axios = require('axios');
const cheerio = require('cheerio');

async function monitorarPreco(url) {
  try {
    const { data } = await axios.get(url);
    const $ = cheerio.load(data);

    const preco = $('.price').text().trim();
    const nome = $('h1').text().trim();

    console.log(`Produto: ${nome}`);
    console.log(`Preço atual: ${preco}`);

    // Salvar em log
    const fs = require('fs');
    const logEntry = `${new Date().toISOString()} - ${nome}: ${preco}\n`;
    fs.appendFileSync('precos.log', logEntry);

    // Verificar se está abaixo do limite
    const precoNumerico = parseFloat(preco.replace('R$', '').replace(',', '.'));
    if (precoNumerico < 100) {
      console.log('⚠️ Preço abaixo de R$100! Considere comprar.');
    }
  } catch (error) {
    console.error('Erro ao monitorar:', error.message);
  }
}

monitorarPreco('https://exemplo.com/produto');

5. Automação de tarefas repetitivas no dia a dia

Geração de relatórios CSV a partir de dados de API:

const axios = require('axios');
const fs = require('fs');

async function gerarRelatorioVendas() {
  try {
    const { data } = await axios.get('https://api.exemplo.com/vendas');

    let csv = 'Data,Produto,Valor,Cliente\n';
    data.vendas.forEach(venda => {
      csv += `${venda.data},${venda.produto},${venda.valor},${venda.cliente}\n`;
    });

    const filename = `relatorio_vendas_${Date.now()}.csv`;
    fs.writeFileSync(filename, csv);
    console.log(`Relatório gerado: ${filename}`);
  } catch (error) {
    console.error('Erro ao gerar relatório:', error.message);
  }
}

gerarRelatorioVendas();

6. Scripts com interface de linha de comando (CLI)

Criação de ferramenta CLI usando commander:

#!/usr/bin/env node
const { Command } = require('commander');
const fs = require('fs');
const path = require('path');

const program = new Command();

program
  .name('organizador')
  .description('Organiza arquivos por extensão')
  .version('1.0.0');

program
  .command('organizar')
  .description('Organiza arquivos do diretório atual')
  .option('-d, --diretorio <path>', 'Diretório para organizar', '.')
  .option('-r, --recursivo', 'Processar subdiretórios')
  .action((options) => {
    const dir = path.resolve(options.diretorio);
    console.log(`Organizando: ${dir}`);

    fs.readdir(dir, (err, files) => {
      if (err) {
        console.error('Erro ao ler diretório:', err.message);
        return;
      }

      files.forEach(file => {
        const filePath = path.join(dir, file);
        if (fs.statSync(filePath).isFile()) {
          const ext = path.extname(file).slice(1) || 'sem_extensao';
          const extDir = path.join(dir, ext);

          if (!fs.existsSync(extDir)) {
            fs.mkdirSync(extDir);
          }

          fs.renameSync(filePath, path.join(extDir, file));
          console.log(`  Movido: ${file} -> ${ext}/`);
        }
      });

      console.log('Organização concluída!');
    });
  });

program.parse(process.argv);

Para instalar globalmente: npm install -g .

7. Agendamento e execução periódica

Usando node-cron para tarefas agendadas:

const cron = require('node-cron');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');

// Backup diário às 2:00 AM
cron.schedule('0 2 * * *', async () => {
  console.log('Iniciando backup automático...');

  const date = new Date().toISOString().slice(0, 10);
  const backupDir = path.join(__dirname, 'backups', date);

  // Criar diretório de backup
  fs.mkdirSync(backupDir, { recursive: true });

  // Copiar arquivos importantes
  const importantDirs = ['./data', './config', './uploads'];
  importantDirs.forEach(dir => {
    if (fs.existsSync(dir)) {
      exec(`cp -r ${dir} ${backupDir}/`, (err, stdout, stderr) => {
        if (err) {
          console.error(`Erro ao copiar ${dir}: ${stderr}`);
        } else {
          console.log(`Backup de ${dir} concluído`);
        }
      });
    }
  });

  // Compactar backup
  exec(`tar -czf ${backupDir}.tar.gz ${backupDir}`, (err) => {
    if (err) {
      console.error('Erro ao compactar:', err.message);
    } else {
      console.log(`Backup compactado: ${backupDir}.tar.gz`);
    }
  });
});

console.log('Agendador de backup iniciado. Aguardando...');

8. Boas práticas e depuração de scripts de automação

Para scripts robustos, implemente logs estruturados:

const winston = require('winston');

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
    new winston.transports.Console({
      format: winston.format.simple()
    })
  ]
});

// Uso
logger.info('Script iniciado', { 
  script: 'backup.js',
  timestamp: new Date().toISOString() 
});

try {
  // ... lógica do script ...
} catch (error) {
  logger.error('Erro crítico', {
    error: error.message,
    stack: error.stack
  });
  process.exit(1);
}

Testes com Jest:

// organizador.test.js
const fs = require('fs');
const path = require('path');

describe('Organizador de arquivos', () => {
  const testDir = path.join(__dirname, 'test_files');

  beforeEach(() => {
    // Criar diretório de teste com arquivos
    fs.mkdirSync(testDir, { recursive: true });
    fs.writeFileSync(path.join(testDir, 'foto.jpg'), '');
    fs.writeFileSync(path.join(testDir, 'documento.pdf'), '');
    fs.writeFileSync(path.join(testDir, 'musica.mp3'), '');
  });

  afterEach(() => {
    // Limpar diretório de teste
    fs.rmSync(testDir, { recursive: true, force: true });
  });

  test('deve organizar arquivos por extensão', () => {
    const files = fs.readdirSync(testDir);
    expect(files).toHaveLength(3);

    // Simular organização
    files.forEach(file => {
      const ext = path.extname(file).slice(1);
      const extDir = path.join(testDir, ext);
      if (!fs.existsSync(extDir)) {
        fs.mkdirSync(extDir);
      }
      fs.renameSync(
        path.join(testDir, file),
        path.join(extDir, file)
      );
    });

    // Verificar organização
    expect(fs.existsSync(path.join(testDir, 'jpg'))).toBe(true);
    expect(fs.existsSync(path.join(testDir, 'pdf'))).toBe(true);
    expect(fs.existsSync(path.join(testDir, 'mp3'))).toBe(true);
  });
});

Conclusão

Node.js oferece um ecossistema maduro e versátil para automação de tarefas. Desde scripts simples de manipulação de arquivos até sistemas complexos de monitoramento e deploy, a combinação de assincronicidade, portabilidade e vasta biblioteca de pacotes torna Node.js uma escolha poderosa para automatizar processos do dia a dia. Comece com scripts pequenos e vá expandindo conforme suas necessidades — a flexibilidade do Node.js permite que você construa desde ferramentas CLI simples até sistemas de automação empresariais completos.

Referências