Practical n8n and Claude Code Workflows

This guide provides production-ready workflow patterns and real-world implementation examples for combining n8n with Claude Code. Each example includes complete implementations, best practices, and lessons learned from production deployments.

🎯 Who Is This Guide For?

This guide is for developers with intermediate-to-advanced experience using n8n, Docker, and Node.js. It assumes you are comfortable with concepts like webhooks, REST APIs, and database management. If you are new to these topics, we recommend starting with the n8n Integration Overview first.

πŸ—ΊοΈ System Architecture Overview

The workflows in this guide rely on several interconnected services. The diagram below illustrates how they work together. The n8n instance orchestrates the workflows, calling out to a dedicated, production-ready Claude API service that you will also create.

graph TD
    subgraph "External Triggers"
        A[GitHub Webhook] --> B(n8n);
        C[Jira Webhook] --> B;
    end

    subgraph "n8n Automation Platform"
        B -- Executes Workflow --> D{Custom Logic};
        D -- Queues Jobs --> E[Redis];
        D -- Stores State --> F[Postgres DB];
        D -- Makes API Call --> G(Custom Claude API Service);
    end

    subgraph "Custom API Service (Node.js)"
        G -- Securely Calls --> H[Anthropic API];
    end

    style G fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#9f9,stroke:#333,stroke-width:2px

🎯 Production Setup

Infrastructure Requirements

# docker-compose.production.yml
version: '3.8'
 
services:
  n8n:
    image: n8nio/n8n:latest
    restart: always
    ports:
      - "5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=${N8N_USER}
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - EXECUTIONS_PROCESS=queue
      - QUEUE_BULL_REDIS_HOST=redis
      - N8N_METRICS=true
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - postgres
      - redis
    networks:
      - n8n_network
 
  postgres:
    image: postgres:15
    restart: always
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - n8n_network
 
  redis:
    image: redis:7-alpine
    restart: always
    volumes:
      - redis_data:/data
    networks:
      - n8n_network
 
  claude-api:
    build: ./claude-api
    restart: always
    ports:
      - "3000:3000"
    environment:
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - NODE_ENV=production
      - LOG_LEVEL=info
      - RATE_LIMIT_REQUESTS=100
      - RATE_LIMIT_WINDOW=60000
    volumes:
      - ./projects:/app/projects
    networks:
      - n8n_network
 
volumes:
  n8n_data:
  postgres_data:
  redis_data:
 
networks:
  n8n_network:
    driver: bridge

Production Claude API Service

// claude-api/src/server.ts
import express from 'express';
import { Anthropic } from '@anthropic-ai/sdk';
import { RateLimiter } from './rateLimiter';
import { MetricsCollector } from './metrics';
import { SecurityMiddleware } from './security';
import { CacheManager } from './cache';
 
const app = express();
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
});
 
// Production middleware
app.use(express.json({ limit: '10mb' }));
app.use(SecurityMiddleware.validateRequest);
app.use(SecurityMiddleware.sanitizeInput);
app.use(RateLimiter.middleware);
app.use(MetricsCollector.middleware);
 
// Health check
app.get('/health', (req, res) => {
  res.json({ 
    status: 'healthy',
    version: process.env.npm_package_version,
    uptime: process.uptime()
  });
});
 
// Main Claude execution endpoint
app.post('/execute', async (req, res) => {
  const requestId = req.headers['x-request-id'] || crypto.randomUUID();
  const startTime = Date.now();
  
  try {
    const { prompt, context, mode, options = {} } = req.body;
    
    // Check cache
    const cacheKey = CacheManager.generateKey({ prompt, context, mode });
    const cached = await CacheManager.get(cacheKey);
    
    if (cached && !options.noCache) {
      MetricsCollector.recordCacheHit(requestId);
      return res.json({
        ...cached,
        cached: true,
        requestId
      });
    }
    
    // Prepare system prompt based on mode
    const systemPrompt = getSystemPrompt(mode);
    
    // Execute Claude request
    const completion = await anthropic.messages.create({
      model: options.model || 'claude-3-sonnet-20240229',
      max_tokens: options.maxTokens || 4000,
      temperature: options.temperature || 0.7,
      system: systemPrompt,
      messages: [{
        role: 'user',
        content: formatPrompt(prompt, context)
      }]
    });
    
    const response = {
      success: true,
      output: completion.content[0].text,
      mode,
      usage: {
        promptTokens: completion.usage.input_tokens,
        completionTokens: completion.usage.output_tokens,
        totalTokens: completion.usage.input_tokens + completion.usage.output_tokens
      },
      requestId,
      timestamp: new Date().toISOString()
    };
    
    // Cache successful responses
    await CacheManager.set(cacheKey, response, options.cacheTTL || 3600);
    
    // Record metrics
    MetricsCollector.recordRequest({
      requestId,
      mode,
      duration: Date.now() - startTime,
      tokens: response.usage.totalTokens,
      success: true
    });
    
    res.json(response);
  } catch (error) {
    MetricsCollector.recordError({
      requestId,
      error: error.message,
      mode: req.body.mode
    });
    
    res.status(500).json({
      success: false,
      error: error.message,
      requestId,
      timestamp: new Date().toISOString()
    });
  }
});
 
