데이터의 흐름을 이해하자 — Expression, 변수, JSON 경로

데이터의 흐름을 이해하자 — Expression, 변수, JSON 경로

n8n을 쓰다 보면 어느 순간 벽을 만난다. "이전 노드의 데이터를 어떻게 가져오지?" — 이 질문의 답이 Expression이다. Expression을 마스터하면 n8n의 진정한 힘이 열린다.

n8n의 데이터 모델

n8n에서 노드 간에 전달되는 데이터는 아이템(Item) 의 배열이다. 이것을 이해하는 것이 모든 것의 출발점이다.

아이템이란?

[
  { "json": { "name": "홍길동", "age": 30 } },
  { "json": { "name": "김철수", "age": 25 } },
  { "json": { "name": "이영희", "age": 35 } }
]

핵심 규칙:

개념 설명
아이템(Item) 데이터의 최소 단위. 하나의 레코드, 하나의 행
json 아이템의 텍스트 데이터를 담는 객체
binary 아이템의 파일 데이터(이미지, PDF 등)를 담는 객체
배열 노드는 항상 아이템의 배열을 출력한다

시각적으로 비유하면:

[노드 A] ──▶ [노드 B]

  ┌──────────────────┐
  │ Item 0: {...}    │  ← 상자 1
  │ Item 1: {...}    │  ← 상자 2  
  │ Item 2: {...}    │  ← 상자 3
  └──────────────────┘

  컨베이어 벨트 위의 상자들

노드 B는 이 상자들을 하나씩 차례로 처리한다. 3개의 아이템이 들어오면 노드 B는 3번 실행된다.

💡 핵심: n8n의 모든 노드는 "아이템 배열을 받아서 → 처리하고 → 아이템 배열을 내보낸다"는 동일한 패턴으로 동작한다.


Expression 기초

Expression은 노드 설정에서 동적 값을 참조하는 문법이다. 이중 중괄호 {{ }} 안에 JavaScript 표현식을 작성한다.

Expression 입력 방법

노드 설정 필드에서 고정 값Expression 두 가지 모드가 있다.

고정 값 모드:  "안녕하세요"        (항상 같은 값)
Expression 모드: {{ $json.name }}  (데이터에 따라 변하는 값)

