Development & Debugging Nodes
Execute custom code, debug workflows, and log execution data
Development & Debugging Nodes
Sometimes nodes can't do what you need. Write custom code, debug issues, and log execution data. These nodes give you the power to extend DeepChain beyond its built-in capabilities.
Code Node
When to use: When you need custom logic that doesn't fit into standard nodes—complex calculations, custom validation, data transformation, etc.
The Code Node lets you write Dart code that executes as part of your workflow. Access input data, perform any operation, and return results.
Configuration
Configuration:
code: |
final result = input['value'] * 2;
return {'doubled': result};
timeout: 5000
Code Examples
Example 1: Complex Validation
When to use: Validate data against business rules that are too complex for simple If nodes.
Incoming data:
{
"email": "alice@example.com",
"password": "Secure123!",
"age": 18,
"country": "US"
}
Code Node:
// Validate registration
final email = input['email'] as String;
final password = input['password'] as String;
final age = input['age'] as int;
final country = input['country'] as String;
// Check email domain
if (!email.endsWith('@company.com')) {
throw Exception('Email must be from @company.com domain');
}
// Check password strength
if (password.length < 8 || !password.contains(RegExp(r'[!@#$%^&*]'))) {
throw Exception('Password must be 8+ chars with special characters');
}
// Check age restrictions
if (country == 'US' && age < 18) {
throw Exception('Must be 18+ in US');
}
return {
'valid': true,
'strength': password.length > 12 ? 'strong' : 'medium'
};
If validation fails, the exception is caught and the workflow moves to error handling.
Example 2: Advanced Math/Calculations
When to use: Perform calculations beyond simple arithmetic.
Incoming data:
{
"principal": 10000,
"rate": 0.05,
"years": 10
}
Code Node:
// Compound interest calculation
final principal = input['principal'] as double;
final rate = input['rate'] as double;
final years = input['years'] as int;
final amount = principal * Math.pow(1 + rate, years);
final interest = amount - principal;
return {
'final_amount': amount.toStringAsFixed(2),
'interest_earned': interest.toStringAsFixed(2),
'rounded_amount': amount.round()
};
Example 3: String Manipulation
When to use: Complex text processing beyond regex.
Incoming data:
{
"text": "Hello World",
"format": "camelCase"
}
Code Node:
String toCamelCase(String input) {
final words = input.split(' ');
if (words.isEmpty) return '';
String result = words[0].toLowerCase();
for (int i = 1; i < words.length; i++) {
result += words[i][0].toUpperCase() + words[i].substring(1).toLowerCase();
}
return result;
}
final text = input['text'] as String;
final format = input['format'] as String;
if (format == 'camelCase') {
return {'result': toCamelCase(text)};
} else if (format == 'snake_case') {
return {'result': text.toLowerCase().replaceAll(' ', '_')};
} else {
return {'result': text};
}
Output:
{
"result": "helloWorld"
}
Example 4: Array Processing
When to use: Transform arrays in ways that would need multiple nodes.
Incoming data:
{
"users": [
{ "id": 1, "name": "Alice", "age": 28 },
{ "id": 2, "name": "Bob", "age": 35 },
{ "id": 3, "name": "Charlie", "age": 22 }
]
}
Code Node:
final users = input['users'] as List<dynamic>;
// Group by age range
final Map<String, List<dynamic>> grouped = {};
for (var user in users) {
final age = user['age'] as int;
String group = age < 25 ? 'young' : 'adult';
if (grouped[group] == null) {
grouped[group] = [];
}
grouped[group]!.add(user);
}
// Sort each group by name
grouped.forEach((key, users) {
users.sort((a, b) => (a['name'] as String).compareTo(b['name'] as String));
});
return {
'groups': grouped,
'total': users.length,
'average_age': (users.fold(0, (sum, u) => sum + u['age']) / users.length).round()
};
Output:
{
"groups": {
"young": [
{ "id": 3, "name": "Charlie", "age": 22 }
],
"adult": [
{ "id": 1, "name": "Alice", "age": 28 },
{ "id": 2, "name": "Bob", "age": 35 }
]
},
"total": 3,
"average_age": 28
}
Example 5: Integration with External Logic
When to use: Call libraries or execute complex logic.
Incoming data:
{
"amount": 100,
"from_currency": "USD",
"to_currency": "EUR"
}
Code Node:
// Simple currency conversion (in real workflow, call API)
final Map<String, double> rates = {
'USD_EUR': 0.92,
'USD_GBP': 0.79,
'USD_JPY': 149.50
};
final amount = input['amount'] as double;
final from = input['from_currency'] as String;
final to = input['to_currency'] as String;
if (from == to) {
return {
'original': amount,
'converted': amount,
'rate': 1.0
};
}
final rateKey = '${from}_${to}';
final rate = rates[rateKey];
if (rate == null) {
throw Exception('Conversion rate not found for $from to $to');
}
final converted = amount * rate;
return {
'original': amount,
'converted': converted.toStringAsFixed(2),
'rate': rate
};
Common Code Patterns
Pattern 1: Input Validation
// Always validate inputs before processing
final required = ['user_id', 'email'];
for (var field in required) {
if (input[field] == null || input[field].toString().isEmpty) {
throw Exception('Missing required field: $field');
}
}
// Validate types
if (input['age'] is! int) {
throw Exception('Age must be an integer');
}
return {'valid': true};
Pattern 2: Error Handling
try {
// Do something risky
final result = riskyOperation(input['data']);
return {'success': true, 'data': result};
} catch (e) {
// Return error details for workflow handling
return {
'success': false,
'error': e.toString(),
'timestamp': DateTime.now().toIso8601String()
};
}
Pattern 3: Conditional Return
if (input['amount'] > 1000) {
// High value → require approval
return {
'requires_approval': true,
'approval_level': 'manager'
};
} else {
// Low value → auto-approve
return {
'requires_approval': false,
'approved': true
};
}
Log Node
When to use: When you need to debug your workflow—print variables, trace execution, monitor data flow.
The Log Node outputs messages to the workflow logs. Perfect for debugging, monitoring, and understanding what's happening in your workflow.
Configuration
Configuration:
level: debug | info | warn | error
message: "Processing {{ input.id }}"
include_data: true
Log Examples
Example 1: Debug Logging
Log Node Configuration:
level: debug
message: "Processing order: {{ input.order_id }}, Total: ${{ input.total }}"
include_data: true
Output to logs:
[DEBUG] Processing order: ORD-123, Total: $99.99
{
"order_id": "ORD-123",
"total": 99.99,
"customer": "Alice",
...
}
Example 2: Info Logging
level: info
message: "Order {{ input.order_id }} approved by {{ input.approver }}"
include_data: false
Output to logs:
[INFO] Order ORD-123 approved by manager@company.com
Example 3: Error Logging
level: error
message: "Payment failed for order {{ input.order_id }}: {{ input.error_message }}"
include_data: true
Perfect for alerting on failures!
Example 4: Warn Logging
level: warn
message: "Order {{ input.order_id }} is high value (${{ input.total }}), flagged for review"
include_data: false
Debug Node
When to use: When you need to inspect the exact state of data flowing through your workflow during development.
The Debug Node pauses execution and opens the debugger, letting you inspect variables, step through code, etc.
Configuration
Configuration:
enabled: true # Usually false in production
break_on_error: true
inspect_variables:
- input
- intermediate_data
Hot Reload Node
When to use: During development, when you want to see changes immediately without restarting the workflow.
The Hot Reload Node enables live code changes while your workflow is running.
Configuration
Configuration:
enabled: true
watch_files:
- /code/my_function.dart
Common Debugging Patterns
Pattern 1: Track Data Through Pipeline
Start
↓
Log (Level: info, "Started with: {{ input }}")
↓
Data Transform
↓
Log (Level: debug, "After transform: {{ output }}")
↓
HTTP Request
↓
Log (Level: debug, "API response: {{ response }}")
↓
Database Insert
↓
Log (Level: info, "Inserted record ID: {{ record_id }}")
Each log shows data at each stage.
Pattern 2: Error Tracking
HTTP Request
├─ Success → Log (info, "API call succeeded")
│ ↓
│ Continue
│
└─ Error → Log (error, "API call failed: {{ error }}")
↓
Code Node (extract error details)
↓
Email (notify ops team)
Pattern 3: Conditional Logging
Code Node
├─ High-risk operation → Log (warn, "High risk...")
└─ Normal operation → Log (debug, "Normal...")
Performance Profiling with Logging
Measure Execution Time
// In Code Node
final startTime = DateTime.now().millisecondsSinceEpoch;
// Do some work
// ...
final duration = DateTime.now().millisecondsSinceEpoch - startTime;
return {
'result': result,
'execution_time_ms': duration
};
Then use Log Node:
level: info
message: "Operation took {{ output.execution_time_ms }}ms"
Development Best Practices
Code Quality
- Validate all inputs - don't assume data is correct
- Use meaningful variable names -
final amount = ...notfinal a = ... - Add comments for complex logic
- Test edge cases - null values, empty arrays, negative numbers, etc.
Error Handling
- Throw exceptions with helpful messages - not just "Error"
- Log before returning - track what went wrong
- Handle errors gracefully - don't crash the workflow
Performance
- Keep code simple - complex code is slow code
- Avoid infinite loops - add safety checks
- Use efficient algorithms - don't sort/search large arrays inefficiently
- Cache results - don't recalculate same values
Debugging
- Use Log nodes liberally - log at each step during development
- Check types - ensure inputs are what you expect
- Test with real data - test mode can hide issues
- Read error messages - they often tell you exactly what went wrong
Next Steps
- Need control flow? Check Control Flow Nodes
- Transforming data? Try Data Processing Nodes
- Calling APIs? Visit Communication Nodes
- Database operations? See Database Nodes
- Complex workflows? Use Chain Node to break into smaller pieces