n8n is my default tool for business automation: it is self-hosted, it talks to almost any API and when the built-in nodes are not enough a Code node gives you full JavaScript. The first version of a workflow usually takes an hour. Keeping it correct for a year takes a few patterns.
1. Make every run idempotent
Workflows get retried, webhooks get delivered twice and schedules overlap. Writes should therefore be upserts keyed on a natural unique value (a domain name, an invoice number, an email), so running the same input twice leaves the database unchanged.
2. Keep a history table
When a workflow updates a record, also write a row to a history table: record, field, old value, new value, source, timestamp. When someone asks why a value changed, the answer is one query away. It also makes rollbacks trivial.
3. Build SQL carefully
Never paste raw input into a query. Either use the node's query parameters, or build the statement in a Code node and escape every value. For JSON that contains Arabic or other Unicode text, sending it base64-encoded to a stored procedure and decoding it there avoids a whole class of encoding bugs.
// n8n Code node: build a safe upsert
const esc = (v) => String(v).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
return items.map(({ json }) => ({
json: {
sql: "INSERT INTO domains (name, expires_at) VALUES ('" + esc(json.name) +
"', '" + esc(json.expires) + "') ON DUPLICATE KEY UPDATE expires_at = VALUES(expires_at)"
}
}));
4. Handle errors on purpose
- Attach an error workflow that sends an alert with the workflow name, node and message.
- Use Continue on fail only where a failure is expected, and route those items to a branch that logs them.
- Keep notification channels behind an active/inactive setting, so you can mute one without editing the workflow.
5. Secure the entry points
Webhooks are public URLs. Protect them with header authentication or a shared secret, validate the payload shape early, and reject anything unexpected before it reaches the database.
6. Version your workflows
Export every changed workflow as a new JSON version and keep it in Git. In n8n 2.x remember that saving a workflow is not the same as publishing it: check that the active version is the one you just tested.