n8n Avanzato: Workflow con Subflow, Error Handling e Deploy Self-Hosted

Se hai già usato n8n per qualche workflow base, sai che le possibilità sono enormi. Ma il vero potere di n8n si sblocca quando padroneggi i sub-workflow, l’error handling robusto, e sai come fare il deploy self-hosted in produzione. Questa guida avanzata parte dal presupposto che tu abbia già visto il confronto Make vs n8n vs Zapier e abbia scelto n8n — ora impariamo a usarlo seriamente.

Trovi tutto il riferimento tecnico nei docs n8n ufficiali e le immagini Docker su Docker Hub.

Sub-Workflow con Execute Workflow Node

I sub-workflow (o workflow annidati) in n8n ti permettono di creare logiche riutilizzabili che puoi chiamare da qualsiasi altro workflow. Il nodo chiave è Execute Workflow.

Quando usarli:

  • Hai la stessa logica ripetuta in più workflow (es. normalizzazione dati cliente)
  • Vuoi separare workflow complessi in moduli testabili indipendentemente
  • Hai un workflow “orchestratore” che ne chiama altri specializzati

Ecco come passare dati tra workflow parent e child. Nel workflow parent:

// Nodo "Execute Workflow" — configurazione
{
  "resource": "workflow",
  "operation": "execute",
  "workflowId": "42",  // ID del sub-workflow
  "mode": "passDataToTrigger",
  "workflowInputs": {
    "customerId": "={{ $json.id }}",
    "email":      "={{ $json.email }}",
    "planType":   "={{ $json.subscription.plan }}"
  }
}

Nel workflow child, usa il nodo When Executed by Another Workflow come trigger. Riceve i dati come items normali:

// Nel nodo Code del sub-workflow
const customer = $input.first().json;
// customer.customerId, customer.email, customer.planType

const result = {
  normalized: {
    id:    customer.customerId.toString(),
    email: customer.email.toLowerCase().trim(),
    tier:  customer.planType === 'pro' ? 'premium' : 'basic',
  }
};
return [{ json: result }];

💡 Tip: Usa mode: "passDataToTrigger" quando vuoi passare items multipli al sub-workflow. Usa mode: "define" per mappare i campi esplicitamente. Sempre documenta lo schema di input del sub-workflow in un nodo Sticky Note.

Error Handling Robusto: Retry Logic e Notifiche

Un workflow in produzione deve gestire i fallimenti in modo elegante. n8n offre due livelli di error handling:

1. Error handling a livello di nodo — per ogni nodo puoi attivare “On Error” e scegliere tra:

  • Stop Workflow: interrompe tutto (default)
  • Continue: continua con l’output precedente
  • Continue (using error output): aggiunge il messaggio di errore all’item e continua

2. Error Trigger a livello di workflow — crea un workflow dedicato alla gestione errori:

// Struttura workflow "Error Handler Global"
// Trigger: Error Trigger
// Input disponibili automaticamente:
{
  "execution": {
    "id": "550",
    "url": "https://n8n.tuodominio.it/workflow/42/executions/550",
    "retryOf": null,
    "error": {
      "message": "Request failed with status code 429",
      "stack": "...",
      "cause": { "status": 429 }
    },
    "lastNodeExecuted": "HTTP Request"
  },
  "workflow": {
    "id": "42",
    "name": "Sync Clienti CRM"
  }
}

Nel workflow di error handling, aggiungi questi nodi:

// Nodo Code: Formatta messaggio di errore
const exec = $input.first().json.execution;
const wf   = $input.first().json.workflow;