Expression 모드로 전환하려면: 1. 입력 필드 우측의 = 아이콘을 클릭하거나 2. 값을 입력하기 시작할 때 {{를 타이핑하면 자동 전환


$json — 현재 아이템 참조

가장 많이 쓰는 변수다. 현재 처리 중인 아이템의 json 데이터를 참조한다.

// 이전 노드의 출력이 이렇다면:
{
  "name": "홍길동",
  "email": "[email protected]",
  "address": {
    "city": "서울",
    "zip": "06001"
  },
  "tags": ["개발자", "리더"]
}
Expression 결과 설명
{{ $json.name }} 홍길동 최상위 필드
{{ $json.email }} [email protected] 최상위 필드
{{ $json.address.city }} 서울 중첩 객체의 필드
{{ $json.address.zip }} 06001 중첩 객체의 필드
{{ $json.tags[0] }} 개발자 배열의 첫 번째 요소
{{ $json.tags[1] }} 리더 배열의 두 번째 요소
{{ $json.tags.length }} 2 배열의 길이

필드명에 특수문자가 있을 때

필드명에 공백이나 하이픈이 있으면 대괄호 표기법을 사용한다:

{{ $json["first name"] }}      // 공백이 있는 필드명
{{ $json["content-type"] }}    // 하이픈이 있는 필드명
{{ $json["2024-data"] }}       // 숫자로 시작하는 필드명

다른 노드의 데이터 참조

기본적으로 $json바로 이전 노드의 출력을 참조한다. 하지만 2개 전, 3개 전 노드의 데이터가 필요할 때도 있다.

$('노드이름') — 특정 노드 참조

// "HTTP Request" 노드의 출력 데이터 참조
{{ $('HTTP Request').item.json.name }}

// "Edit Fields" 노드의 출력 참조
{{ $('Edit Fields').item.json.도시 }}

사용 시나리오

[Webhook] ──▶ [HTTP Request] ──▶ [Edit Fields] ──▶ [Slack]
   (A)              (B)              (C)           (D)

Slack 노드(D)에서 각 노드의 데이터를 참조하려면:

참조 대상 Expression
C(바로 이전) {{ $json.필드명 }}
B {{ $('HTTP Request').item.json.필드명 }}
A {{ $('Webhook').item.json.body.필드명 }}

💡 팁: 노드 이름을 알기 쉽게 바꿔두면 Expression 작성이 훨씬 편해진다. "HTTP Request"보다 "날씨 API 호출"이 훨씬 명확하다.


내장 변수 총정리

n8n은 $json 외에도 여러 내장 변수를 제공한다.

데이터 참조 변수

변수 설명 예시
$json 현재 아이템의 json 데이터 {{ $json.name }}
$binary 현재 아이템의 바이너리 데이터 {{ $binary.data.fileName }}
$input 현재 노드의 입력 데이터 전체 {{ $input.all() }}
$('Node') 특정 노드의 출력 참조 {{ $('MyNode').item.json.x }}

워크플로우 정보 변수

변수 설명 예시 결과
$workflow.id 현재 워크플로우 ID "abc123"
$workflow.name 현재 워크플로우 이름 "날씨 알림"
$execution.id 현재 실행 ID "exec_456"
$execution.resumeUrl Wait 노드 재개 URL "https://..."

환경 변수

변수 설명 예시
$env 시스템 환경 변수 {{ $env.API_KEY }}
$vars n8n 전역 변수 {{ $vars.slackChannel }}

유틸리티 변수

변수 설명 예시 결과
$now 현재 날짜/시간 (Luxon DateTime) 2026-04-12T08:00:00.000+09:00
$today 오늘 날짜 (시간 0시) 2026-04-12T00:00:00.000+09:00
$runIndex 현재 아이템의 인덱스 (0부터) 0, 1, 2
$itemIndex Loop 내 현재 반복 인덱스 0, 1, 2
$position 현재 노드의 실행 위치 거의 사용 안 함

자주 쓰는 Expression 패턴

실전에서 매일 사용하게 될 패턴을 모았다.

문자열 조합

// 문자열 연결
{{ "안녕하세요, " + $json.name + "님!" }}
// 결과: "안녕하세요, 홍길동님!"

// 템플릿 리터럴 (백틱 사용)
{{ `${$json.name}님의 이메일: ${$json.email}` }}
// 결과: "홍길동님의 이메일: [email protected]"

조건부 값

// 삼항 연산자
{{ $json.age >= 18 ? "성인" : "미성년자" }}

// null/undefined 대비 (기본값 설정)
{{ $json.nickname ?? "이름 없음" }}
{{ $json.phone || "전화번호 미등록" }}

날짜 처리

n8n은 Luxon 라이브러리를 내장하고 있다.

// 현재 날짜/시간
{{ $now.toFormat('yyyy-MM-dd HH:mm') }}
// 결과: "2026-04-12 08:00"

// 한국 형식
{{ $now.toFormat('yyyy년 MM월 dd일') }}
// 결과: "2026년 04월 12일"

// N일 전/후
{{ $now.minus({days: 7}).toFormat('yyyy-MM-dd') }}
// 결과: "2026-04-05" (7일 전)

{{ $now.plus({hours: 3}).toFormat('HH:mm') }}
// 결과: "11:00" (3시간 후)

// ISO 문자열을 DateTime으로 변환
{{ DateTime.fromISO($json.created_at).toFormat('yyyy-MM-dd') }}

숫자 처리

// 반올림
{{ Math.round($json.price * 1.1) }}

// 소수점 자릿수 제한
{{ $json.ratio.toFixed(2) }}

// 천 단위 콤마
{{ $json.amount.toLocaleString('ko-KR') }}
// 결과: "1,234,567"

배열 처리

// 배열 → 문자열 (쉼표 구분)
{{ $json.tags.join(", ") }}
// 결과: "개발자, 리더"

// 배열 길이
{{ $json.items.length }}

// 배열 필터
{{ $json.users.filter(u => u.active === true).length }}
// 결과: 활성 사용자 수

$input — 고급 데이터 접근

$input은 현재 노드에 들어온 모든 아이템에 접근할 수 있다.

주요 메서드

메서드 설명 반환
$input.first() 첫 번째 아이템 단일 아이템
$input.last() 마지막 아이템 단일 아이템
$input.all() 모든 아이템 아이템 배열
$input.item 현재 처리 중인 아이템 단일 아이템

사용 예시

// 첫 번째 아이템의 이름
{{ $input.first().json.name }}

// 마지막 아이템의 이메일
{{ $input.last().json.email }}

// 전체 아이템 수
{{ $input.all().length }}

// 모든 아이템의 이름을 배열로
{{ $input.all().map(item => item.json.name) }}

$if — 인라인 조건

n8n은 $if 헬퍼 함수를 제공한다. IF 노드를 사용하지 않고도 간단한 조건 분기가 가능하다.

{{ $if($json.status === "active", "활성", "비활성") }}

// 중첩 조건
{{ $if($json.score >= 90, "A",
    $if($json.score >= 80, "B",
      $if($json.score >= 70, "C", "F"))) }}

💡 주의: 조건이 2개 이상 복잡해지면 $if 대신 IF 노드나 Switch 노드를 사용하는 것이 가독성에 좋다.


Expression 디버깅 팁

Expression이 동작하지 않을 때 디버깅하는 방법을 알아두자.

1. Expression Editor 활용

필드에서 Expression을 작성할 때, 하단에 미리보기(Preview) 가 실시간으로 표시된다.

Expression: {{ $json.name + " (" + $json.email + ")" }}
Preview:    홍길동 ([email protected])

미리보기가 [ERROR]로 표시되면 문법 오류가 있다는 뜻이다.

2. 이전 노드 데이터 확인

Expression이 undefined를 반환한다면, 참조하려는 데이터가 없다는 것이다.

해결법: 1. 이전 노드를 클릭 2. Output 탭에서 JSON 뷰로 전환 3. 실제 데이터 구조를 확인 4. 정확한 경로로 Expression 수정

3. 흔한 실수 5가지

실수 원인 해결
$json.Weather (대소문자) API 응답은 weather(소문자) JSON 뷰에서 정확한 키 확인
$json.data.0.name 배열 접근은 .0이 아님 $json.data[0].name 사용
$json.user name 공백이 있는 키 $json["user name"] 사용
{{ $json.name }}입니다 중괄호 밖의 텍스트 {{ $json.name + "입니다" }}
$json 빈 결과 이전 노드 미실행 이전 노드부터 순서대로 실행

4. typeof로 타입 확인

데이터 타입이 의심되면 typeof를 활용하자:

{{ typeof $json.age }}
// "number" 또는 "string"

{{ typeof $json.items }}
// "object" (배열도 object로 표시됨)

{{ Array.isArray($json.items) }}
// true 또는 false

데이터 흐름 시각화

전체 흐름을 정리하면 이렇다:

[Trigger]
    │
    │  아이템 배열 출력
    │  [{ json: {...} }, { json: {...} }]
    ▼
[Node A]
    │  각 아이템을 처리
    │  {{ $json.field }} 로 현재 아이템 참조
    │  {{ $('Trigger').item.json.field }} 로 이전 노드 참조
    ▼
[Node B]  
    │  Node A의 출력이 이제 $json이 됨
    │  
    ▼
[Node C]
    ...

핵심 원칙: 1. 데이터는 왼쪽에서 오른쪽으로 흐른다 2. 각 노드는 이전 노드의 출력 전체를 입력으로 받는다 3. $json은 항상 바로 직전 노드의 데이터를 가리킨다 4. 더 이전 노드가 필요하면 $('노드이름')을 사용한다


📝 정리

  • [x] 아이템(Item): n8n 데이터의 최소 단위. json(텍스트)과 binary(파일)로 구성
  • [x] $json: 바로 이전 노드의 현재 아이템 데이터 참조
  • [x] $('Node'): 특정 이름의 노드 출력 참조
  • [x] 내장 변수: $now(현재시간), $env(환경변수), $input(입력 데이터)
  • [x] Luxon: 날짜/시간 처리 내장 라이브러리. $now.toFormat('yyyy-MM-dd')
  • [x] 디버깅: Expression Editor 미리보기 활용, JSON 뷰에서 데이터 구조 확인

다음 편 예고

6편: Webhook 노드 — 외부 세계와 연결하는 문

지금까지는 n8n이 스스로 시작하는 워크플로우를 만들었다. 이제 외부에서 n8n을 호출하는 법을 배운다. Webhook 노드로 GitHub, Stripe, 커스텀 앱 등 외부 서비스의 이벤트를 실시간으로 수신하자.