Skip to main content

Project Overview

n8n is a workflow automation platform built with TypeScript, using a monorepo structure managed by pnpm workspaces. The platform consists of:
  • Node.js backend with Express and TypeORM
  • Vue 3 frontend with Vite and Pinia
  • Extensible node-based workflow engine
  • Multiple specialized packages for different concerns

Monorepo Structure

n8n uses pnpm workspaces with Turbo for build orchestration. This enables:
  • Isolated package development with shared dependencies
  • Automatic file-linking between interdependent modules
  • Parallel builds with smart caching
  • Independent versioning and testing

Workspace Configuration

The monorepo is defined in pnpm-workspace.yaml:
packages:
  - packages/*
  - packages/@n8n/*
  - packages/frontend/**
  - packages/extensions/**
  - packages/testing/**

Build Orchestration

Turbo (turbo.json) orchestrates builds with dependency awareness:
{
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "typecheck": {
      "dependsOn": ["^typecheck", "^build"]
    },
    "test": {
      "dependsOn": ["^build", "build"]
    }
  }
}
The ^ prefix means “run this task on all dependencies first.”

Package Structure

The monorepo is organized into logical packages with clear responsibilities:

Core Packages

// Core workflow interfaces and types
// Shared between frontend and backend
export interface IWorkflowBase {
  id?: string;
  name: string;
  active: boolean;
  nodes: INode[];
  connections: IConnections;
}

Frontend Packages

// Vue 3 frontend application
// Main workflow editor interface
import { createApp } from 'vue';
import { createPinia } from 'pinia';

const app = createApp(App);
app.use(createPinia());

Integration Packages

// Built-in nodes for integrations
// 300+ nodes for different services
export class HttpRequest implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'HTTP Request',
    name: 'httpRequest',
    // ...
  };
}

Shared Packages

PackagePurposeUsed By
@n8n/api-typesTypeScript interfaces for APIFrontend & Backend
@n8n/configCentralized configurationAll packages
@n8n/diDependency injection containerBackend services
@n8n/utilsShared utilitiesAll packages

Development Packages

PackagePurpose
@n8n/node-cliCLI to create new n8n nodes
packages/testing/playwrightE2E tests with Playwright
packages/testing/containersTestcontainers for local testing
packages/testing/janitorTest architecture enforcement

Directory Structure

n8n/
├── packages/
│   ├── @n8n/
│   │   ├── api-types/          # Shared TypeScript interfaces
│   │   ├── config/             # Configuration management
│   │   ├── di/                 # Dependency injection
│   │   ├── design-system/      # Vue components
│   │   ├── i18n/               # Translations
│   │   ├── nodes-langchain/    # AI nodes
│   │   └── ...
│   ├── cli/                     # Express server & API
│   │   ├── src/
│   │   │   ├── controllers/    # REST API controllers
│   │   │   ├── services/       # Business logic
│   │   │   ├── repositories/   # Database access
│   │   │   └── modules/        # Feature modules
│   │   └── bin/n8n             # CLI entry point
│   ├── core/                    # Workflow execution
│   ├── frontend/
│   │   ├── editor-ui/          # Vue workflow editor
│   │   └── @n8n/
│   │       ├── design-system/
│   │       ├── stores/         # Pinia stores
│   │       └── composables/    # Vue composables
│   ├── nodes-base/              # Built-in nodes
│   ├── workflow/                # Workflow types
│   └── testing/
│       ├── playwright/         # E2E tests
│       └── containers/         # Test infrastructure
├── docker/
│   └── images/                  # Dockerfiles
├── pnpm-workspace.yaml          # Workspace config
├── turbo.json                   # Build orchestration
└── package.json                 # Root scripts

Technology Stack

Backend

  • Runtime: Node.js 24+
  • Framework: Express
  • Language: TypeScript
  • ORM: TypeORM
  • Databases: SQLite, PostgreSQL, MySQL
  • Queue: Bull (Redis-based)
  • Testing: Jest (unit), Supertest (integration)

Frontend

  • Framework: Vue 3
  • Build Tool: Vite
  • State Management: Pinia
  • Router: Vue Router
  • UI Library: Element Plus + Custom Design System
  • Component Development: Storybook
  • Testing: Vitest (unit), Playwright (E2E)

Code Quality

  • Formatting: Biome
  • Linting: ESLint
  • Type Checking: TypeScript
  • Git Hooks: Lefthook

Architectural Patterns

1. Dependency Injection

n8n uses @n8n/di for IoC (Inversion of Control):
import { Service } from '@n8n/di';

@Service()
export class WorkflowService {
  constructor(
    private workflowRepository: WorkflowRepository,
    private executionService: ExecutionService,
  ) {}
}

2. Controller-Service-Repository

Backend follows MVC-like pattern:
// Controller: Handle HTTP requests
@RestController('/workflows')
export class WorkflowsController {
  constructor(private workflowService: WorkflowService) {}

  @Get('/:id')
  async getWorkflow(@Param('id') id: string) {
    return await this.workflowService.findById(id);
  }
}

// Service: Business logic
@Service()
export class WorkflowService {
  constructor(private workflowRepository: WorkflowRepository) {}

  async findById(id: string) {
    return await this.workflowRepository.findById(id);
  }
}

// Repository: Database access
@Service()
export class WorkflowRepository {
  async findById(id: string) {
    return await this.em.findOne(WorkflowEntity, id);
  }
}

3. Event-Driven Architecture

Internal event bus enables decoupled communication:
import { EventService } from '@n8n/event-service';

@Service()
export class WorkflowService {
  constructor(private eventService: EventService) {}

  async activateWorkflow(workflowId: string) {
    // Activate logic...
    await this.eventService.emit('workflow.activated', { workflowId });
  }
}

// Somewhere else
eventService.on('workflow.activated', async ({ workflowId }) => {
  // Handle activation
});

4. Context-Based Execution

Workflows execute in different contexts based on node types:
interface IExecuteContext {
  getNodeParameter(name: string): unknown;
  getInputData(): INodeExecutionData[];
  helpers: IExecuteHelpers;
}

class HttpRequest implements INodeType {
  async execute(this: IExecuteContext) {
    const url = this.getNodeParameter('url') as string;
    const data = this.getInputData();
    // Execute HTTP request
  }
}

5. Frontend State Management

Pinia stores manage application state:
import { defineStore } from 'pinia';

export const useWorkflowsStore = defineStore('workflows', () => {
  const workflows = ref<IWorkflowDb[]>([]);

  async function fetchWorkflows() {
    workflows.value = await api.getWorkflows();
  }

  return { workflows, fetchWorkflows };
});

6. Design System

Centralized components ensure consistency:
// @n8n/design-system
export const N8nButton = defineComponent({
  props: {
    type: { type: String, default: 'primary' },
    size: { type: String, default: 'medium' },
  },
  // Component logic
});

// Used in editor-ui
import { N8nButton } from '@n8n/design-system';

Data Flow

Workflow Execution Flow

Node Communication

Nodes communicate through connections:
interface IConnections {
  [nodeName: string]: {
    [outputType: string]: Array<
      Array<{
        node: string;
        type: string;
        index: number;
      }>
    >;
  };
}

// Example
const connections: IConnections = {
  'HTTP Request': {
    main: [[
      { node: 'Set', type: 'main', index: 0 },
    ]],
  },
};

Workflow Traversal

Use graph traversal utilities from n8n-workflow:
import {
  getParentNodes,
  getChildNodes,
  mapConnectionsByDestination,
} from 'n8n-workflow';

// workflow.connections is indexed by source node
// To find parent nodes, invert it first:
const connectionsByDestination = mapConnectionsByDestination(workflow.connections);
const parents = getParentNodes(connectionsByDestination, 'NodeName', 'main', 1);

// To find child nodes, use connections directly:
const children = getChildNodes(workflow.connections, 'NodeName', 'main', 1);

Module Organization

Backend Modules

Features are organized into modules with clear boundaries:
packages/cli/src/modules/
├── workflows/
│   ├── workflows.controller.ts
│   ├── workflows.service.ts
│   └── workflows.repository.ts
├── executions/
├── credentials/
├── users/
└── projects/

Frontend Modules

Vue components follow feature-based organization:
packages/frontend/editor-ui/src/
├── components/
│   ├── WorkflowCanvas/
│   ├── NodeSettings/
│   └── ExecutionView/
├── stores/
│   ├── workflows.store.ts
│   ├── ui.store.ts
│   └── settings.store.ts
├── views/
│   ├── WorkflowEditor.vue
│   └── ExecutionsList.vue
└── composables/

Development Workflows

Each package can be developed independently:
# Develop a specific package
cd packages/cli
pnpm dev

# Build a specific package
cd packages/workflow
pnpm build

# Test a specific package
cd packages/core
pnpm test

Key Conventions

TypeScript Best Practices:
  • Never use any type - use proper types or unknown
  • Avoid type casting with as - use type guards instead
  • Define shared interfaces in @n8n/api-types for FE/BE communication
Error Handling:
  • Use UnexpectedError, OperationalError, or UserError
  • Deprecated: ApplicationError class
Frontend Development:
  • All UI text must use i18n - add translations to @n8n/i18n
  • Use CSS variables - never hardcode spacing as px values
  • data-testid must be a single value (no spaces)

Next Steps