Software architecture does not stop at the web framework. For my projects, I prefer infrastructure that stays simple, readable, and reproducible. My foundation is mainly built around Docker, PostgreSQL, and n8n.
The goal is not to stack tools. Each component has a clear responsibility.
1. Docker: reproducible environments
Docker lets me describe the services required by a project instead of depending on each machine's configuration.
An environment might contain:
services:
api:
build: ./api
postgres:
image: postgres:16
n8n:
image: n8nio/n8n
The exact setup changes from project to project, but the principle remains the same: each service is isolated and its configuration can be versioned.
This greatly reduces the classic:
“It works on my machine.”
2. PostgreSQL: the data core
I use PostgreSQL as my default relational database for business applications.
I rely on:
- transactions;
- integrity constraints;
- indexes;
- relationships;
- JSONB when some data needs flexibility;
- mature ORM ecosystems.
Laravel can use Eloquent, Django its ORM, and a TypeScript application can use Prisma or Drizzle. The database remains the same: a reliable central data store.
3. n8n: automation without bloating the API
Not every operation needs to become a backend route.
Sending an email, synchronizing a CRM, notifying Slack, or processing a webhook can be orchestrated in n8n.
The API keeps critical responsibilities while n8n handles peripheral processes.
A separation I like
Frontend
│
▼
Backend API ───────► PostgreSQL
│
└───────────────► n8n
│
┌────────────┼────────────┐
▼ ▼ ▼
Email Slack CRM
The backend remains the source of truth. n8n becomes the orchestration layer.
Example: creating an order
An application can send:
POST /api/orders
The backend validates the order and stores it in PostgreSQL.
It can then trigger an n8n webhook:
{
"event": "order.created",
"orderId": 8421
}
n8n can then:
- retrieve order information;
- send a confirmation email;
- notify Slack;
- synchronize a CRM;
- record the workflow result.
The API does not need to know the details of every integration.
Why this architecture stays lightweight
I do not turn every function into a microservice.
Instead, I use:
- one main backend;
- one PostgreSQL database;
- n8n workflows for automation;
- Docker for execution.
This keeps the architecture understandable while allowing automation to grow progressively.
The important part: reliability
n8n should not become a black box.
For important workflows, I plan for:
- event identifiers;
- idempotency;
- logs;
- controlled retries;
- failure alerts;
- a strategy for partial executions.
The goal is simple: automate without losing control of the system.
