콘텐츠로 이동

05. 표현식으로 데이터 잇기

앞 노드가 만든 값을 뒤 노드가 쓰는 방법입니다. 이음새에서 가장 자주 쓰고, 가장 자주 틀리는 부분이니 이 문서만큼은 한 번 훑어두시길 권합니다.

문법은 하나입니다.

${무엇을.가져올지}

어디서 값을 가져올 수 있나

쓰는 법 가져오는 값
${nodes.<노드id>.response.body.<필드>} 앞 노드의 출력 — 가장 많이 씁니다
${nodes.<노드id>.request.data.<필드>} 그 노드가 받았던 입력
${input.<필드>} 엣지가 넘겨준 값 (조건 노드에서도 이 범위를 씁니다)
${secrets.<키>} 워크스페이스 시크릿 (API 키 등)
${vars.<키>} 워크스페이스 일반 변수
${connections.<제공사>.<필드>} 연결된 서비스의 토큰 등
${item} / ${item.<필드>} 반복 중인 현재 원소 (루프 안에서만)
${index} 반복 회차 번호 (0부터)
${loops.<LOOP_START id>.item} 바깥쪽 반복의 현재 원소 (반복이 겹칠 때)
${loops.<LOOP_START id>.index} 바깥쪽 반복의 회차 번호
${datasets.<제목>.count} 데이터셋 행 개수
${datasets.<제목>.latest} 데이터셋 최근 행 전체
${datasets.<제목>.latest.<필드>} 최근 행의 특정 필드

앞 노드 출력 가져오기 (핵심)

${nodes.fetch-orders.response.body.data}
       └─ 노드 id ─┘              └ 필드 ┘

노드 id에 하이픈이 있어도 됩니다. 중첩 필드는 점으로 계속 파고들면 됩니다.

${nodes.fetch.response.body.data.customer.name}      # 중첩 객체
${nodes.fetch.response.body.items.0.name}            # 배열 첫 번째 원소 — 숫자는 인덱스
${nodes.fetch.response.body.items.2.price}           # 세 번째 원소

시크릿·변수 가져오기

apiKey: "${secrets.OPENAI_API_KEY}"     # 민감한 값
uri: "${vars.API_BASE_URL}/orders"      # 일반 설정값

시크릿과 일반 변수는 서로 접근할 수 없습니다. ${vars.*}로 시크릿을 꺼낼 수 없고 그 반대도 안 됩니다. → 08. 변수와 시크릿


| raw — 구조를 그대로 넘기기

이음새에서 가장 흔한 실수가 여기서 나옵니다. 꼭 읽어주세요.

표현식 결과는 기본적으로 문자열로 바뀝니다. 숫자나 짧은 텍스트는 문제없지만, 배열이나 객체를 다음 노드에 넘길 때는 이게 문제가 됩니다.

# ❌ 배열이 문자열로 뭉개짐
data: "${nodes.fetch-orders.response.body.data}"
# → "[{id=1, amount=1000}, {id=2, amount=2000}]" 같은 이상한 문자열이 됩니다

# ✅ 배열 그대로 전달
data: "${nodes.fetch-orders.response.body.data | raw}"

| raw가 필요한 곳

상황
Transform 노드에 데이터 넘길 때 data: "${nodes.fetch.response.body.items \| raw}"
루프에 반복할 배열 넘길 때 items: "${nodes.fetch.response.body.data \| raw}"
객체 통째로 넘길 때 payload: "${nodes.build.response.body \| raw}"

| raw의 규칙

값 전체가 표현식 하나일 때만 씁니다. 문장 중간에 끼워 넣을 수 없습니다.

# ✅ 값 전체가 표현식 하나
data: "${nodes.fetch.response.body.items | raw}"

# ❌ 문장 안에 섞음 — 동작하지 않습니다
text: "주문 목록: ${nodes.fetch.response.body.items | raw}"

문장 안에 넣고 싶다면 그건 애초에 문자열이어야 하는 값입니다. | raw 없이 쓰세요.


| "기본값" — 값이 없을 때 대신 쓸 값

값이 null일 때 쓸 기본값을 정해둘 수 있습니다. 선택 입력 필드처럼 "있으면 쓰고 없으면 넘어가는" 자리에 씁니다.

text: "안녕하세요 ${input.name | \"고객\"}님"       # name 이 null 이면 → "안녕하세요 고객님"
body:
  memo: "${input.memo | \"\"}"                     # 빈 문자열로
  count: "${input.count | 0}"                      # 숫자로

쓸 수 있는 기본값은 "문자열" · 정수 · 실수 · true · false · null 입니다.

⚠️ 경로가 아예 없으면 fallback도 실패합니다

fallback은 값이 null일 때만 동작합니다. 경로 자체가 없으면 (오타·존재하지 않는 키) 기본값이 있어도 그대로 실패합니다.

${input.userId | "unknown"}    # userId 가 null 이면 → "unknown"
${input.usreId | "unknown"}    # 오타 — 그런 필드가 없으므로 실행 실패