// System prompts for different modes
function getSystemPrompt(mode: string): string {
  const prompts = {
    'generate': 'You are Claude Code, an expert software engineer. Generate clean, efficient, and well-documented code.',
    'review': 'You are Claude Code, a senior code reviewer. Analyze code for bugs, security issues, and improvements.',
    'fix': 'You are Claude Code, a debugging expert. Identify and fix issues in the code.',
    'test': 'You are Claude Code, a testing specialist. Generate comprehensive test cases.',
    'document': 'You are Claude Code, a technical writer. Create clear and helpful documentation.',
    'refactor': 'You are Claude Code, a refactoring expert. Improve code structure while maintaining functionality.'
  };
  
  return prompts[mode] || prompts['generate'];
}
 
// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('SIGTERM received, shutting down gracefully');
  await MetricsCollector.flush();
  await CacheManager.close();
  process.exit(0);
});
 
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Claude API server running on port ${PORT}`);
});

πŸš€ Real-World Use Cases

1. Automated PR Review Pipeline

Scenario: Automatically review every pull request with specific focus areas based on the project.

{
  "name": "Smart PR Review Pipeline",
  "nodes": [
    {
      "name": "GitHub PR Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "github-pr",
        "responseMode": "responseNode"
      }
    },
    {
      "name": "Fetch PR Files",
      "type": "n8n-nodes-base.github",
      "parameters": {
        "resource": "file",
        "operation": "list",
        "owner": "={{ $json.repository.owner.login }}",
        "repository": "={{ $json.repository.name }}",
        "pullRequestNumber": "={{ $json.pull_request.number }}"
      }
    },
    {
      "name": "Categorize Changes",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": `
          const files = $input.all();
          const categories = {
            frontend: [],
            backend: [],
            database: [],
            config: [],
            tests: [],
            docs: []
          };
          
          files.forEach(file => {
            const path = file.json.filename;
            if (path.includes('frontend/') || path.match(/\\.(jsx?|tsx?|vue|css|scss)$/)) {
              categories.frontend.push(file);
            } else if (path.includes('backend/') || path.includes('api/')) {
              categories.backend.push(file);
            } else if (path.includes('migrations/') || path.match(/\\.sql$/)) {
              categories.database.push(file);
            } else if (path.match(/\\.(json|yml|yaml|env)$/)) {
              categories.config.push(file);
            } else if (path.includes('test') || path.match(/\\.spec\\.|test\\./)) {
              categories.tests.push(file);
            } else if (path.match(/\\.(md|rst|txt)$/)) {
              categories.docs.push(file);
            }
          });
          
          return Object.entries(categories)
            .filter(([_, files]) => files.length > 0)
            .map(([category, files]) => ({
              json: { category, files, count: files.length }
            }));
        `
      }
    },
    {
      "name": "Review Each Category",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "POST",
        "url": "http://claude-api:3000/execute",
        "sendBody": true,
        "bodyParameters": {
          "prompt": "Review these {{$json.category}} changes:\n\n{{$json.files.map(f => f.json.patch).join('\\n\\n')}}",
          "mode": "review",
          "context": {
            "category": "={{$json.category}}",
            "projectType": "={{$json.repository.language}}",
            "guidelines": "Focus on: security, performance, best practices, potential bugs"
          }
        }
      }
    },
    {
      "name": "Aggregate Reviews",
      "type": "n8n-nodes-base.aggregate",
      "parameters": {
        "fieldsToAggregate": [
          {
            "fieldToAggregate": "output",
            "renameField": true,
            "outputFieldName": "reviews"
          }
        ]
      }
    },
    {
      "name": "Format Final Review",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "POST",
        "url": "http://claude-api:3000/execute",
        "sendBody": true,
        "bodyParameters": {
          "prompt": "Consolidate these category-specific reviews into a comprehensive PR review",
          "mode": "document",
          "context": "={{$json}}",
          "options": {
            "temperature": 0.5
          }
        }
      }
    },
    {
      "name": "Post Review",
      "type": "n8n-nodes-base.github",
      "parameters": {
        "resource": "review",
        "operation": "create",
        "event": "COMMENT",
        "body": "={{$json.output}}"
      }
    }
  ]
}

2. Intelligent Bug Triage System

Scenario: Automatically triage incoming bugs, assign severity, and suggest fixes.

// Bug Triage Workflow with Claude
const bugTriageWorkflow = {
  name: "Intelligent Bug Triage",
  nodes: [
    {
      name: "Bug Report Trigger",
      type: "webhook",
      parameters: {
        path: "bug-report"
      }
    },
    {
      name: "Enrich Bug Data",
      type: "code",
      jsCode: `
        const bug = $input.first().json;
        
        // Fetch related data
        const relatedIssues = await searchSimilarIssues(bug.title);
        const affectedCode = await identifyAffectedCode(bug.description);
        const userImpact = calculateUserImpact(bug);
        
        return {
          json: {
            ...bug,
            enriched: {
              relatedIssues,
              affectedCode,
              userImpact,
              reporterHistory: await getReporterHistory(bug.reporter)
            }
          }
        };
      `
    },
    {
      name: "Analyze with Claude",
      type: "httpRequest",
      parameters: {
        url: "http://claude-api:3000/execute",
        method: "POST",
        body: {
          prompt: `Analyze this bug report and provide:
            1. Severity assessment (P0-P4)
            2. Root cause analysis
            3. Suggested fix approach
            4. Estimated effort
            5. Similar resolved issues
            
            Bug: {{$json.title}}
            Description: {{$json.description}}
            Stack Trace: {{$json.stackTrace}}
            Related Issues: {{$json.enriched.relatedIssues}}
            Affected Code: {{$json.enriched.affectedCode}}`,
          mode: "analyze",
          options: {
            model: "claude-3-opus-20240229",
            temperature: 0.3
          }
        }
      }
    },
    {
      name: "Create Action Plan",
      type: "code",
      jsCode: `
        const analysis = $input.first().json;
        const bug = $node["Bug Report Trigger"].json;
        
        const actionPlan = {
          severity: analysis.severity,
          assignee: selectAssignee(analysis.affectedCode, analysis.estimatedEffort),
          labels: generateLabels(analysis),
          milestone: selectMilestone(analysis.severity),
          actions: []
        };
        
        // P0/P1: Immediate action
        if (['P0', 'P1'].includes(analysis.severity)) {
          actionPlan.actions.push({
            type: 'page_oncall',
            team: determineTeam(analysis.affectedCode)
          });
          
          actionPlan.actions.push({
            type: 'create_incident',
            severity: analysis.severity
          });
        }
        
        // Generate fix if confidence is high
        if (analysis.confidence > 0.8 && analysis.suggestedFix) {
          actionPlan.actions.push({
            type: 'generate_fix',
            approach: analysis.suggestedFix
          });
        }
        
        return { json: actionPlan };
      `
    },
    {
      name: "Execute Actions",
      type: "split",
      parameters: {
        // Split into parallel execution paths
      }
    }
  ]
};

3. Self-Healing Infrastructure

Scenario: Detect and automatically fix common infrastructure issues.

// Self-Healing Infrastructure Workflow
const selfHealingWorkflow = {
  name: "Self-Healing Infrastructure",
  trigger: {
    type: "prometheus-alert",
    conditions: ["high_error_rate", "service_down", "performance_degradation"]
  },
  
  healingStrategies: {
    high_error_rate: async (alert) => {
      // Analyze logs to identify error patterns
      const logs = await fetchRecentLogs(alert.service, '5m');
      
      const analysis = await claude({
        prompt: `Analyze these error logs and suggest remediation:
          Service: ${alert.service}
          Error Rate: ${alert.errorRate}
          Logs: ${logs}
          
          Provide:
          1. Root cause
          2. Immediate fix
          3. Long-term solution`,
        mode: 'fix'
      });
      
      // Execute immediate fixes
      if (analysis.immediateFix.type === 'restart') {
        await restartService(alert.service);
      } else if (analysis.immediateFix.type === 'scale') {
        await scaleService(alert.service, analysis.immediateFix.replicas);
      } else if (analysis.immediateFix.type === 'rollback') {
        await rollbackDeployment(alert.service);
      }
      
      // Create ticket for long-term fix
      await createTicket({
        title: `Self-healing: ${alert.service} - ${analysis.rootCause}`,
        description: analysis.longTermSolution,
        priority: 'P2'
      });
    },
    
    service_down: async (alert) => {
      const diagnostics = await runDiagnostics(alert.service);
      
      const recovery = await claude({
        prompt: `Service ${alert.service} is down. Diagnostics: ${JSON.stringify(diagnostics)}
          Suggest recovery steps prioritized by likelihood of success.`,
        mode: 'fix'
      });
      
      for (const step of recovery.steps) {
        const result = await executeRecoveryStep(step);
        if (result.success) {
          await notifySuccess(alert, step);
          break;
        }
      }
    }
  }
};

4. Continuous Documentation Pipeline

Scenario: Keep documentation automatically updated as code changes.

Documentation Pipeline:
  Triggers:
    - Git push to main
    - PR merged
    - API schema change
    - New feature flag
    
  Process:
    1. Detect what changed
    2. Identify affected documentation
    3. Generate updates with Claude
    4. Create PR with changes
    5. Request human review for significant changes
// Documentation Update Workflow
const docUpdateWorkflow = {
  name: "Continuous Documentation",
  
  detectChanges: async (commit) => {
    const changes = {
      api: [],
      components: [],
      config: [],
      features: []
    };
    
    // Analyze commit
    const files = await getCommitFiles(commit);
    
    for (const file of files) {
      if (file.path.includes('/api/')) {
        changes.api.push(await analyzeAPIChange(file));
      } else if (file.path.includes('/components/')) {
        changes.components.push(await analyzeComponentChange(file));
      }
      // ... more categories
    }
    
    return changes;
  },
  
  updateDocumentation: async (changes) => {
    const updates = [];
    
    // API documentation
    if (changes.api.length > 0) {
      const apiDocs = await claude({
        prompt: `Update API documentation based on these changes:
          ${JSON.stringify(changes.api)}
          
          Include:
          - Endpoint changes
          - Parameter updates
          - Response format changes
          - Breaking changes callout
          - Migration guide if needed`,
        mode: 'document'
      });
      
      updates.push({
        file: 'docs/api/README.md',
        content: apiDocs,
        reviewRequired: changes.api.some(c => c.breaking)
      });
    }
    
    // Component documentation
    if (changes.components.length > 0) {
      for (const component of changes.components) {
        const componentDoc = await generateComponentDoc(component);
        updates.push({
          file: `docs/components/${component.name}.md`,
          content: componentDoc,
          reviewRequired: component.publicAPI
        });
      }
    }
    
    return updates;
  },
  
  generateComponentDoc: async (component) => {
    return await claude({
      prompt: `Generate documentation for this React component:
        ${component.code}
        
        Include:
        - Purpose and use cases
        - Props documentation with types
        - Usage examples
        - Common patterns
        - Accessibility notes`,
      mode: 'document',
      options: {
        temperature: 0.3
      }
    });
  }
};

5. Intelligent Release Management

Scenario: Automate release notes, changelog generation, and deployment decisions.

// Intelligent Release Management
interface ReleaseWorkflow {
  analyzeChanges(): Promise<ReleaseAnalysis>;
  generateReleaseNotes(): Promise<string>;
  assessRisk(): Promise<RiskAssessment>;
  planDeployment(): Promise<DeploymentPlan>;
}
 
const releaseManagement: ReleaseWorkflow = {
  async analyzeChanges() {
    const commits = await getCommitsSinceLastRelease();
    const prs = await getMergedPRs();
    const issues = await getClosedIssues();
    
    const analysis = await claude({
      prompt: `Analyze these changes for release:
        Commits: ${commits.length}
        PRs: ${prs.map(pr => `#${pr.number}: ${pr.title}`).join('\n')}
        Issues: ${issues.map(i => `#${i.number}: ${i.title}`).join('\n')}
        
        Categorize into:
        - Features
        - Improvements  
        - Bug fixes
        - Breaking changes
        - Security updates`,
      mode: 'analyze'
    });
    
    return analysis;
  },
  
  async generateReleaseNotes() {
    const analysis = await this.analyzeChanges();
    const previousNotes = await getPreviousReleaseNotes(3);
    
    const notes = await claude({
      prompt: `Generate release notes following our style:
        Version: ${await getNextVersion()}
        Changes: ${JSON.stringify(analysis)}
        
        Previous release notes for style reference:
        ${previousNotes}
        
        Include:
        - Highlights section
        - Detailed changes by category
        - Migration guide for breaking changes
        - Credits to contributors`,
      mode: 'document'
    });
    
    return notes;
  },
  
  async assessRisk() {
    const changes = await this.analyzeChanges();
    const testResults = await getTestResults();
    const perfMetrics = await getPerformanceMetrics();
    
    const risk = await claude({
      prompt: `Assess deployment risk:
        Changes: ${JSON.stringify(changes)}
        Test Coverage: ${testResults.coverage}%
        Failed Tests: ${testResults.failed}
        Performance Impact: ${perfMetrics.summary}
        
        Provide:
        - Risk score (1-10)
        - Key risks
        - Mitigation strategies
        - Recommended deployment approach`,
      mode: 'analyze'
    });
    
    return risk;
  },
  
  async planDeployment() {
    const risk = await this.assessRisk();
    
    let strategy;
    if (risk.score <= 3) {
      strategy = 'direct';
    } else if (risk.score <= 6) {
      strategy = 'canary';
    } else {
      strategy = 'blue-green';
    }
    
    const plan = await claude({
      prompt: `Create deployment plan:
        Strategy: ${strategy}
        Risks: ${JSON.stringify(risk.keyRisks)}
        
        Include:
        - Pre-deployment checklist
        - Deployment steps
        - Validation steps
        - Rollback plan
        - Communication plan`,
      mode: 'generate'
    });
    
    return {
      strategy,
      risk,
      plan,
      estimatedDuration: calculateDeploymentDuration(strategy)
    };
  }
};

πŸ“Š Performance Optimization

Caching Strategy

// Intelligent Caching for Claude Responses
class ClaudeCacheManager {
  private redis: Redis;
  private cacheStats = {
    hits: 0,
    misses: 0,
    evictions: 0
  };
  
  async get(key: string): Promise<CachedResponse | null> {
    const cached = await this.redis.get(key);
    if (!cached) {
      this.cacheStats.misses++;
      return null;
    }
    
    const data = JSON.parse(cached);
    
    // Validate cache freshness
    if (await this.isStale(data)) {
      await this.refresh(key, data);
      return await this.get(key);
    }
    
    this.cacheStats.hits++;
    return data;
  }
  
  async set(key: string, value: any, options: CacheOptions = {}) {
    const ttl = options.ttl || this.calculateTTL(value);
    
    const cacheData = {
      value,
      timestamp: Date.now(),
      metadata: {
        tokens: value.usage?.totalTokens,
        mode: value.mode,
        hash: this.contentHash(value)
      }
    };
    
    await this.redis.setex(key, ttl, JSON.stringify(cacheData));
  }
  
  private calculateTTL(response: any): number {
    // Dynamic TTL based on content type
    const baseTTL = {
      'generate': 3600,    // 1 hour
      'review': 7200,      // 2 hours
      'document': 86400,   // 24 hours
      'analyze': 1800      // 30 minutes
    };
    
    let ttl = baseTTL[response.mode] || 3600;
    
    // Adjust based on token usage (expensive responses cached longer)
    if (response.usage?.totalTokens > 2000) {
      ttl *= 2;
    }
    
    return ttl;
  }
  
  private async isStale(data: CachedResponse): boolean {
    // Check if underlying code has changed
    if (data.metadata.codeVersion) {
      const currentVersion = await this.getCodeVersion();
      if (currentVersion !== data.metadata.codeVersion) {
        return true;
      }
    }
    
    // Check if dependencies have updated
    if (data.metadata.dependencies) {
      const currentDeps = await this.getDependencyVersions();
      if (!this.depsMatch(currentDeps, data.metadata.dependencies)) {
        return true;
      }
    }
    
    return false;
  }
}

Batch Processing

// Batch Processing for Multiple Claude Requests
class ClaudeBatchProcessor {
  private queue: BatchRequest[] = [];
  private processing = false;
  
  async add(request: BatchRequest): Promise<BatchResponse> {
    const promise = new Promise<BatchResponse>((resolve, reject) => {
      this.queue.push({
        ...request,
        resolve,
        reject
      });
    });
    
    if (!this.processing) {
      this.processBatch();
    }
    
    return promise;
  }
  
  private async processBatch() {
    this.processing = true;
    
    while (this.queue.length > 0) {
      // Take up to 10 requests
      const batch = this.queue.splice(0, 10);
      
      if (batch.length === 1) {
        // Single request, process normally
        await this.processSingle(batch[0]);
      } else {
        // Multiple requests, optimize
        await this.processMultiple(batch);
      }
      
      // Rate limiting pause
      await this.rateLimitPause();
    }
    
    this.processing = false;
  }
  
  private async processMultiple(batch: BatchRequest[]) {
    // Group similar requests
    const grouped = this.groupBySimilarity(batch);
    
    for (const group of grouped) {
      if (group.length === 1) {
        await this.processSingle(group[0]);
        continue;
      }
      
      // Create combined prompt
      const combinedPrompt = this.createCombinedPrompt(group);
      
      try {
        const response = await claude({
          prompt: combinedPrompt,
          mode: 'batch',
          options: {
            maxTokens: 8000
          }
        });
        
        // Parse and distribute responses
        const parsed = this.parseMultiResponse(response, group);
        
        group.forEach((req, index) => {
          req.resolve(parsed[index]);
        });
      } catch (error) {
        group.forEach(req => req.reject(error));
      }
    }
  }
  
  private groupBySimilarity(batch: BatchRequest[]): BatchRequest[][] {
    const groups: Map<string, BatchRequest[]> = new Map();
    
    batch.forEach(request => {
      const key = `${request.mode}-${request.context?.type || 'default'}`;
      if (!groups.has(key)) {
        groups.set(key, []);
      }
      groups.get(key)!.push(request);
    });
    
    return Array.from(groups.values());
  }
}

πŸ”’ Security Best Practices

Input Validation

// Security Middleware for n8n Claude Integration
class SecurityMiddleware {
  static async validateRequest(req: Request, res: Response, next: NextFunction) {
    // Validate API key
    const apiKey = req.headers['x-api-key'];
    if (!apiKey || !await validateApiKey(apiKey)) {
      return res.status(401).json({ error: 'Invalid API key' });
    }
    
    // Validate request size
    if (req.headers['content-length'] > 1024 * 1024) { // 1MB limit
      return res.status(413).json({ error: 'Request too large' });
    }
    
    // Validate prompt content
    const { prompt } = req.body;
    if (!prompt || typeof prompt !== 'string') {
      return res.status(400).json({ error: 'Invalid prompt' });
    }
    
    // Check for injection attempts
    if (detectInjection(prompt)) {
      await logSecurityEvent('injection_attempt', req);
      return res.status(400).json({ error: 'Invalid characters in prompt' });
    }
    
    next();
  }
  
  static sanitizeInput(req: Request, res: Response, next: NextFunction) {
    // Sanitize all string inputs
    const sanitize = (obj: any): any => {
      if (typeof obj === 'string') {
        return obj
          .replace(/[<>]/g, '') // Remove potential HTML
          .substring(0, 50000); // Limit length
      } else if (Array.isArray(obj)) {
        return obj.map(sanitize);
      } else if (obj && typeof obj === 'object') {
        const sanitized: any = {};
        for (const [key, value] of Object.entries(obj)) {
          sanitized[key] = sanitize(value);
        }
        return sanitized;
      }
      return obj;
    };
    
    req.body = sanitize(req.body);
    next();
  }
}

Audit Logging

// Comprehensive Audit Logging
class AuditLogger {
  async logRequest(req: Request, response: any) {
    const auditEntry = {
      timestamp: new Date().toISOString(),
      requestId: req.headers['x-request-id'],
      userId: req.user?.id,
      action: 'claude_request',
      details: {
        mode: req.body.mode,
        promptLength: req.body.prompt?.length,
        contextType: req.body.context?.type,
        tokensUsed: response.usage?.totalTokens,
        success: response.success,
        duration: response.duration
      },
      ip: req.ip,
      userAgent: req.headers['user-agent']
    };
    
    // Store in time-series database
    await this.timeSeriesDB.write(auditEntry);
    
    // Alert on suspicious patterns
    if (await this.detectAnomalies(auditEntry)) {
      await this.alertSecurity(auditEntry);
    }
  }
  
  private async detectAnomalies(entry: AuditEntry): boolean {
    // Check for unusual patterns
    const recentRequests = await this.getRecentRequests(entry.userId, '5m');
    
    // Rapid request rate
    if (recentRequests.length > 50) {
      return true;
    }
    
    // Unusual token usage
    const avgTokens = this.calculateAverage(recentRequests.map(r => r.details.tokensUsed));
    if (entry.details.tokensUsed > avgTokens * 3) {
      return true;
    }
    
    // Unusual access patterns
    const unusualTime = this.isUnusualAccessTime(entry.timestamp, entry.userId);
    if (unusualTime) {
      return true;
    }
    
    return false;
  }
}

πŸ“ˆ Monitoring and Observability

Metrics Collection

// Comprehensive Metrics for n8n Claude Integration
class MetricsCollector {
  private prometheus = new PrometheusClient();
  
  constructor() {
    // Define metrics
    this.prometheus.register.registerMetric(
      new prometheus.Counter({
        name: 'claude_requests_total',
        help: 'Total number of Claude requests',
        labelNames: ['mode', 'status']
      })
    );
    
    this.prometheus.register.registerMetric(
      new prometheus.Histogram({
        name: 'claude_request_duration_seconds',
        help: 'Claude request duration in seconds',
        labelNames: ['mode'],
        buckets: [0.1, 0.5, 1, 2, 5, 10, 30]
      })
    );
    
    this.prometheus.register.registerMetric(
      new prometheus.Gauge({
        name: 'claude_tokens_used',
        help: 'Tokens used in Claude requests',
        labelNames: ['mode', 'type']
      })
    );
    
    this.prometheus.register.registerMetric(
      new prometheus.Gauge({
        name: 'n8n_workflow_success_rate',
        help: 'Success rate of n8n workflows using Claude',
        labelNames: ['workflow_name']
      })
    );
  }
  
  recordRequest(details: RequestDetails) {
    // Increment counter
    this.prometheus.counter('claude_requests_total')
      .labels(details.mode, details.success ? 'success' : 'failure')
      .inc();
    
    // Record duration
    this.prometheus.histogram('claude_request_duration_seconds')
      .labels(details.mode)
      .observe(details.duration / 1000);
    
    // Record token usage
    if (details.tokens) {
      this.prometheus.gauge('claude_tokens_used')
        .labels(details.mode, 'prompt')
        .set(details.tokens.prompt);
      
      this.prometheus.gauge('claude_tokens_used')
        .labels(details.mode, 'completion')
        .set(details.tokens.completion);
    }
  }
  
  async getMetrics(): Promise<string> {
    return this.prometheus.register.metrics();
  }
}

Distributed Tracing

// OpenTelemetry Tracing for n8n Claude Workflows
import { trace, context, SpanStatusCode } from '@opentelemetry/api';
 
const tracer = trace.getTracer('n8n-claude-integration');
 
class DistributedTracing {
  async traceWorkflow(workflowId: string, execution: WorkflowExecution) {
    const span = tracer.startSpan('workflow.execute', {
      attributes: {
        'workflow.id': workflowId,
        'workflow.name': execution.workflowName,
        'trigger.type': execution.triggerType
      }
    });
    
    try {
      // Trace each node execution
      for (const node of execution.nodes) {
        await this.traceNode(node, span);
      }
      
      span.setStatus({ code: SpanStatusCode.OK });
    } catch (error) {
      span.recordException(error);
      span.setStatus({ 
        code: SpanStatusCode.ERROR,
        message: error.message
      });
      throw error;
    } finally {
      span.end();
    }
  }
  
  private async traceNode(node: NodeExecution, parentSpan: Span) {
    const nodeSpan = tracer.startSpan(`node.${node.type}`, {
      parent: parentSpan,
      attributes: {
        'node.id': node.id,
        'node.name': node.name,
        'node.type': node.type
      }
    });
    
    context.with(trace.setSpan(context.active(), nodeSpan), async () => {
      try {
        if (node.type === 'claude') {
          await this.traceClaudeRequest(node, nodeSpan);
        } else {
          await node.execute();
        }
        
        nodeSpan.setStatus({ code: SpanStatusCode.OK });
      } catch (error) {
        nodeSpan.recordException(error);
        nodeSpan.setStatus({ 
          code: SpanStatusCode.ERROR,
          message: error.message
        });
        throw error;
      } finally {
        nodeSpan.end();
      }
    });
  }
  
  private async traceClaudeRequest(node: NodeExecution, parentSpan: Span) {
    const claudeSpan = tracer.startSpan('claude.request', {
      parent: parentSpan,
      attributes: {
        'claude.mode': node.parameters.mode,
        'claude.model': node.parameters.model || 'default'
      }
    });
    
    try {
      const start = Date.now();
      const response = await executeClaudeRequest(node.parameters);
      
      claudeSpan.setAttributes({
        'claude.tokens.prompt': response.usage.promptTokens,
        'claude.tokens.completion': response.usage.completionTokens,
        'claude.duration': Date.now() - start
      });
      
      return response;
    } finally {
      claudeSpan.end();
    }
  }
}

πŸš€ Deployment Strategies

Blue-Green Deployment

# Blue-Green Deployment for n8n Claude Integration
version: '3.8'
 
services:
  # Blue Environment (Current Production)
  n8n-blue:
    image: n8nio/n8n:${BLUE_VERSION}
    networks:
      - blue-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n-blue.rule=Host(`n8n.production.com`) && Headers(`X-Version`, `blue`)"
  
  claude-api-blue:
    image: claude-api:${BLUE_VERSION}
    networks:
      - blue-network
  
  # Green Environment (New Version)
  n8n-green:
    image: n8nio/n8n:${GREEN_VERSION}
    networks:
      - green-network
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n-green.rule=Host(`n8n.production.com`) && Headers(`X-Version`, `green`)"
  
  claude-api-green:
    image: claude-api:${GREEN_VERSION}
    networks:
      - green-network
  
  # Load Balancer
  traefik:
    image: traefik:v2.10
    command:
      - "--providers.docker=true"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - blue-network
      - green-network
 
networks:
  blue-network:
  green-network:

Canary Deployment

// Canary Deployment Controller
class CanaryDeployment {
  private trafficSplitPercentage = 5; // Start with 5% traffic
  
  async deploy(newVersion: string) {
    // Deploy canary instance
    await this.deployCanaryInstance(newVersion);
    
    // Monitor metrics
    const monitoring = setInterval(async () => {
      const metrics = await this.collectCanaryMetrics();
      
      if (this.isHealthy(metrics)) {
        // Gradually increase traffic
        this.trafficSplitPercentage = Math.min(100, this.trafficSplitPercentage + 10);
        await this.updateTrafficSplit(this.trafficSplitPercentage);
        
        if (this.trafficSplitPercentage === 100) {
          // Full deployment successful
          clearInterval(monitoring);
          await this.promoteCanary();
        }
      } else {
        // Rollback
        clearInterval(monitoring);
        await this.rollback();
      }
    }, 60000); // Check every minute
  }
  
  private isHealthy(metrics: CanaryMetrics): boolean {
    return (
      metrics.errorRate < 0.01 && // Less than 1% errors
      metrics.responseTime < 2000 && // Less than 2s response time
      metrics.successRate > 0.99 // Greater than 99% success
    );
  }
}

πŸ“š Lessons Learned

Common Pitfalls and Solutions

  1. Token Limit Exceeded

    • Solution: Implement context windowing
    • Use summarization for long inputs
    • Split large operations into chunks
  2. Rate Limiting Issues

    • Solution: Implement exponential backoff
    • Use request queuing
    • Cache responses aggressively
  3. Inconsistent Responses

    • Solution: Use lower temperature for consistency
    • Implement response validation
    • Add retry logic with rephrasing
  4. Cost Management

    • Solution: Implement usage quotas
    • Use cheaper models for simple tasks
    • Aggressive caching strategy
  5. Debugging Complex Workflows

    • Solution: Comprehensive logging
    • Distributed tracing
    • Workflow visualization tools

πŸ”— Resources


Based on real production deployments and community feedback Last Updated: 2025-07-21