const message = [
  `🚨 *Errore workflow n8n*`,
  `*Workflow:* ${wf.name} (ID: ${wf.id})`,
  `*Nodo:* ${exec.lastNodeExecuted}`,
  `*Errore:* ${exec.error.message}`,
  `*URL esecuzione:* ${exec.url}`,
  `*Timestamp:* ${new Date().toISOString()}`,
].join('
');

return [{ json: { text: message, url: exec.url } }];

Poi collega a un nodo Slack (o Send Email) per la notifica, e opzionalmente a un nodo HTTP Request per fare retry:

// Nodo HTTP Request: Trigger retry esecuzione
{
  "method": "POST",
  "url": "=https://n8n.tuodominio.it/api/v1/executions/{{ $json.executionId }}/retry",
  "authentication": "predefinedCredentialType",
  "nodeCredentialType": "n8nApi",
  "sendBody": true,
  "bodyParameters": {
    "loadWorkflow": true
  }
}

🔧 Pattern consigliato: Crea un solo workflow “Error Handler Globale” e collegalo come error workflow in Settings → Error Workflow di ogni workflow critico. Centralizza la logica di notifica in un unico punto.

Pattern Avanzati: SplitInBatches e Espressioni JS

Quando lavori con centinaia o migliaia di item, il nodo Loop Over Items (ex SplitInBatches) è fondamentale per evitare timeout e rate limiting:

// Espressioni avanzate nell'Expression Editor

// $jmespath: query potente su JSON annidati
{{ $jmespath($json, "orders[?status=='pending'].id") }}

// $node: accedi all'output di qualsiasi nodo precedente
{{ $node["HTTP Request"].json.data.users }}

// $items: tutti gli items del run corrente
{{ $items("SplitInBatches")[0].json }}

// Filtrare items con espressione
{{ $items().filter(item => item.json.active === true).length }}

// $runIndex: indice del batch corrente (in SplitInBatches)
// Utile per logging: "Batch {{ $runIndex + 1 }} di N"

Per il merge di branch multipli, usa il nodo Merge con mode combineAll o multiplex a seconda che tu voglia combinare o fare prodotto cartesiano degli item:

// Nodo Code: pattern avanzato con $jmespath
const orders = $input.all().map(item => item.json);

// Raggruppa per cliente usando reduce
const byCustomer = orders.reduce((acc, order) => {
  const key = order.customerId;
  if (!acc[key]) acc[key] = { customerId: key, orders: [], total: 0 };
  acc[key].orders.push(order.id);
  acc[key].total += order.amount;
  return acc;
}, {});

return Object.values(byCustomer).map(c => ({ json: c }));

Deploy Self-Hosted con Docker Compose

La configurazione di produzione consigliata usa n8n con PostgreSQL (non SQLite che è solo per sviluppo) e un reverse proxy Nginx o Caddy:

# docker-compose.yml
version: '3.8'

services:
  postgres:
    image: postgres:15-alpine
    restart: always
    environment:
      POSTGRES_DB:       n8n
      POSTGRES_USER:     n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

  n8n:
    image: n8nio/n8n:latest
    restart: always
    ports:
      - "5678:5678"
    environment:
      DB_TYPE:               postgresdb
      DB_POSTGRESDB_HOST:    postgres
      DB_POSTGRESDB_PORT:    5432
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER:    n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      N8N_HOST:              ${N8N_HOST}
      N8N_PORT:              5678
      N8N_PROTOCOL:          https
      WEBHOOK_URL:           https://${N8N_HOST}/
      N8N_ENCRYPTION_KEY:    ${N8N_ENCRYPTION_KEY}
      EXECUTIONS_MODE:       regular
      N8N_LOG_LEVEL:         info
      GENERIC_TIMEZONE:      Europe/Rome
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

Il file .env deve contenere almeno:

POSTGRES_PASSWORD=una_password_lunga_e_sicura
N8N_HOST=n8n.tuodominio.it
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)  # Genera una chiave sicura

Per avviare e aggiornare:

# Avvio iniziale
docker compose up -d

# Update n8n all'ultima versione
docker compose pull n8n
docker compose up -d n8n

# Backup workflow (esporta via API)
curl -H "X-N8N-API-KEY: $N8N_API_KEY"   https://n8n.tuodominio.it/api/v1/workflows   > backup_workflows_$(date +%Y%m%d).json

Per il monitoring, aggiungi un workflow n8n con trigger Schedule che ogni 5 minuti chiama il tuo endpoint di health check e notifica su Slack se non risponde:

// Health check URL n8n built-in
// GET /healthz → { "status": "ok" }

// In Cron workflow ogni 5min:
// HTTP Request → https://n8n.tuodominio.it/healthz
// IF: {{ $json.status !== 'ok' }} → Slack notify

🎯 Performance tip: Per workflow con molti item (1000+), aumenta EXECUTIONS_PROCESS a main e configura N8N_CONCURRENCY_PRODUCTION_LIMIT. Per workflow molto pesanti, considera la modalità Queue con Redis e worker separati.

FAQ: n8n Avanzato

Quanti sub-workflow annidati posso avere?

Non c’è un limite hard, ma considera che ogni livello di annidamento aggiunge overhead. In pratica, 2-3 livelli di sub-workflow sono più che sufficienti. Per logiche molto complesse, considera di usare un nodo Code con logica JavaScript diretta.

Come faccio il backup automatico dei workflow?

La soluzione migliore è usare la Source Control integrata in n8n (Enterprise) o l’API REST per esportare tutti i workflow in JSON e commitarli su Git. Crea un workflow n8n schedulato che esporta e committa ogni notte. Puoi anche usare il --export-all della CLI n8n.

SQLite o PostgreSQL per n8n self-hosted?

PostgreSQL sempre per produzione. SQLite va bene solo per test locali: non è thread-safe per esecuzioni concorrenti, non scala, e si può corrompere. La migrazione da SQLite a PostgreSQL una volta in produzione è dolorosa — parti subito con Postgres.

Come gestisco le credenziali in modo sicuro?

n8n cripta le credenziali nel database usando N8N_ENCRYPTION_KEY. Tieni questa chiave in una variabile d’ambiente sicura (mai nel codice o nel docker-compose committato). In alternativa, usa un secret manager (AWS Secrets Manager, HashiCorp Vault) e inietta le credenziali a runtime.

Posso usare le espressioni JavaScript in tutti i nodi?

Le espressioni {{ }} funzionano nella maggior parte dei campi di configurazione dei nodi. Per logica complessa, usa il nodo Code (JavaScript/Python). Le espressioni non supportano statement multi-linea — per quelli, usa sempre Code. Ricorda che $jmespath, $node, $items e $runIndex sono disponibili solo nelle espressioni, non nel nodo Code.

Condividi

Articoli Recenti

Categorie popolari