일부러 이렇게 되어 있습니다. 오타를 기본값으로 덮어버리면 잘못된 값이 조용히 흘러가기 때문입니다.

⚠️ 0으로 시작하는 값은 따옴표로 감싸세요

기본값의 숫자는 숫자로 해석되므로 앞자리 0이 사라집니다. 전화번호·주문번호처럼 0으로 시작하는 값은 반드시 문자열로 적으세요.

${input.phone | "01012345678"}    # ✅ 그대로 01012345678
${input.phone | 01012345678}      # ❌ 숫자로 해석되어 1012345678

| raw 와 함께 쓰기

| 는 파이프로 이어 붙일 수 있고, raw가 앞입니다.

items: "${nodes.fetch.response.body.data | raw | null}"   # 값이 없으면 JSON null 로

| null 만 쓰면 문자열 자리에서는 빈 문자열이 됩니다. JSON null로 넘기려면 | raw | null 입니다.

기본값 문자열 안에는 } 를 넣을 수 없습니다 — 표현식을 닫는 문자이기 때문입니다. | 는 기본값 안에서는 구분자로 보지 않으므로 그대로 써도 됩니다.


객체를 입력으로 넘기는 두 가지 방법

방법 1 — 구조를 직접 적고 값만 표현식으로 (권장)

input:
  body:
    orderId: "${nodes.trigger.response.body.orderId}"
    amount: "${nodes.aggregate.response.body.총액}"
    status: "confirmed"

방법 2 — 통째로 | raw

input:
  body: "${nodes.build-payload.response.body | raw}"

객체를 | raw 없이 통째로 넘기는 것만 피하면 됩니다.


엣지 매핑 vs 직접 참조

같은 일을 하는 두 가지 스타일이 있습니다. 하나를 골라 일관되게 쓰세요.

스타일 A — 직접 참조 (간단)

nodes:
  - id: notify
    type: CALL
    integration: http_request
    input:
      body:
        orderId: "${nodes.trigger.response.body.orderId}"

edges:
  - from: trigger
    to: notify

노드가 "어디서 오는 값인지"까지 직접 압니다. 짧고 읽기 쉽습니다.

스타일 B — 엣지 매핑 + ${input.*}

nodes:
  - id: notify
    type: CALL
    integration: http_request
    input:
      body:
        orderId: "${input.orderId}"

edges:
  - from: trigger
    to: notify
    request:
      data:
        orderId: "${nodes.trigger.response.body.orderId}"

노드는 "orderId가 필요하다"만 알고, 그 값을 어디서 가져올지는 선이 압니다.

언제 B를 써야 하나

조건 노드(CONDITIONAL)로 이어질 때는 B가 필수입니다. 조건식은 ${input.*} 범위에서 평가되므로, 엣지로 값을 넘겨주지 않으면 조건이 아무것도 못 봅니다. → 06. 흐름 제어


자주 겪는 문제

표현식이 그대로 문자로 나와요

${...} 안의 경로가 틀렸을 때 실행이 ExpressionResolveException으로 실패합니다. 실행 이력에서 그 노드를 열고 바로 앞 노드의 실제 출력을 확인한 뒤 경로를 맞추세요.

표현식은 저장할 때 검사하지 않습니다. 실행할 때 비로소 확인됩니다. GUI에서 중간중간 저장할 수 있도록 일부러 이렇게 되어 있습니다.

JOINT 노드 값을 가져올 수 없어요

JOINT자기 출력이 없습니다. 합류만 시키는 노드입니다. JOINT 다음 노드에서는 JOINT 이전 노드를 직접 참조하세요.

# ❌ JOINT는 출력이 없음
${nodes.join.response.body.value}

# ✅ JOINT 앞 노드를 직접 참조
${nodes.fetch-a.response.body.value}

{{ }} 는 표현식이 아닙니다

{{key}}레시피 기능의 자리표시자입니다. 직접 작성하는 워크플로우 YAML에는 절대 넣지 마세요. 표현식은 언제나 중괄호 하나 + $${...} 입니다.

스케줄 트리거인데 windowStart가 없어요

lookback을 설정해야만 생깁니다. → 03. 트리거 설정하기


빠른 참조표

# 앞 노드 출력
${nodes.fetch.response.body.data}
${nodes.fetch.response.body.items.0.name}     # 배열 인덱스
${nodes.fetch.response.body.data | raw}       # 구조 보존

# 트리거
${nodes.trigger.response.body.orderId}        # 웹훅 본문
${nodes.trigger.response.body.windowStart}    # 스케줄 lookback

# 워크스페이스 자원
${secrets.MY_API_KEY}
${vars.BASE_URL}
${connections.slack.accessToken}
${datasets.처리이력.count}
${datasets.처리이력.latest.주문번호}

# 엣지가 넘긴 값 / 조건식
${input.orderId}

# 루프 안
${item}
${item.orderId}
${index}
${loops.each-order.item}                      # 겹친 루프의 바깥쪽 원소
${loops.each-order.index}

# 값이 없을 때 기본값
${input.name | "고객"}
${input.count | 0}
${nodes.fetch.response.body.data | raw | null}

다음06. 흐름 제어