G: 안녕하세요! 자동화와 워크플로우를 사랑하는 여러분! 🙋♂️ n8n은 강력한 오픈소스 자동화 도구로, 다양한 서비스와 연동하여 복잡한 워크플로우를 쉽고 빠르게 구축할 수 있게 해줍니다. 하지만 때로는 내장 노드나 커뮤니티 노드만으로는 충족되지 않는 특별한 요구사항이 생기곤 합니다. 바로 이럴 때, 커스텀 노드의 힘이 발휘되죠! 💪
“커스텀 노드? 왠지 어려워 보여요… 삽질 많이 해야 할 것 같아서 엄두가 안 나요 😩” 라고 생각하셨다면, 이 글이 바로 여러분을 위한 비법서입니다! 오늘은 제가 직접 n8n 커스텀 노드를 만들면서 겪었던 삽질을 줄이고, 효율적으로 개발할 수 있었던 노하우를 아낌없이 공개합니다. 자, 그럼 시작해볼까요? 🎉
1. 왜 커스텀 노드가 필요한가요? 🤔
n8n은 이미 수많은 노드를 제공하지만, 다음과 같은 경우에는 커스텀 노드가 빛을 발합니다.
- 특정 API 연동: 사내 시스템의 내부 API, 아직 n8n에 노드가 없는 신규 서비스의 API 등 아주 특수한 API를 연동해야 할 때 유용합니다.
- 예시: “우리 회사의 재고 관리 시스템 API에 직접 접속해서 데이터를 가져와야 해요!”
- 복잡한 비즈니스 로직: 여러 데이터를 조합하거나, 복잡한 조건에 따라 데이터를 가공해야 할 때, Expression이나 Function 노드만으로는 한계가 있을 수 있습니다. 이때 Node.js의 강력한 기능들을 활용하여 노드 내부에 로직을 구현할 수 있습니다.
- 예시: “들어온 고객 데이터에서 특정 패턴을 분석하고, 여러 변수를 조합해서 최종 점수를 산출해야 해요!”
- 재사용 가능한 모듈화: 특정 작업을 여러 워크플로우에서 반복적으로 사용해야 할 때, 커스텀 노드로 만들어두면 매우 효율적입니다.
- 예시: “모든 워크플로우에서 공통적으로 사용되는 데이터 정제 과정이 있는데, 이걸 한 번에 처리하는 노드가 있었으면 좋겠어요!”
- 외부 라이브러리 활용: n8n 환경에서 특정 외부 Node.js 라이브러리를 사용해야 할 때, 커스텀 노드 내부에 해당 라이브러리를 포함시켜 기능을 확장할 수 있습니다.
- 예시: “특정 이미지 처리 라이브러리나 암호화 라이브러리를 n8n 워크플로우에서 사용하고 싶어요!”
2. 커스텀 노드 개발, 시작 전에 알아야 할 것들 💡
삽질을 줄이려면 올바른 도구와 환경 설정이 필수입니다.
-
Node.js & npm (또는 yarn): n8n 커스텀 노드는 Node.js 환경에서 개발됩니다. 최신 LTS 버전을 사용하는 것을 권장합니다.
- 설치 확인:
node -v
및npm -v
또는yarn -v
- 설치 확인:
-
TypeScript (강력 추천): n8n 공식 문서에서도 TypeScript 사용을 권장합니다. 타입 안정성을 제공하여 개발 중 발생할 수 있는 오류를 미리 방지하고, 코드 자동 완성 기능을 통해 생산성을 높여줍니다. (JavaScript로도 가능하지만, 삽질을 줄이는 비법은 역시 TypeScript입니다! 😉)
-
로컬 n8n 개발 환경: 커스텀 노드를 개발하면서 실시간으로 테스트할 로컬 n8n 인스턴스가 필요합니다.
- Docker를 사용하거나,
npm install -g n8n
으로 전역 설치 후n8n
명령어로 실행할 수 있습니다.
- Docker를 사용하거나,
-
n8n-node-dev
툴: 이게 바로 삽질을 줄이는 핵심 도구 중 하나입니다! 🤩n8n-node-dev
는 커스텀 노드 개발 시 hot-reloading (코드 변경 시 자동으로 n8n에 반영) 및 디버깅 환경 설정을 도와주는 유틸리티입니다.npm install -g n8n-node-dev
-
n8n의 데이터 구조 이해: n8n은 데이터를 “아이템(Item)” 단위로 처리합니다. 각 아이템은 JSON 객체 형태로 전달되며, 배열로 여러 아이템이 들어올 수 있습니다. 입력과 출력 모두 이 구조를 따릅니다.
[ { "json": { "field1": "value1", "field2": "value2" }, "binary": {} // 바이너리 데이터가 있다면 여기에 }, { "json": { "field1": "value3", "field2": "value4" }, "binary": {} } ]
3. 삽질 줄이는 핵심 비법 🔑: 공식 보일러플레이트와 n8n-node-dev
활용!
자, 이제 진짜 비법입니다! 🤫
비법 1: n8n-node-starter
보일러플레이트 활용하기
맨땅에 헤딩하지 마세요! n8n은 커스텀 노드 개발을 위한 공식 보일러플레이트 프로젝트인 n8n-node-starter
를 제공합니다. 이걸 사용하면 기본적인 파일 구조, TypeScript 설정, 테스트 환경 설정 등 귀찮은 초기 세팅을 한 번에 끝낼 수 있습니다.
# 새로운 노드 프로젝트 생성 (my-custom-node는 원하는 프로젝트 이름)
npx n8n-node-dev new my-custom-node
cd my-custom-node
이 명령어를 실행하면 my-custom-node
폴더가 생성되고, 그 안에 다음과 같은 핵심 파일들이 자동으로 생성됩니다.
nodes/MyCustomNode.node.ts
: 노드의 실제 로직이 들어가는 파일nodes/MyCustomNode.description.ts
: 노드의 메타데이터(이름, 입력 필드, 설명 등)가 정의되는 파일package.json
: 프로젝트 설정 파일 (의존성, 스크립트 등)tsconfig.json
: TypeScript 컴파일러 설정 파일
비법 2: n8n-node-dev start
로 실시간 개발 환경 구축
위에서 설치한 n8n-node-dev
툴의 start
명령어를 사용하면 로컬 n8n 인스턴스에 커스텀 노드를 “링크”하여, 코드를 수정할 때마다 자동으로 n8n에 반영되도록 할 수 있습니다. 덕분에 매번 n8n을 재시작하거나 노드를 수동으로 복사할 필요 없이, 웹팩의 hot-reloading처럼 편하게 개발할 수 있습니다. 🔥
# 노드 개발 환경 시작 (프로젝트 루트 폴더에서 실행)
npx n8n-node-dev start
이 명령어를 실행하면 다음과 같은 일이 일어납니다.
- 현재 폴더의 노드 코드를 n8n이 인식할 수 있는 형태로 컴파일합니다.
- 로컬 n8n 인스턴스의
~/.n8n/nodes
경로에 컴파일된 노드를 심볼릭 링크(Symbolic Link)합니다. - 코드 변경을 감지하고, 변경 시 자동으로 재컴파일하여 n8n에 반영합니다.
이제 n8n
을 실행하고 워크플로우를 만들 때, 여러분이 개발 중인 커스텀 노드를 검색해서 사용할 수 있습니다!
4. 실전! 나만의 “Hello World” 노드 만들기 🚀
npx n8n-node-dev new
명령어로 생성된 기본 구조를 바탕으로 간단한 “Hello World” 노드를 만들어봅시다. 이 노드는 입력받은 이름에 “Hello, “를 붙여서 출력할 것입니다.
Step 1: Description.ts
파일 수정하기
nodes/MyCustomNode.description.ts
파일을 열어 다음과 같이 수정합니다.
(기본 생성된 파일명에서 MyCustomNode
를 여러분이 만든 이름으로 변경하세요. 저는 GreetingNode
로 가정합니다.)
// nodes/GreetingNode.description.ts
import { INodeDescription, INodeProperties } from 'n8n-workflow';
export const greetingNodeDescription: INodeDescription = {
// 노드 UI에 표시될 이름
displayName: 'My Awesome Greeting Node',
// 노드를 식별하는 내부 이름 (코드에서 사용)
name: 'myAwesomeGreetingNode',
// 아이콘 (폰트 어썸 아이콘 이름)
icon: 'fa:hand-spock',
// 노드 팔레트에서 어떤 그룹에 속할지
group: ['transform'],
// 노드 버전 (향후 업데이트 시 유용)
version: 1,
// 노드에 대한 간단한 설명
description: '사용자 이름을 입력받아 인사말을 생성합니다.',
// 기본 설정 (입력/출력 유형)
defaults: {
name: 'Greeting',
},
// 입력 유형 (앞 노드에서 데이터를 받는지)
inputs: ['main'],
// 출력 유형 (다음 노드로 데이터를 내보내는지)
outputs: ['main'],
// 노드의 속성 (사용자가 설정할 수 있는 필드들)
properties: [
{
// 필드 유형: 텍스트 입력
displayName: '이름',
name: 'userName',
type: 'string',
default: 'World', // 기본값
placeholder: '인사할 이름을 입력하세요',
description: '인사말을 생성할 대상의 이름을 입력합니다.',
},
],
};
displayName
: n8n UI에서 노드 팔레트에 표시될 이름입니다.name
: 노드를 코드 상에서 식별하는 고유한 이름입니다. (소문자, 하이픈 권장)group
: n8n 노드 팔레트의 카테고리입니다.properties
: 사용자가 노드를 설정할 때 볼 수 있는 입력 필드들을 정의합니다. 여기서는userName
이라는 텍스트 필드를 추가했습니다.
Step 2: Node.ts
파일 수정하기
nodes/GreetingNode.node.ts
파일을 열어 다음과 같이 수정합니다.
// nodes/GreetingNode.node.ts
import { IExecuteFunctions } from 'n8n-core';
import {
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
// 이전에 정의한 노드 설명을 가져옵니다.
import { greetingNodeDescription } from './GreetingNode.description';
export class GreetingNode implements INodeType {
// 노드 설명을 연결합니다.
description: INodeTypeDescription = greetingNodeDescription;
// 노드의 핵심 로직이 들어가는 execute 메서드
async execute(this: IExecuteFunctions): Promise {
// 입력 데이터(아이템)들을 가져옵니다.
// n8n은 여러 아이템을 동시에 처리할 수 있으므로, 루프를 돌려야 합니다.
const items = this.getInputData();
// 처리된 결과를 저장할 배열
const returnData: INodeExecutionData[] = [];
// 각 입력 아이템에 대해 반복 처리합니다.
for (let i = 0; i < items.length; i++) {
// 사용자로부터 설정된 'userName' 값을 가져옵니다.
// this.getNodeParameter()를 사용하여 노드 속성 값을 가져올 수 있습니다.
// 두 번째 인자로 인덱스 (i)를 넘겨주면, 각 아이템에 대한 고유한 값을 가져올 수 있습니다.
const userName = this.getNodeParameter('userName', i) as string;
// 인사말 메시지를 생성합니다.
const message = `Hello, ${userName}! Welcome to n8n custom nodes. 👋`;
// 처리된 데이터를 returnData 배열에 추가합니다.
// json 객체 안에 원하는 데이터를 넣습니다.
returnData.push({
json: {
greetingMessage: message,
},
});
}
// 처리된 데이터를 다음 노드로 전달하기 위해 반환합니다.
// 각 배열 요소는 하나의 출력(output)을 의미하며, 대부분 하나의 출력만 가집니다.
return [returnData];
}
}
execute
메서드: 이 메서드가 노드가 실행될 때 호출되는 핵심 부분입니다.this.getInputData()
: 이전 노드에서 넘어온 데이터를 가져옵니다.this.getNodeParameter('userName', i)
:Description.ts
에서 정의한userName
속성 값을 가져옵니다.i
는 현재 처리 중인 아이템의 인덱스입니다.returnData.push({ json: { greetingMessage: message } })
: 처리된 결과를json
객체 형태로 반환 데이터에 추가합니다.
Step 3: 테스트 및 디버깅
- 개발 서버 시작: 프로젝트 루트 폴더에서 터미널을 열고 다음 명령어를 실행합니다.
npx n8n-node-dev start
이 명령어가 실행된 터미널은 계속 켜 두어야 합니다.
- n8n 실행: 다른 터미널에서 로컬 n8n 인스턴스를 실행합니다.
n8n
- 워크플로우 생성: n8n UI에 접속하여 새 워크플로우를 생성합니다.
- 노드 추가:
+
버튼을 클릭하고My Awesome Greeting Node
를 검색하여 추가합니다. - 설정: 노드를 클릭하고
userName
필드에 여러분의 이름을 입력해 보세요. (예:Jane Doe
) - 실행: 워크플로우를 저장하고 실행해 보세요.
-
결과 확인: 다음 노드(예:
Set
노드나Log
노드)를 연결하여greetingMessage
가 잘 출력되는지 확인해 보세요.Set
노드의 “Keep Only Set” 옵션을 끄고greetingMessage
필드를 추가하면 이전 데이터와 함께 출력됩니다.
{ "json": { "greetingMessage": "Hello, Jane Doe! Welcome to n8n custom nodes. 👋" } }
오류가 발생하면
n8n-node-dev start
가 실행 중인 터미널에 에러 메시지가 출력될 것입니다.console.log
를 활용하여 디버깅하는 것도 좋은 방법입니다.
5. 더 나아가기 💪
“Hello World”를 넘어 더 복잡하고 유용한 커스텀 노드를 만들기 위한 팁입니다.
- 크리덴셜 (Credentials) 사용: API 키나 비밀번호와 같은 민감한 정보는 노드 코드에 직접 하드코딩하지 말고, n8n의 크리덴셜 기능을 사용하세요.
INodeDescription
의credentials
속성 및this.getCredentials()
메서드를 활용합니다.- 예시: 외부 API 호출 시 API 키를 안전하게 관리할 수 있습니다.
- HTTP 요청:
this.helpers.httpRequest()
를 사용하여 손쉽게 HTTP 요청을 보낼 수 있습니다. 이는axios
기반으로 되어 있어 익숙하게 사용할 수 있습니다.- 예시: 외부 웹훅 호출, REST API 연동 등.
- 다양한 데이터 유형 처리: 텍스트뿐만 아니라 바이너리 데이터(파일), 배열, 객체 등 n8n의 다양한 데이터 유형을
INodeExecutionData
인터페이스를 통해 처리하는 방법을 익히세요.- 예시: 이미지 파일을 다운로드하여 리사이즈하거나, CSV 파일을 파싱하여 JSON으로 변환하는 노드.
- 에러 핸들링:
try...catch
블록을 사용하여 예상치 못한 오류를 처리하고, 사용자에게 의미 있는 에러 메시지를 제공하세요.this.add ]Error()
를 사용하여 특정 아이템에 대한 오류를 기록할 수 있습니다. - 입출력 옵션:
inputs
와outputs
속성을 배열로 설정하여 여러 개의 입력/출력 포트를 가질 수 있습니다.execute
메서드에서this.getInputData(index)
및return [[output1], [output2]]
와 같이 처리합니다. - 커뮤니티 활용: n8n 공식 문서, Discord 채널, 포럼 등 커뮤니티에는 수많은 정보와 사례가 공유되어 있습니다. 막히는 부분이 있다면 주저하지 말고 찾아보고 질문하세요!
마무리하며 🎉
n8n 커스텀 노드 개발은 처음에는 다소 복잡해 보일 수 있지만, n8n-node-starter
보일러플레이트와 n8n-node-dev
같은 유용한 도구들을 활용하면 삽질을 최소화하고 생산성을 극대화할 수 있습니다. 여러분만의 특별한 워크플로우를 위한 맞춤형 노드를 직접 만들어보면서, n8n의 무궁무진한 가능성을 경험해 보시길 바랍니다! 🥳
이제 여러분도 나만의 비법으로 n8n 커스텀 노드의 마스터가 될 준비가 되었습니다! 궁금한 점이 있다면 언제든지 댓글로 남겨주세요. 다음에는 더 유익한 정보로 찾아오겠습니다! Happy Automating! ✨