CodeVetter · Repo Unpack · Data flow
n8n-io/n8n
Workflow JSON and credentials live in @n8n/db entities; credentials are encrypted through Cipher (AES-256-CBC/GCM with key-id prefixes). Incoming REST/webhook requests hit controllers/handlers, which delegate to services and repositories; executions run through WorkflowRunner -> WorkflowExecute, producing IRunExecutionData that lifecycle hooks persist via ExecutionPersistence into execution_entity/execution_data. In queue mode the main enqueues a Bull job consumed by a worker which runs the same engine and reports back over Redis pub/sub. The editor reads/writes the same REST API and receives live updates via Push (ws/sse).
- Core persisted state: WorkflowEntity (nodes/connections/settings/pinData), CredentialsEntity (encrypted data), WebhookEntity (path->workflow/node mapping), ExecutionEntity + ExecutionData, plus Settings rows with loadOnStartup that are re-applied to the convict config at boot.
- Request path: Express middleware (cookie-parser, browserId extraction, CSP) -> AuthStrategyRegistry (ApiKeyAuthStrategy then SessionCookieAuthStrategy) -> ControllerRegistry-activated routes -> services -> repositories -> TypeORM DataSource.
- Execution path: controller/manual trigger -> WorkflowRunner.run -> WorkflowExecute.run (PCancelable<IRun>) which iterates the execution stack, calls node execute functions via NodeExecuteFunctions contexts, and emits lifecycle hooks (workflowExecuteBefore/After, nodeExecuteBefore/After) that persist progress.
- Credential secrecy: Cipher.encryptV2 encrypts with the active key from EncryptionKeyProxy, emitting 'keyId:ciphertext' for rotated keys or bare ciphertext for the legacy no-prefix format; decryptV2 parses the prefix, looks up the DEK, unwraps it with the instance key (AES-256-GCM), then decrypts.
- Webhook data path: request -> WebhookRequestHandler.handleRequest (method check, CORS) -> webhookManager.executeWebhook -> WebhookService.findTriggerWebhooksByPath against webhook_entity -> workflow execution -> WebhookResponse (static/stream/no-response) -> headers via WebhookResponseHeaders.
- Queue-mode data path: main's ScalingService.setupQueue creates a Bull queue (maxStalledCount: 0) named by resolveQueueName(instanceType, poolName); workers run setupWorker(concurrency) -> JobProcessor executes the same WorkflowExecute and relays status through Redis pub/sub channels (command, worker-response, mcp-relay).
- Binary data flows through BinaryDataConfig/BinaryDataService with pluggable stores: filesystem managers under cli/binary-data plus S3/Azure object stores via @n8n/blob-storage (S3ByteStore, AzureByteStore).
- Frontend bootstrap: index.html is pre-split around HTML_NONCE_PLACEHOLDER and served per-request with a fresh CSP nonce by historyApiHandler; REST endpoint + Sentry config reach the client as base64 meta tags injected during generateStaticAssets.
- Node/credential type metadata: LoadNodesAndCredentials scans node_modules for n8n-nodes-base and @n8n/n8n-nodes-langchain, plus custom dirs and module-provided nodeLoaders; built types are written to staticCacheDir (/types/nodes.json, credentials.json, node-versions.json) served behind auth.
- Expression evaluation: workflow expressions are evaluated by @n8n/tournament (an AST interpreter, not eval) wrapped by expression-evaluator-proxy and hardened by expressionSandboxHooks in expression-sandboxing.ts.
- Event bus: MessageEventBus is initialized per-process (workerId payload on workers) and LogStreamingEventRelay forwards events; execution/workflow lifecycle also feeds TelemetryEventRelay, ActivityEventRelay, and workflow-failure notifications.