Can n8n Do ETL? Build a Real Pipeline from API to Postgres
Wondering if n8n can handle real ETL workloads? You are not alone. A quick scan of forums and communities shows the same question keeps coming up: "Does n8n have ETL functionality?" Developers and data engineers who have built pipelines with Airflow, Pentaho, or custom scripts often ask whether a visual automation tool can replace their existing stack. The short answer is yes, and the longer answer is that n8n handles ETL surprisingly well — especially for teams that want to move data between SaaS apps, APIs, and databases without managing infrastructure.
In this tutorial, you will build a production-ready n8n ETL pipeline that extracts data from a REST API, transforms it with the Code node, and loads the result into PostgreSQL. By the end, you will have a scheduled, error-handled pipeline you can adapt to your own data sources.
Can n8n Actually Handle ETL Workloads?
The hesitation is understandable. Traditional ETL tools like Airflow, dbt, or Pentaho offer deep ecosystem support for data engineering workflows. But n8n brings something different to the table: a visual builder that connects 400+ integrations out of the box, with the ability to inject custom JavaScript wherever you need it.
Here is what makes n8n viable for ETL:
- HTTP Request node — handles GET, POST, PUT, DELETE, pagination, and custom headers for any REST API.
- Code node — lets you write JavaScript (or Python with the community node) to clean, transform, and validate data.
- Database nodes — native connectors for PostgreSQL, MySQL, MongoDB, MariaDB, SQLite, and more, with full CRUD support.
- Error Trigger node — catches failures in any downstream node so you can alert or retry.
- Cron trigger — schedule pipelines to run every minute, hourly, daily, or on custom intervals.
If you have been managing a self-hosted n8n instance and dealing with uptime issues, a managed solution like n8nautomation.cloud keeps your pipelines running with automatic backups and 24/7 availability — no server management required.
What We Are Building: An API-to-PostgreSQL ETL Pipeline
Our pipeline will follow the classic three-phase ETL pattern:
- Extract — pull user records from the JSONPlaceholder API (a free fake REST API for testing).
- Transform — clean field names, flatten nested objects (address, company), validate email addresses, and add a processed timestamp.
- Load — insert the transformed records into a PostgreSQL table using upsert logic so repeated runs do not create duplicates.
We will then wrap it with error handling and a cron schedule so it runs every hour without manual intervention.
Tip: You can adapt this exact pattern to any REST API — replace the endpoint, adjust the transformation logic in the Code node, and point it at your own database. The structure stays the same.
Step 1: Extract Data from a REST API Using the HTTP Request Node
Add an HTTP Request node as the first step in your workflow. This node will call the JSONPlaceholder API and return a list of user objects.
Configure the node with these parameters:
- Method: GET
- URL:
https://jsonplaceholder.typicode.com/users - Authentication: None (this is a public API)
- Query Parameters: leave empty
- Send Headers: add
Accept: application/json - Response Format: JSON
- Paginate: disabled — the /users endpoint returns all 10 records in one response
If you were pulling a larger dataset from an API that uses pagination (page-based, cursor-based, or offset-based), you would enable the Paginate toggle in the node settings and configure the pagination parameters. n8n handles the looping automatically — it will keep requesting pages until the response is empty or you set a limit.
Click Execute Node to test. You should see an array of 10 user objects, each containing fields like id, name, username, email, address (a nested object), phone, website, and company (also nested). This raw data is what we will transform in the next step.
Step 2: Transform and Clean Data with the Code Node
Add a Code node after the HTTP Request node. This is where the transformation phase of our ETL pipeline happens. We will write JavaScript to clean, validate, and reshape the incoming data before loading it into PostgreSQL.
Set the node mode to Run Once for All Items. Then paste the following code:
const output = [];
for (const user of $input.all()) {
const data = user.json;
// Skip records without a valid email
if (!data.email || !data.email.includes('@')) {
continue;
}
// Flatten the address object
const address = data.address || {};
const geo = address.geo || {};
// Flatten the company object
const company = data.company || {};
output.push({
external_id: data.id,
full_name: data.name,
username: data.username,
email: data.email.toLowerCase(),
phone: data.phone,
website: data.website,
street: address.street || '',
suite: address.suite || '',
city: address.city || '',
zipcode: address.zipcode || '',
lat: geo.lat || null,
lng: geo.lng || null,
company_name: company.name || '',
company_catch_phrase: company.catchPhrase || '',
company_bs: company.bs || '',
processed_at: new Date().toISOString()
});
}
return output;Here is what this transformation does:
- Filters invalid records — any user without a properly formatted email address is skipped.
- Flattens nested objects — the
addressandcompanyobjects are expanded into flat columns suitable for a relational table. - Normalises data — emails are lowercased for consistency.
- Adds metadata — a
processed_atISO timestamp is appended so you can track when each record was loaded. - Renames fields — the API's
idbecomesexternal_idto avoid confusion with an auto-incrementing primary key in your database.
Execute the node to verify the output. Each item should now be a flat object with clean, predictable field names.
Tip: For more complex transformations — regex-based parsing, date arithmetic, or data enrichment via a second API call — chain multiple Code nodes. Keeping each transformation atomic makes debugging much easier.
Step 3: Load Transformed Data into PostgreSQL
Add a PostgreSQL node after the Code node. This node will insert the transformed records into a table. Before you configure the node, make sure you have a table ready in your PostgreSQL database.
Run this SQL to create the target table:
CREATE TABLE IF NOT EXISTS etl_users (
id SERIAL PRIMARY KEY,
external_id INTEGER UNIQUE NOT NULL,
full_name VARCHAR(255),
username VARCHAR(255),
email VARCHAR(255),
phone VARCHAR(100),
website VARCHAR(255),
street VARCHAR(255),
suite VARCHAR(255),
city VARCHAR(255),
zipcode VARCHAR(20),
lat DECIMAL(10, 7),
lng DECIMAL(10, 7),
company_name VARCHAR(255),
company_catch_phrase TEXT,
company_bs TEXT,
processed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);Back in n8n, configure the PostgreSQL node:
- Credential: select or create your PostgreSQL connection. You will need host, port, database name, username, and password.
- Operation: Insert
- Schema:
public - Table:
etl_users - Column Mapping: map each field from the Code node output to the corresponding column in the table.
Enable Upsert by checking the option and setting external_id as the conflict target. This ensures that if you run the pipeline again, existing records are updated instead of throwing a duplicate key error.
Execute the node. Open your PostgreSQL client and run SELECT * FROM etl_users; — you should see all 10 records with flattened fields and the processed_at timestamp populated.
Add Error Handling with the Error Trigger Node
A production ETL pipeline needs error handling. If the API is down, the database rejects a connection, or the transformation throws an exception, you want to know about it immediately rather than discovering it hours later when data is stale.
Add an Error Trigger node to your workflow. This special node fires only when another node in the workflow fails. Connect it to a Slack node (or an Email node, or a Telegram node — whatever alert channel you prefer).
Configure the Error Trigger node with the workflow ID of your main pipeline. Then configure the Slack node:
- Channel:
#data-alerts(or your preferred channel) - Message: include the error details from the Error Trigger output
Use this message template in the Slack node:
🚨 ETL Pipeline Failed
Workflow: {{ $json.workflowName }}
Node: {{ $json.triggerNodeName }}
Error: {{ $json.errorMessage }}
Timestamp: {{ $json.time }}Now if your API call fails or the PostgreSQL insert throws an error, your team gets an instant notification in Slack. You can also extend this pattern to write failed records to a dead-letter table in PostgreSQL for later inspection.
Schedule Your Pipeline to Run Automatically
Manual execution is fine for testing. For a real ETL pipeline, you want it running on a schedule. n8n provides a Cron trigger and a Schedule trigger for this purpose.
Replace the manual execution with a Schedule trigger as the first node in your workflow:
- Trigger Times: Every Hour
- Interval: 1 hour
If you need more precise control, use the Cron trigger instead and supply a standard cron expression. For example, 0 */2 * * * runs every two hours, and 0 6 * * 1-5 runs at 6 AM on weekdays.
Once the schedule is set, activate the workflow. n8n will execute the entire pipeline — extract, transform, load, and error handling — on the interval you defined. The n8nautomation.cloud dashboard also provides built-in execution logs so you can review past runs, inspect output, and debug any failures without SSH access to a server.
Production Considerations for n8n ETL Pipelines
Before you deploy this pipeline to production, keep these points in mind:
- Rate limiting: If the source API enforces rate limits, add a Wait node between the HTTP Request and Code nodes to throttle your requests.
- Incremental loading: Use a PostgreSQL node to fetch the last
processed_attimestamp from your target table and pass it as a query parameter to the API. This avoids re-processing the same records. - Data volume: For pipelines processing thousands of records, enable Split in Batches before the PostgreSQL node to insert data in chunks rather than all at once.
- Backups: If you are running on a managed platform, automatic backups protect your workflow configurations and execution history. On n8nautomation.cloud, backups run automatically so you never lose your pipeline definitions.
ETL does not require a dedicated data engineering team or a six-month rollout. With n8n's HTTP Request node for extraction, the Code node for transformation, and native database connectors for loading, you can build reliable data pipelines in a single afternoon. The visual builder makes maintenance straightforward, and features like the Error Trigger node and cron scheduling close the gap between n8n and traditional ETL tools. For teams that want managed uptime and automatic backups, a dedicated n8n instance keeps your pipelines running without the operational overhead of self-hosting.