데이터의 흐름을 이해하자 — 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, 커스텀 앱 등 외부 서비스의 이벤트를 실시간으로 수신하자.