วิธีการผสมผสาน n8n กับ custom code เพื่อสร้างระบบอัตโนมัติที่ทรงพลังและยืดหยุ่น
Integration Patterns
Pattern 1: Function Node (Simple)
n8n Workflow:
Trigger → Function Node → Action Node
Custom Code:
├─ Data transformation
├─ Validation
├─ Simple calculations
└─ Thai text processing
Pattern 2: HTTP Request Node (External API)
n8n Workflow:
Trigger → HTTP Request → Process Response → Action
Custom Code:
├─ Express.js API
├─ Custom business logic
├─ Database operations
└─ Complex calculations
Pattern 3: Webhook Response (Two-way)
n8n Workflow:
Webhook → Custom API → Response to Webhook
Custom Code:
├─ Real-time processing
├─ Complex workflows
├─ Database transactions
└─ External integrations
Example 1: LINE Chatbot with Thai NLP
n8n Workflow Structure
LINE Webhook → Function (Thai NLP) → IF Node → Function (Response) → LINE Reply
Function Node 1: Thai Intent Detection
// Thai Intent Detection Function
const thaiKeywords = {
greeting: ['สวัสดี', 'ดี', 'หวัดดี', 'เฮลโล', 'ดีครับ', 'ดีคะ'],
price: ['ราคา', 'เท่าไหร่', 'กี่บาท', 'ค่าใช้จ่าย', 'บริการ'],
appointment: ['นัด', 'ว่าง', 'เวลา', 'วัน', 'จอง', 'นัดหมาย', 'ตาราง'],
contact: ['ติดต่อ', 'โทร', 'อีเมล', 'ที่อยู่', 'เบอร์', 'สาขา'],
service: ['บริการ', 'ทำ', 'รักษา', 'ตรวจ', 'consult', 'ปรึกษา'],
emergency: ['ฉุกเฉิน', 'ปวด', 'เจ็บ', 'ฉีด', ' accident', 'บาดเจ็บ']
};
function detectThaiIntent(message) {
const normalizedMessage = message.toLowerCase().trim();
let detectedIntent = 'unknown';
let confidence = 0;
for (const [intent, keywords] of Object.entries(thaiKeywords)) {
const matches = keywords.filter(keyword =>
normalizedMessage.includes(keyword)
).length;
if (matches > confidence) {
confidence = matches;
detectedIntent = intent;
}
}
// Extract entities
const entities = {};
// Phone number extraction
const phoneMatch = normalizedMessage.match(/0[689]\d{8}/);
if (phoneMatch) entities.phone = phoneMatch[0];
// Date extraction (Thai format)
const dateMatch = normalizedMessage.match(/(\d{1,2})[\/\-](\d{1,2})/);
if (dateMatch) entities.date = dateMatch[0];
// Time extraction
const timeMatch = normalizedMessage.match(/(\d{1,2})[:.]?(\d{2})/);
if (timeMatch) entities.time = timeMatch[0];
return {
intent: detectedIntent,
confidence: confidence / 3, // Normalize to 0-1
entities,
originalMessage: message
};
}
// Process incoming LINE messages
const events = $input.all()[0].json.events || [];
const processedMessages = [];
for (const event of events) {
if (event.type === 'message' && event.message.type === 'text') {
const result = detectThaiIntent(event.message.text);
processedMessages.push({
replyToken: event.replyToken,
userId: event.source.userId,
intent: result.intent,
confidence: result.confidence,
entities: result.entities,
message: event.message.text
});
}
}
return processedMessages.map(msg => ({ json: msg }));
Function Node 2: Response Generation
// Response Generation Function
function generateResponse(intent, entities, confidence) {
const responses = {
greeting: {
text: 'สวัสดีครับ! 🙏\nยินดีต้อนรับสู่ ShantiLink\nมีอะไรให้ช่วยไหมครับ?',
quickReply: [
{ type: 'text', text: 'สอบถามราคา' },
{ type: 'text', text: 'นัดหมาย' },
{ type: 'text', text: 'ติดต่อ' }
]
},
price: {
text: '📋 ราคาบริการของเรา:\n\n💻 ปรึกษาระบบ: 1,500 บาท/ชั่วโมง\n🤖 ทำ Chatbot: 5,000 บาทขึ้นไป\n📱 LINE LIFF: 8,000 บาทขึ้นไป\n🔧 ระบบอัตโนมัติ: 15,000 บาทขึ้นไป\n\nสอบถามรายละเอียดได้ครับ',
quickReply: [
{ type: 'text', text: 'นัดหมายปรึกษา' },
{ type: 'text', text: 'ดูผลงาน' }
]
},
appointment: {
text: `📅 นัดหมายได้ที่:\n\n🕐 จันทร์-ศุกร์: 9:00-18:00\n📍 กรุงเทพฯ (ออนไลน์ก็ได้)\n\n${entities.date ? `วันที่สนใจ: ${entities.date}\n` : ''}${entities.time ? `เวลา: ${entities.time}\n` : ''}กรุณาระบุ:\n1. ชื่อ-นามสกุล\n2. เบอร์โทรศัพท์\n3. ประเภทที่ปรึกษา`,
quickReply: [
{ type: 'text', text: 'ปรึกษาระบบ' },
{ type: 'text', text: 'ทำเว็บไซต์' },
{ type: 'text', text: 'ทำแอปพลิเคชัน' }
]
},
contact: {
text: '📞 ติดต่อ ShantiLink:\n\n📱 โทร: 081-234-5678\n📧 อีเมล: info@shantilink.com\n🌐 เว็บ: shantilink.com\n💬 LINE: @shantilink\n\nติดต่อได้ 24 ชั่วโมงครับ',
quickReply: [
{ type: 'text', text: 'ปรึกษาฟรี' },
{ type: 'text', text: 'ดูโปรเจกต์' }
]
},
service: {
text: '🛠️ บริการของเรา:\n\n✅ ระบบอัตโนมัติ (n8n)\n✅ Chatbot LINE/Facebook\n✅ เว็บไซต์และแอป\n✅ LINE LIFF Application\n✅ ระบบจอง-นัดหมาย\n✅ Dashboard รายงาน\n\nทุกระบบทำงานได้จริงครับ',
quickReply: [
{ type: 'text', text: 'ดูผลงาน' },
{ type: 'text', text: 'ปรึกษาฟรี' }
]
},
emergency: {
text: '⚠️ กรณีฉุกเฉิน:\n\n🚨 หากเป็นเรื่องแพทย์ฉุกเฉิน\nโทร: 1669 (ฉุกเฉินการแพทย์)\n\n🏥 หากต้องการคลินิกใกล้เคียง\nแจ้งที่อยู่ปัจจุบันได้ครับ\n\n📞 ติดต่อเรา: 081-234-5678',
quickReply: [
{ type: 'text', text: 'แจ้งที่อยู่' },
{ type: 'text', text: 'โทรหาคลินิก' }
]
},
unknown: {
text: '😊 ขอโทษครับ ยังไม่เข้าใจ\n\nลองพิมพ์:\n• "สวัสดี" - เริ่มต้น\n• "ราคา" - ดูบริการ\n• "นัด" - จองคิว\n• "ติดต่อ" - หาเรา\n\nหรือพิมพ์คำถามได้เลยครับ',
quickReply: [
{ type: 'text', text: 'สวัสดี' },
{ type: 'text', text: 'ราคา' },
{ type: 'text', text: 'นัดหมาย' }
]
}
};
return responses[intent] || responses.unknown;
}
// Generate response for each message
const responses = [];
for (const message of $input.all()) {
const data = message.json;
const response = generateResponse(data.intent, data.entities, data.confidence);
responses.push({
replyToken: data.replyToken,
messages: [
{
type: 'text',
text: response.text,
quickReply: {
items: response.quickReply.map(reply => ({
type: 'action',
action: {
type: 'message',
label: reply.text,
text: reply.text
}
}))
}
}
]
});
}
return responses.map(resp => ({ json: resp }));
Example 2: Custom API for Order Processing
Express.js API Server
// server.js
const express = require('express');
const { createClient } = require('@supabase/supabase-js');
const nodemailer = require('nodemailer');
const app = express();
app.use(express.json());
// Initialize Supabase
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
);
// Initialize email transporter
const transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS
}
});
// Order processing endpoint
app.post('/api/orders', async (req, res) => {
try {
const { customerId, items, totalAmount, deliveryAddress } = req.body;
// Validate order
if (!customerId || !items || items.length === 0) {
return res.status(400).json({
success: false,
error: 'Invalid order data'
});
}
// Generate order ID
const orderId = `ORD${Date.now()}${Math.random().toString(36).substr(2, 5).toUpperCase()}`;
// Save to database
const { data: order, error } = await supabase
.from('orders')
.insert({
order_id: orderId,
customer_id: customerId,
items: items,
total_amount: totalAmount,
delivery_address: deliveryAddress,
status: 'pending',
created_at: new Date().toISOString()
})
.select()
.single();
if (error) throw error;
// Update inventory
for (const item of items) {
await supabase
.from('products')
.update({ stock: supabase.rpc('decrement', { amount: item.quantity }) })
.eq('product_id', item.productId);
}
// Send confirmation email
await transporter.sendMail({
to: customerEmail,
subject: `Order Confirmation - ${orderId}`,
html: `
<h2>ยืนยันการสั่งซื้อ</h2>
<p>เลขที่สั่งซื้อ: ${orderId}</p>
<p>วันที่: ${new Date().toLocaleDateString('th-TH')}</p>
<h3>รายการสินค้า:</h3>
<ul>
${items.map(item => `
<li>${item.name} x ${item.quantity} = ${item.price * item.quantity} บาท</li>
`).join('')}
</ul>
<h3>รวม: ${totalAmount} บาท</h3>
<p>ที่อยู่จัดส่ง: ${deliveryAddress}</p>
`
});
// Trigger n8n workflow (if needed)
await fetch('https://your-n8n-instance.com/webhook/order-processed', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ orderId, status: 'confirmed' })
});
res.json({
success: true,
orderId,
message: 'Order processed successfully'
});
} catch (error) {
console.error('Order processing error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
// Order status endpoint
app.get('/api/orders/:orderId', async (req, res) => {
try {
const { orderId } = req.params;
const { data: order, error } = await supabase
.from('orders')
.select('*')
.eq('order_id', orderId)
.single();
if (error || !order) {
return res.status(404).json({
success: false,
error: 'Order not found'
});
}
res.json({
success: true,
order: {
orderId: order.order_id,
status: order.status,
totalAmount: order.total_amount,
createdAt: order.created_at,
estimatedDelivery: order.estimated_delivery
}
});
} catch (error) {
console.error('Order status error:', error);
res.status(500).json({
success: false,
error: 'Internal server error'
});
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Order API server running on port ${PORT}`);
});
n8n Integration
n8n Workflow:
Order Webhook → HTTP Request (Custom API) → Function (Process Response) → Google Sheets → LINE Notification
Example 3: Data Processing Pipeline
Custom Data Processor
// data-processor.js
class SalesDataProcessor {
constructor() {
this.holidays = this.getThaiHolidays();
this.workingHours = { start: 9, end: 18 };
}
processSalesData(rawData) {
return rawData.map(record => this.processSingleRecord(record));
}
processSingleRecord(record) {
return {
...record,
processedAt: new Date().toISOString(),
isWorkingHour: this.isWorkingHour(new Date(record.timestamp)),
isHoliday: this.isHoliday(new Date(record.timestamp)),
dayOfWeek: new Date(record.timestamp).toLocaleDateString('th-TH', { weekday: 'long' }),
revenueCategory: this.categorizeRevenue(record.amount),
customerSegment: this.segmentCustomer(record.customerId, record.amount)
};
}
isWorkingHour(date) {
const hour = date.getHours();
const day = date.getDay();
return day >= 1 && day <= 5 && hour >= this.workingHours.start && hour < this.workingHours.end;
}
isHoliday(date) {
const dateStr = date.toISOString().split('T')[0];
return this.holidays.includes(dateStr);
}
categorizeRevenue(amount) {
if (amount < 1000) return 'low';
if (amount < 5000) return 'medium';
if (amount < 20000) return 'high';
return 'premium';
}
segmentCustomer(customerId, amount) {
// Simple segmentation based on purchase amount
if (amount > 10000) return 'vip';
if (amount > 5000) return 'regular';
return 'new';
}
generateReport(processedData) {
const report = {
summary: {
totalRevenue: processedData.reduce((sum, r) => sum + r.amount, 0),
totalOrders: processedData.length,
averageOrderValue: processedData.reduce((sum, r) => sum + r.amount, 0) / processedData.length,
workingHourOrders: processedData.filter(r => r.isWorkingHour).length,
holidayOrders: processedData.filter(r => r.isHoliday).length
},
byCategory: this.groupBy(processedData, 'revenueCategory'),
bySegment: this.groupBy(processedData, 'customerSegment'),
byDayOfWeek: this.groupBy(processedData, 'dayOfWeek')
};
return report;
}
groupBy(data, field) {
return data.reduce((acc, record) => {
const key = record[field];
if (!acc[key]) {
acc[key] = { count: 0, revenue: 0 };
}
acc[key].count++;
acc[key].revenue += record.amount;
return acc;
}, {});
}
getThaiHolidays() {
// Simplified Thai holidays list
return [
'2024-01-01', // New Year
'2024-02-12', // Makha Bucha Day
'2024-04-06', // Chakri Day
'2024-04-13', // Songkran
'2024-04-14', // Songkran
'2024-04-15', // Songkran
'2024-05-01', // Labor Day
'2024-05-22', // Visakha Bucha Day
// ... more holidays
];
}
}
module.exports = SalesDataProcessor;
Best Practices
1. Error Handling
// Always wrap custom code in try-catch
try {
// Your custom logic here
const result = processData(inputData);
return result;
} catch (error) {
console.error('Custom code error:', error);
// Return error response that n8n can handle
return {
error: true,
message: error.message,
timestamp: new Date().toISOString()
};
}
2. Input Validation
function validateInput(data) {
if (!data || typeof data !== 'object') {
throw new Error('Invalid input data');
}
if (!data.message || typeof data.message !== 'string') {
throw new Error('Message is required and must be a string');
}
if (data.message.length > 1000) {
throw new Error('Message too long');
}
return true;
}
3. Performance Optimization
// Use async/await for I/O operations
async function processLargeDataset(data) {
const batchSize = 100;
const results = [];
for (let i = 0; i < data.length; i += batchSize) {
const batch = data.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processItem(item))
);
results.push(...batchResults);
// Prevent blocking
if (i % (batchSize * 10) === 0) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
return results;
}
Deployment Considerations
1. Environment Setup
# Package.json dependencies
{
"dependencies": {
"express": "^4.18.0",
"@supabase/supabase-js": "^2.0.0",
"nodemailer": "^6.9.0",
"dotenv": "^16.0.0"
},
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
2. Docker Configuration
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
3. Monitoring
// Add monitoring to your custom code
const monitoring = {
logExecution: (functionName, duration, success) => {
console.log({
function: functionName,
duration,
success,
timestamp: new Date().toISOString()
});
},
logError: (functionName, error) => {
console.error({
function: functionName,
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
}
};
Next Steps
- Cloud Services - การ deploy และจัดการระบบ
- Client Examples - ดูตัวอย่างโปรเจกต์จริง
- Supabase Basics - ฐานข้อมูลและ backend
ต้องการความช่วยเหลือ? ติดต่อเราได้ที่ ShantiLink.com 💬