G: 안녕하세요, 자동화와 효율성을 사랑하는 여러분! 🚀
n8n은 코딩 없이도 복잡한 워크플로우를 구축할 수 있게 해주는 강력한 자동화 도구입니다. 드래그 앤 드롭 방식으로 노드를 연결하기만 하면 되죠. 하지만 때로는 기본 노드만으로는 해결하기 어려운, 아주 특수한 데이터 처리나 커스텀 로직이 필요할 때가 있습니다.
이런 순간, n8n의 “비장의 무기”가 등장합니다. 바로 Code Node입니다! 🧙♂️ Code Node는 자바스크립트(JavaScript) 코드를 직접 작성하여 n8n 워크플로우 내에서 상상할 수 있는 거의 모든 데이터 조작과 로직 구현을 가능하게 해줍니다.
이 글에서는 n8n Code Node가 무엇인지부터 시작하여, 언제 사용해야 하는지, 핵심 문법과 유용한 함수들, 그리고 실제 활용 가능한 풍부한 예제들을 통해 여러분이 Code Node의 진정한 잠재력을 깨울 수 있도록 도와드리겠습니다.
자, 이제 n8n 워크플로우에 코딩의 마법을 불어넣어 볼 준비가 되셨나요? ✨
📚 1. n8n Code Node란 무엇인가요?
n8n Code Node는 워크플로우 내에서 JavaScript 코드를 실행할 수 있는 특별한 노드입니다. 일종의 작은 프로그래밍 샌드박스라고 생각하시면 됩니다. 다른 노드들이 제공하지 못하는 섬세한 제어나 복잡한 계산, 조건부 로직 등을 이 노드를 통해 구현할 수 있습니다.
핵심 특징:
- 유연성: JavaScript의 모든 기능을 활용하여 데이터를 변환, 필터링, 조합, 생성할 수 있습니다.
- 확장성: n8n 워크플로우의 한계를 뛰어넘어, 거의 모든 커스텀 로직을 추가할 수 있습니다.
- 데이터 접근: 이전 노드에서 넘어온 모든 데이터에 쉽게 접근하고 조작할 수 있습니다.
- 출력 제어: 가공된 데이터를 원하는 형식으로 다음 노드에 전달할 수 있습니다.
마치 여러분의 n8n 워크플로우에 “맞춤 제작된 만능칼” 하나를 추가하는 것과 같습니다. 🔪
🎯 2. Code Node, 언제 사용해야 할까요?
Code Node는 n8n의 기본 노드로는 해결하기 어렵거나 비효율적인 상황에서 빛을 발합니다. 다음은 Code Node를 사용하면 좋은 대표적인 시나리오들입니다.
2.1. 복잡한 데이터 변환 및 가공 🔄
- 시나리오: 여러 필드의 데이터를 조합하여 새로운 필드를 만들거나, 특정 규칙에 따라 문자열을 변환해야 할 때 (예: 이름 포맷 변경, 주소 파싱).
- 예시:
first_name
과last_name
필드를 합쳐full_name
필드를 생성하거나, 숫자를 특정 통화 형식으로 변환하는 경우.// 입력 데이터: { "firstName": "John", "lastName": "Doe" } // 출력 데이터: { "fullName": "John Doe", "email": "john.doe@example.com" } return items.map(item => { const firstName = item.json.firstName; const lastName = item.json.lastName; return { json: { fullName: `${firstName} ${lastName}`, email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}@example.com` } }; });
2.2. 커스텀 로직 및 조건부 처리 🚦
- 시나리오: 특정 조건(예: 값의 범위, 특정 문자열 포함 여부)에 따라 데이터의 흐름을 변경하거나, 다른 값을 할당해야 할 때.
- 예시: 주문 금액이 100달러 이상이면 ‘VIP’ 고객으로 분류하고 할인율을 적용하는 로직.
// 입력 데이터: { "orderAmount": 120 } // 출력 데이터: { "customerType": "VIP", "discountRate": 0.1 } return items.map(item => { const orderAmount = item.json.orderAmount; if (orderAmount >= 100) { return { json: { customerType: 'VIP', discountRate: 0.1 } }; } else { return { json: { customerType: 'Regular', discountRate: 0 } }; } });
2.3. 외부 API 호출 (고급 시나리오) 🔗
- 시나리오: n8n의 기본 HTTP Request 노드로 처리하기 어려운, 아주 복잡한 인증 방식이나 동적인 요청 본문/헤더 구성이 필요할 때. (⚠️ 일반적인 API 호출은 HTTP Request 노드가 훨씬 편리하고 권장됩니다!)
- 예시: HMAC 서명과 같은 복잡한 인증 방식을 사용하여 특정 API를 호출해야 하는 경우. (이 경우
crypto
모듈 등을 활용할 수 있습니다.)
2.4. 데이터 집계 및 요약 📊
- 시나리오: 여러 개의 입력 아이템(예: 주문 목록)에서 총합, 평균, 개수 등을 계산하여 하나의 요약 보고서를 만들 때.
-
예시: 여러 판매 트랜잭션의 총 판매액과 평균 판매액을 계산하는 경우.
// 입력 데이터: [{ "amount": 10 }, { "amount": 20 }, { "amount": 30 }] // 출력 데이터: { "totalAmount": 60, "averageAmount": 20 } const totalAmount = items.reduce((sum, item) => sum + item.json.amount, 0); const averageAmount = totalAmount / items.length; return [{ json: { totalAmount, averageAmount } }];
2.5. 특정 에러 처리 또는 로깅 🚨
- 시나리오: 특정 조건에서만 에러를 발생시키거나, 디버깅을 위해 중간 데이터를 정교하게 로깅해야 할 때.
- 예시: 특정 필드가 누락되었을 경우 워크플로우를 중단하고 에러 메시지를 생성하는 경우.
📖 3. Code Node 핵심 문법 및 활용법
Code Node는 JavaScript를 사용하지만, n8n 워크플로우 환경에 맞게 몇 가지 특별한 전역 변수와 함수를 제공합니다.
3.1. 기본 구조: 입력과 출력 📦
Code Node의 핵심은 items
배열을 입력받아, 새로운 items
배열을 반환하는 것입니다. 각 아이템은 보통 json
속성을 포함합니다.
// 기본 Code Node 구조
// 'items'는 이전 노드에서 전달받은 데이터의 배열입니다.
// 각 배열 요소는 { json: { ... }, binary: { ... } } 와 같은 형태입니다.
const outputItems = []; // 새로운 출력 아이템들을 담을 배열
for (const item of items) {
// item.json: 현재 아이템의 JSON 데이터
// item.binary: 현재 아이템의 바이너리 데이터 (파일 등)
// 🔽 여기에 데이터 처리 로직 작성 🔽
const originalData = item.json;
const processedData = {
// 예시: 원본 데이터에 새로운 필드 추가
...originalData,
newField: '새로운 값'
};
// 🔼 여기에 데이터 처리 로직 작성 🔼
outputItems.push({
json: processedData // 처리된 JSON 데이터를 출력 아이템에 추가
// binary: item.binary // 필요하다면 바이너리 데이터도 함께 전달
});
}
return outputItems; // 처리된 아이템 배열을 반환
가장 일반적인 형태는 입력 items
를 map
함수로 처리하여 새로운 items
배열을 반환하는 것입니다.
return items.map(item => {
// item.json에 접근하여 데이터 처리
const originalValue = item.json.someField;
const newValue = originalValue.toUpperCase();
return {
json: {
...item.json, // 원본 데이터를 유지하면서
processedField: newValue // 새로운 필드 추가 또는 기존 필드 수정
}
};
});
3.2. 입력 데이터 접근 💡
items
: 이전 노드에서 전달받은 모든 아이템의 배열입니다. Code Node의 메인 입력입니다.item
: (주로items.map(item => ...)
내부에서) 현재 처리 중인 단일 아이템을 나타냅니다.item.json
: 현재 아이템의 JSON 데이터입니다. (가장 많이 사용)item.binary
: 현재 아이템의 바이너리 데이터입니다. (예: 파일)
$json
: 현재 아이템의 JSON 데이터에 바로 접근하는 약식 문법입니다. (item.json
과 동일)$input.first().json
: 여러 아이템 중 첫 번째 아이템의 JSON 데이터에 접근합니다.
3.3. 출력 데이터 구성 📝
Code Node는 반드시 아이템들의 배열을 반환해야 합니다. 각 아이템은 최소한 json
속성을 가져야 합니다.
- 단일 아이템 출력:
return [{ json: { message: 'Hello, World!' } }];
- 여러 아이템 출력:
return [ { json: { id: 1, name: 'Alice' } }, { json: { id: 2, name: 'Bob' } } ];
- 입력 아이템을 변환하여 출력:
return items.map(item => { return { json: { ...item.json, status: 'processed' } }; });
3.4. 유용한 내장 함수 🛠️
n8n Code Node 환경에는 워크플로우와 상호작용할 수 있는 몇 가지 전역 함수가 있습니다.
getIndex()
: 현재 처리 중인 아이템의 인덱스를 반환합니다. (0부터 시작)return items.map((item, index) => { // map의 두 번째 인자로 index를 받는 것이 더 일반적입니다. return { json: { ...item.json, originalIndex: index } }; }); // 또는 Code Node 내부에서 // const index = getIndex();
getBinaryData(nodeName, itemIndex, binaryPropertyName)
: 특정 노드의 특정 아이템에서 바이너리 데이터를 가져옵니다.getWorkflowStaticData()
/getWorkflowData()
: 워크플로우 수준의 고정 데이터 또는 실행 중인 데이터를 가져옵니다. (고급)executeCommand(command, args)
: n8n 서버에서 쉘 명령어를 실행합니다. (🚨 경고: 보안상 매우 위험하므로 사용에 극도의 주의가 필요합니다!)console.log()
: 디버깅을 위해 Execution Log에 메시지를 출력할 수 있습니다.console.log('현재 아이템:', JSON.stringify(item.json));
🚀 4. Code Node 실전 예제
이제 몇 가지 실제 시나리오를 통해 Code Node의 활용법을 익혀봅시다.
4.1. 예제 1: JSON 데이터 필터링 및 변환 🧹
시나리오: 고객 주문 목록에서 ‘주문 완료’ 상태의 주문만 필터링하고, 각 주문의 총액을 계산하여 새로운 필드 totalPrice
를 추가합니다.
입력 데이터 (JSON):
[
{ "orderId": "ORD001", "items": [{ "name": "A", "price": 10, "qty": 1 }], "status": "pending" },
{ "orderId": "ORD002", "items": [{ "name": "B", "price": 20, "qty": 2 }, { "name": "C", "price": 5, "qty": 1 }], "status": "completed" },
{ "orderId": "ORD003", "items": [{ "name": "D", "price": 15, "qty": 3 }], "status": "completed" }
]
Code Node 코드:
const completedOrders = items.filter(item => item.json.status === 'completed');
const processedOrders = completedOrders.map(item => {
const originalOrder = item.json;
let totalPrice = 0;
if (originalOrder.items && Array.isArray(originalOrder.items)) {
totalPrice = originalOrder.items.reduce((sum, product) => {
return sum + (product.price * product.qty);
}, 0);
}
return {
json: {
orderId: originalOrder.orderId,
status: originalOrder.status,
items: originalOrder.items, // 원본 아이템 목록 유지
totalPrice: totalPrice, // 계산된 총액 추가
currency: 'USD' // 추가 정보
}
};
});
return processedOrders;
설명:
filter
함수를 사용하여status
가 ‘completed’인 주문만 걸러냅니다.- 필터링된 주문들에 대해
map
함수를 사용하여 각 주문을 순회합니다. reduce
함수를 사용하여 각 주문의items
배열을 순회하며price * qty
를 합산하여totalPrice
를 계산합니다.- 새로운
json
객체를 구성하여orderId
,status
,items
,totalPrice
,currency
필드를 포함하도록 반환합니다.
출력 데이터 (JSON):
[
{ "orderId": "ORD002", "items": [{ "name": "B", "price": 20, "qty": 2 }, { "name": "C", "price": 5, "qty": 1 }], "status": "completed", "totalPrice": 45, "currency": "USD" },
{ "orderId": "ORD003", "items": [{ "name": "D", "price": 15, "qty": 3 }], "status": "completed", "totalPrice": 45, "currency": "USD" }
]
4.2. 예제 2: 여러 아이템 합치기 (집계) 📈
시나리오: 월별 매출 데이터가 여러 아이템으로 들어왔을 때, 모든 월의 총 매출과 평균 매출을 계산하여 하나의 요약 아이템으로 만듭니다.
입력 데이터 (JSON):
[
{ "month": "Jan", "revenue": 10000 },
{ "month": "Feb", "revenue": 12000 },
{ "month": "Mar", "revenue": 9500 },
{ "month": "Apr", "revenue": 11000 }
]
Code Node 코드:
let totalRevenue = 0;
for (const item of items) {
totalRevenue += item.json.revenue;
}
const averageRevenue = totalRevenue / items.length;
return [{
json: {
reportTitle: "월별 매출 요약 보고서",
totalRevenue: totalRevenue,
averageRevenue: averageRevenue,
numberOfMonths: items.length,
reportDate: new Date().toISOString().split('T')[0] // 오늘 날짜 추가
}
}];
설명:
totalRevenue
변수를 초기화합니다.items
배열을 순회하며 각 아이템의revenue
값을totalRevenue
에 더합니다.totalRevenue
를items.length
로 나누어averageRevenue
를 계산합니다.- 이 모든 요약 정보를 담은 하나의 새로운 JSON 아이템을 반환합니다.
출력 데이터 (JSON):
[
{ "reportTitle": "월별 매출 요약 보고서", "totalRevenue": 42500, "averageRevenue": 10625, "numberOfMonths": 4, "reportDate": "2023-10-27" }
]
(날짜는 실행 시점에 따라 달라집니다)
4.3. 예제 3: 동적 조건부 로직 구현 (출력 분기) 🚦
시나리오: 사용자 이메일 주소를 분석하여, 특정 도메인(예: @example.com
)은 ‘내부 사용자’, 그 외에는 ‘외부 사용자’로 분류하고, 서로 다른 출력으로 보냅니다. (이후 노드에서 이 출력에 따라 다른 작업을 수행할 수 있도록)
입력 데이터 (JSON):
[
{ "name": "Alice", "email": "alice@example.com" },
{ "name": "Bob", "email": "bob@gmail.com" },
{ "name": "Charlie", "email": "charlie@example.com" }
]
Code Node 코드:
const internalUsers = [];
const externalUsers = [];
for (const item of items) {
const email = item.json.email;
if (email && email.endsWith('@example.com')) {
internalUsers.push({
json: { ...item.json, userType: 'internal' }
});
} else {
externalUsers.push({
json: { ...item.json, userType: 'external' }
});
}
}
// Code Node는 여러 개의 출력을 가질 수 있습니다.
// [output 0]: internalUsers
// [output 1]: externalUsers
return [internalUsers, externalUsers];
설명:
internalUsers
와externalUsers
라는 두 개의 빈 배열을 준비합니다.- 각 사용자의 이메일을 확인하여
@example.com
으로 끝나는지 검사합니다. - 조건에 따라 해당 사용자를 적절한 배열에 추가하고,
userType
필드를 추가합니다. - 마지막으로
[internalUsers, externalUsers]
와 같이 배열의 배열을 반환하여 n8n의 “Output 0”, “Output 1” 등으로 데이터를 분기합니다.
출력 데이터 (JSON):
- Output 0 (내부 사용자):
[ { "name": "Alice", "email": "alice@example.com", "userType": "internal" }, { "name": "Charlie", "email": "charlie@example.com", "userType": "internal" } ]
- Output 1 (외부 사용자):
[ { "name": "Bob", "email": "bob@gmail.com", "userType": "external" } ]
4.4. 예제 4: 간단한 외부 API 호출 (⚠️ 주의) 🌐
시나리오: (교육용 예제) Code Node 내에서 외부 API를 호출하고 응답을 처리합니다. 주의: 일반적인 API 호출은 n8n의 HTTP Request
노드를 사용하는 것이 훨씬 간편하고 권장됩니다! Code Node는 HTTP Request
노드로 불가능한 복잡한 인증/헤더/본문 로직이 필요할 때만 고려하세요.
Code Node 코드:
// n8n Code Node 환경에는 기본적으로 fetch API가 내장되어 있지 않을 수 있습니다.
// 일반적으로는 n8n에서 제공하는 'httpRequest' 유틸리티 함수나 'axios' 같은 라이브러리를 사용합니다.
// 여기서는 개념 설명을 위해 fetch를 사용하지만, 실제 n8n에서는 아래 주석 처리된 부분을 참고하세요.
// const axios = require('axios'); // Node.js 환경이므로 axios를 require할 수 있습니다.
// const response = await axios.get('https://jsonplaceholder.typicode.com/posts/1');
// n8n 내장 유틸리티 (실제 사용 예시)
// const httpRequest = require('n8n-nodes-base/dist/nodes/HttpRequest');
// const response = await httpRequest.request({ url: 'https://jsonplaceholder.typicode.com/posts/1', method: 'GET' });
// 일반적인 Node.js 환경처럼 fetch를 사용한다고 가정 (실제 n8n 환경에서는 제한적일 수 있음)
try {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1'); // 더미 API
const data = await response.json();
return [{
json: {
apiResponse: data,
status: 'success',
fetchedAt: new Date().toISOString()
}
}];
} catch (error) {
return [{
json: {
error: error.message,
status: 'failed',
fetchedAt: new Date().toISOString()
}
}];
}
설명:
fetch
API를 사용하여 외부 API (여기서는 JSONPlaceholder)에 GET 요청을 보냅니다.await
키워드를 사용하여 응답을 기다리고 JSON으로 파싱합니다.- 성공적으로 데이터를 가져오면 해당 데이터를 포함한
json
객체를 반환합니다. - 에러 발생 시
try...catch
블록을 통해 에러 메시지를 반환합니다.
출력 데이터 (JSON):
[
{ "apiResponse": { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }, "status": "success", "fetchedAt": "2023-10-27T10:30:00.000Z" }
]
(데이터와 시간은 달라질 수 있습니다)
⚠️ 5. Code Node 사용 시 주의사항 및 모범 사례
Code Node는 강력하지만, 오용하면 워크플로우를 복잡하게 만들거나 디버깅을 어렵게 할 수 있습니다.
- JavaScript 지식 필수: 기본적인 JavaScript 문법, 배열 및 객체 조작, 비동기 프로그래밍(Promise, async/await)에 대한 이해가 필요합니다. 🧑💻
- 디버깅의 어려움: Code Node 내부의 코드를 디버깅하는 것은 시각적인 노드보다 어렵습니다.
console.log()
를 적극적으로 활용하여 변수 값이나 중간 처리 결과를 Execution Log에서 확인하세요. - 성능 고려: 대량의 데이터를 처리할 때는 Code Node 내에서 복잡한 반복문이나 계산을 많이 수행하면 워크플로우 속도가 느려질 수 있습니다. 가능한 한 효율적인 알고리즘을 사용하세요.
- 재사용성: 복잡한 로직이라면, 함수로 분리하여 코드를 더 읽기 쉽고 관리하기 쉽게 만드세요.
- 에러 핸들링: 예상치 못한 상황(예: 데이터 누락, API 호출 실패)에 대비하여
try...catch
블록을 사용하여 에러를 안전하게 처리하는 습관을 들이세요. - 보안:
executeCommand()
와 같은 함수는 시스템 명령을 직접 실행하므로, 절대 신뢰할 수 없는 외부 입력값을 이 함수에 직접 전달하지 마세요. 보안 취약점으로 이어질 수 있습니다. 🔒 - 가독성: 주석을 충분히 달아 다른 사람(또는 미래의 자신)이 코드를 이해하기 쉽게 만드세요.
🎉 마무리하며
n8n Code Node는 여러분의 자동화 워크플로우를 한 차원 높게 끌어올릴 수 있는 강력한 도구입니다. 기본 노드로는 구현하기 어려운 복잡한 데이터 처리, 커스텀 로직, 섬세한 조건부 제어 등을 Code Node를 통해 구현할 수 있습니다.
처음에는 자바스크립트 코드를 작성하는 것이 어렵게 느껴질 수 있지만, 몇 가지 예제를 통해 연습하다 보면 금방 익숙해질 것입니다. 두려워하지 말고, 여러분의 아이디어를 코드로 자유롭게 표현해보세요! 🚀
이 가이드가 여러분이 n8n Code Node를 마스터하는 데 큰 도움이 되기를 바랍니다. 궁금한 점이나 더 복잡한 활용 시나리오가 있다면 언제든지 댓글로 남겨주세요!
다음 글에서 또 만나요! Happy Automating! 😊