Examples

Exemplos Node.js

Exemplos completos de integração com Node.js, Express, TypeScript e mais

Exemplos Node.js

Exemplos práticos e completos de como integrar o RenderHub em aplicações Node.js.

Instalação

npm install axios
# ou
yarn add axios

Configuração Básica

Variáveis de Ambiente

Crie um arquivo .env:

RENDERHUB_API_KEY=rh_live_sua_chave_aqui
RENDERHUB_BASE_URL=https://renderhub.com/api/v1

Cliente RenderHub

// lib/renderhub.js
const axios = require('axios');
require('dotenv').config();

class RenderHubClient {
  constructor(apiKey = process.env.RENDERHUB_API_KEY) {
    this.apiKey = apiKey;
    this.baseURL = process.env.RENDERHUB_BASE_URL || 'https://renderhub.com/api/v1';

    this.client = axios.create({
      baseURL: this.baseURL,
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000
    });
  }

  async render(options) {
    try {
      const response = await this.client.post('/render', options);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async createTemplate(template) {
    try {
      const response = await this.client.post('/templates', template);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  async getTemplate(templateId) {
    try {
      const response = await this.client.get(`/templates/${templateId}`);
      return response.data;
    } catch (error) {
      this.handleError(error);
    }
  }

  handleError(error) {
    if (error.response) {
      const { status, data } = error.response;

      switch (status) {
        case 401:
          throw new Error(`Erro de autenticação: ${data.message}`);
        case 403:
          throw new Error(`Quota excedida: ${data.message}`);
        case 429:
          const retryAfter = data.retry_after || 60;
          throw new Error(`Rate limit excedido. Tente novamente em ${retryAfter}s`);
        default:
          throw new Error(`Erro ${status}: ${data.message || error.message}`);
      }
    }
    throw error;
  }
}

module.exports = RenderHubClient;

Exemplo 1: Converter HTML para PDF

// examples/convert-html.js
const RenderHubClient = require('../lib/renderhub');
const fs = require('fs');

async function convertHTMLToPDF() {
  const client = new RenderHubClient();

  const html = `
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <style>
          body {
            font-family: Arial, sans-serif;
            padding: 40px;
            max-width: 800px;
            margin: 0 auto;
          }
          h1 {
            color: #2563eb;
            border-bottom: 3px solid #2563eb;
            padding-bottom: 10px;
          }
          .highlight {
            background: #fef3c7;
            padding: 20px;
            border-radius: 8px;
            margin: 20px 0;
          }
        </style>
      </head>
      <body>
        <h1>Relatório de Vendas - Janeiro 2024</h1>

        <div class="highlight">
          <h2>Resumo</h2>
          <p>Total de vendas: R$ 150.000,00</p>
          <p>Novos clientes: 42</p>
          <p>Taxa de conversão: 15.8%</p>
        </div>

        <h2>Detalhes</h2>
        <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit...</p>
      </body>
    </html>
  `;

  const result = await client.render({
    mode: 'CONVERT',
    input_type: 'html',
    data: html,
    page_size: 'A4',
    orientation: 'portrait'
  });

  // Salvar PDF
  const pdfBuffer = Buffer.from(result.pdf_base64, 'base64');
  fs.writeFileSync('relatorio.pdf', pdfBuffer);

  console.log('✅ PDF gerado com sucesso!');
  console.log(`Páginas: ${result.pages}`);
  console.log(`Tamanho: ${result.file_size_kb} KB`);
  console.log(`Duração: ${result.duration_ms}ms`);
}

convertHTMLToPDF().catch(console.error);

Exemplo 2: Fatura com Template

// examples/generate-invoice.js
const RenderHubClient = require('../lib/renderhub');
const fs = require('fs');

// 1. Criar template (fazer apenas uma vez)
async function createInvoiceTemplate() {
  const client = new RenderHubClient();

  const template = await client.createTemplate({
    name: 'invoice-template-v1',
    description: 'Template de fatura padrão',
    category: 'invoice',
    content: fs.readFileSync('./templates/invoice.html', 'utf8'),
    variables: [
      { name: 'company_name', type: 'string', required: true },
      { name: 'invoice_number', type: 'string', required: true },
      { name: 'customer_name', type: 'string', required: true },
      { name: 'items', type: 'array', required: true },
      { name: 'total', type: 'string', required: true }
    ]
  });

  console.log('Template criado:', template.id);
  return template.id;
}

// 2. Gerar fatura a partir do template
async function generateInvoice(customerId, templateId) {
  const client = new RenderHubClient();

  // Buscar dados do cliente e pedidos (simulado)
  const customer = {
    id: customerId,
    name: 'João Silva Comércio LTDA',
    email: 'joao@example.com',
    address: 'Rua das Flores, 123',
    city: 'São Paulo',
    state: 'SP'
  };

  const orders = [
    { product: 'Notebook Dell', quantity: 2, price: 3500.00 },
    { product: 'Mouse Logitech', quantity: 5, price: 150.00 },
    { product: 'Teclado Mecânico', quantity: 3, price: 450.00 }
  ];

  // Mapear dados para o template
  const invoiceData = {
    company_name: 'Minha Empresa LTDA',
    company_cnpj: '12.345.678/0001-99',
    invoice_number: `2024-${customerId.toString().padStart(5, '0')}`,
    invoice_date: new Date().toLocaleDateString('pt-BR'),

    customer_name: customer.name,
    customer_email: customer.email,
    customer_address: `${customer.address}, ${customer.city} - ${customer.state}`,

    items: orders.map(order => ({
      description: order.product,
      quantity: order.quantity,
      unit_price: order.price.toFixed(2),
      total: (order.quantity * order.price).toFixed(2)
    })),

    subtotal: orders.reduce((sum, o) => sum + (o.quantity * o.price), 0).toFixed(2),
    tax: (orders.reduce((sum, o) => sum + (o.quantity * o.price), 0) * 0.15).toFixed(2),
    total: (orders.reduce((sum, o) => sum + (o.quantity * o.price), 0) * 1.15).toFixed(2)
  };

  // Renderizar PDF
  const result = await client.render({
    mode: 'RENDER',
    template_id: templateId,
    data: invoiceData
  });

  // Salvar PDF
  const pdfBuffer = Buffer.from(result.pdf_base64, 'base64');
  const filename = `invoice-${invoiceData.invoice_number}.pdf`;
  fs.writeFileSync(filename, pdfBuffer);

  console.log(`✅ Fatura ${invoiceData.invoice_number} gerada!`);
  return filename;
}

// Executar
(async () => {
  const templateId = 'tpl_abc123'; // Ou criar com createInvoiceTemplate()
  await generateInvoice(1001, templateId);
})();

Exemplo 3: Integração com Express

// server.js
const express = require('express');
const RenderHubClient = require('./lib/renderhub');

const app = express();
app.use(express.json());

const renderhub = new RenderHubClient();

// Endpoint para gerar PDF
app.post('/api/generate-pdf', async (req, res) => {
  try {
    const { template_id, data } = req.body;

    // Validar dados
    if (!template_id || !data) {
      return res.status(400).json({
        error: 'template_id e data são obrigatórios'
      });
    }

    // Gerar PDF
    const result = await renderhub.render({
      mode: 'RENDER',
      template_id,
      data
    });

    // Retornar PDF em base64 ou enviar arquivo
    res.json({
      success: true,
      pdf_base64: result.pdf_base64,
      pages: result.pages,
      file_size_kb: result.file_size_kb
    });

  } catch (error) {
    console.error('Erro ao gerar PDF:', error);
    res.status(500).json({
      error: 'Erro ao gerar PDF',
      message: error.message
    });
  }
});

// Endpoint para download direto
app.post('/api/download-pdf', async (req, res) => {
  try {
    const { template_id, data } = req.body;

    const result = await renderhub.render({
      mode: 'RENDER',
      template_id,
      data
    });

    // Converter base64 para buffer
    const pdfBuffer = Buffer.from(result.pdf_base64, 'base64');

    // Enviar como download
    res.setHeader('Content-Type', 'application/pdf');
    res.setHeader('Content-Disposition', 'attachment; filename="document.pdf"');
    res.send(pdfBuffer);

  } catch (error) {
    console.error('Erro:', error);
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000, () => {
  console.log('Servidor rodando na porta 3000');
});

Exemplo 4: Processamento em Lote com Fila

// examples/batch-processing.js
const Queue = require('bull');
const RenderHubClient = require('../lib/renderhub');
const fs = require('fs');

// Criar fila
const pdfQueue = new Queue('pdf-generation', {
  redis: {
    host: '127.0.0.1',
    port: 6379
  }
});

// Processar jobs
pdfQueue.process(async (job) => {
  const { customerId, templateId } = job.data;

  console.log(`Processando fatura para cliente ${customerId}...`);

  const client = new RenderHubClient();

  // Buscar dados do cliente
  const customerData = await fetchCustomerData(customerId);

  // Gerar PDF
  const result = await client.render({
    mode: 'RENDER',
    template_id: templateId,
    data: customerData
  });

  // Salvar
  const pdfBuffer = Buffer.from(result.pdf_base64, 'base64');
  const filename = `invoices/invoice-${customerId}.pdf`;
  fs.writeFileSync(filename, pdfBuffer);

  // Atualizar progresso
  job.progress(100);

  return { filename, customerId };
});

// Adicionar jobs à fila
async function generateInvoicesForAllCustomers(customerIds, templateId) {
  for (const customerId of customerIds) {
    await pdfQueue.add({
      customerId,
      templateId
    }, {
      attempts: 3,
      backoff: {
        type: 'exponential',
        delay: 2000
      }
    });
  }

  console.log(`${customerIds.length} jobs adicionados à fila`);
}

// Eventos
pdfQueue.on('completed', (job, result) => {
  console.log(`✅ Job ${job.id} concluído: ${result.filename}`);
});

pdfQueue.on('failed', (job, err) => {
  console.error(`❌ Job ${job.id} falhou:`, err.message);
});

// Executar
const customerIds = [1001, 1002, 1003, 1004, 1005];
generateInvoicesForAllCustomers(customerIds, 'tpl_invoice_v1');

async function fetchCustomerData(customerId) {
  // Simular busca no banco
  return {
    customer_name: `Cliente ${customerId}`,
    invoice_number: `2024-${customerId}`,
    total: '1500.00'
  };
}

Exemplo 5: Webhooks para PDFs Assíncronos

// webhook-server.js
const express = require('express');
const RenderHubClient = require('./lib/renderhub');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const renderhub = new RenderHubClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Gerar PDF com webhook
app.post('/api/generate-async', async (req, res) => {
  try {
    const { template_id, data } = req.body;

    const result = await renderhub.render({
      mode: 'RENDER',
      template_id,
      data,
      webhook_url: 'https://meuapp.com/webhooks/pdf-ready',
      webhook_headers: {
        'X-Custom-Token': 'meu-token-secreto'
      }
    });

    // Retorna imediatamente com job_id
    res.json({
      success: true,
      job_id: result.job_id,
      status: result.status
    });

  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Receber callback do RenderHub
app.post('/webhooks/pdf-ready', async (req, res) => {
  try {
    // Validar assinatura HMAC
    const signature = req.headers['x-renderhub-signature'];
    const payload = JSON.stringify(req.body);

    const expectedSignature = crypto
      .createHmac('sha256', WEBHOOK_SECRET)
      .update(payload)
      .digest('hex');

    if (signature !== expectedSignature) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    // Processar webhook
    const { id, status, job_id, pdf_url, error } = req.body;

    if (status === 'DONE') {
      console.log(`✅ PDF ${id} pronto!`);

      // Baixar PDF da URL temporária
      const axios = require('axios');
      const pdfResponse = await axios.get(pdf_url, {
        responseType: 'arraybuffer'
      });

      // Salvar em S3, storage, etc
      const fs = require('fs');
      fs.writeFileSync(`pdfs/${id}.pdf`, pdfResponse.data);

      // Atualizar status no banco de dados
      await db.jobs.update(job_id, {
        status: 'COMPLETED',
        pdf_path: `pdfs/${id}.pdf`
      });

      // Notificar usuário (email, websocket, etc)
      await notifyUser(job_id);

    } else if (status === 'FAILED') {
      console.error(`❌ Erro ao gerar PDF: ${error}`);

      await db.jobs.update(job_id, {
        status: 'FAILED',
        error_message: error
      });
    }

    // IMPORTANTE: Retornar 200 OK rapidamente
    res.status(200).json({ received: true });

  } catch (error) {
    console.error('Erro ao processar webhook:', error);
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);

Exemplo 6: TypeScript

// lib/renderhub.ts
import axios, { AxiosInstance } from 'axios';

interface RenderOptions {
  mode: 'CONVERT' | 'RENDER';
  input_type?: 'html' | 'docx' | 'markdown';
  data?: string | object;
  template_id?: string;
  page_size?: 'A4' | 'Letter' | 'Legal';
  orientation?: 'portrait' | 'landscape';
}

interface RenderResult {
  id: string;
  status: 'DONE' | 'QUEUED' | 'PROCESSING' | 'FAILED';
  pdf_base64?: string;
  pages?: number;
  file_size_kb?: number;
  duration_ms?: number;
}

class RenderHubClient {
  private client: AxiosInstance;

  constructor(private apiKey: string) {
    this.client = axios.create({
      baseURL: 'https://renderhub.com/api/v1',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json'
      },
      timeout: 30000
    });
  }

  async render(options: RenderOptions): Promise<RenderResult> {
    try {
      const response = await this.client.post<RenderResult>('/render', options);
      return response.data;
    } catch (error: any) {
      if (error.response) {
        throw new Error(`RenderHub API Error: ${error.response.data.message}`);
      }
      throw error;
    }
  }

  async savePDF(result: RenderResult, filename: string): Promise<void> {
    if (!result.pdf_base64) {
      throw new Error('PDF base64 não disponível');
    }

    const fs = await import('fs');
    const pdfBuffer = Buffer.from(result.pdf_base64, 'base64');
    fs.writeFileSync(filename, pdfBuffer);
  }
}

export default RenderHubClient;

Exemplo 7: Retry com Backoff Exponencial

// lib/retry.js
async function generatePDFWithRetry(renderFn, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await renderFn();
    } catch (error) {
      const isLastAttempt = attempt === maxRetries - 1;

      // Se rate limited, aguardar
      if (error.message.includes('Rate limit')) {
        if (isLastAttempt) throw error;

        const delay = Math.min(2 ** attempt * 1000, 30000); // Max 30s
        console.log(`Rate limited. Aguardando ${delay/1000}s...`);
        await sleep(delay);
        continue;
      }

      // Outros erros, não fazer retry
      throw error;
    }
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Uso
const client = new RenderHubClient();

const result = await generatePDFWithRetry(async () => {
  return await client.render({
    mode: 'CONVERT',
    input_type: 'html',
    data: '<h1>Test</h1>'
  });
});

Próximos Passos

On this page