Skip to content
Text Post API

Module 01 · foundations · beginner

Express vs n8n · Text Post API

The same "create post" API built twice: once as a plain Express server, once as an n8n workflow. Both follow the same contract and pass the same test script, so you can compare them line by line.

The contract

Request Response
POST /posts {"author":"Ian","text":"Shipped my first n8n API"} 201 {"id":"...","author":"Ian","text":"Shipped my first n8n API","length":24,"createdAt":"..."}
POST /posts {"text":"First post from n8n"} 201 with "author":"anonymous"
POST /posts {}, {"text":""} or a text over 280 characters 400 {"error":"text must be 1-280 characters"}

Express version

server.js, about 15 lines:

const express = require('express');
const app = express();
let nextId = 1;

app.use(express.json());

app.post('/posts', (req, res) => {
  const { author, text } = req.body ?? {};
  if (!text || text.length > 280) return res.status(400).json({ error: 'text must be 1-280 characters' });
  res.status(201).json({
    id: String(nextId++),
    author: author || 'anonymous',
    text,
    length: text.length,
    createdAt: new Date().toISOString(),
  });
});

app.listen(3000, () => console.log('http://localhost:3000/posts'));

package.json needs one dependency ("express": "^5.1.0") and a start script (node server.js). Run it with npm install && npm start.

n8n version

Six nodes, no server file:

Webhook (POST /posts) → Valid Post? ─true─→ Build Post  → Respond Created (201)
                                    └false─→ Build Error → Respond Error (400)
Node Settings
Webhook POST, path posts, respond using a Respond to Webhook node
Valid Post? (IF, AND) {{ $json.body.text }} is not empty, and {{ ($json.body.text || '').length }} ≤ 280
Build Post (Edit Fields) id = {{ $execution.id }}, author = {{ $json.body.author || 'anonymous' }}, text, length = {{ $json.body.text.length }}, createdAt = {{ $now.toISO() }}
Respond Created Respond with JSON {{ $json }}, response code 201
Build Error (Edit Fields) error = text must be 1-280 characters
Respond Error Respond with JSON {{ $json }}, response code 400

The full step-by-step build is in the lesson.

Line by line

Job Express n8n
Start a server app.listen(3000) n8n is already running
Route POST /posts app.post('/posts', …) Webhook node, path posts
Parse the JSON body app.use(express.json()) Automatic; the body lands in $json.body
Require a text !text Valid Post? condition 1, is not empty
Check the length text.length > 280 Valid Post? condition 2, ≤ 280
Default the author author || 'anonymous' Same expression in Build Post
Give the post an id nextId++ counter $execution.id
Build the reply Object literal in res.json(…) Build Post / Build Error Edit Fields nodes
Set the status code res.status(201) / res.status(400) Response code on each Respond node
Send the reply res.json(…) One Respond to Webhook node per branch

Same test, both pass

./test/test.sh http://localhost:3000          # Express
./test/test.sh http://localhost:5678/webhook  # n8n (published workflow)

Each run prints five PASS lines: valid post, default author, missing text, empty text, too long.

Gotchas each side hides

  • Empty string: !text rejects "" for free. In n8n, the IF operator exists lets "" through; you need is not empty.
  • Missing fields in expressions: Express's !text check runs first, so text.length is only read when there is a text. In n8n both IF conditions are evaluated, so the length check needs ($json.body.text || '') or it fails on a missing text.
  • Every branch must respond: in Express, forgetting res.json hangs the request, and so does a branch without a Respond node in n8n. In n8n it's easier to miss, because the canvas looks finished.
  • Status codes live on the Respond node: set 400 on the wrong Respond node and your success path returns 400, or leave the default and a created post returns 200 instead of 201.
  • Content-Type: application/json is required in both: without it, Express leaves req.body empty and n8n leaves $json.body.text undefined. Both then answer 400.

n8n vs custom code: pros and cons

n8n

Pros

  • No server, routing or body parsing to write or deploy; the API exists as soon as the workflow is published.
  • Visual: a client or teammate can read the flow on the canvas without reading code.
  • Execution history shows every request's input and output, which makes debugging fast.
  • 400+ integrations (Sheets, Gmail, CRMs, AI) are a node away, so saving the post to a sheet or posting it to Slack is one more node.
  • Non-developers can change rules, like the character limit or the error message, without a deploy.

Cons

  • Logic is spread across node settings, so code review, diffs and refactors are harder than in a file.
  • Versioning means exporting workflow JSON; there is no natural Git workflow.
  • Testing is end to end (like test.sh); there are no unit tests for a single node.
  • Each request runs a workflow execution, which is slower and heavier than a plain route under high traffic.
  • You depend on n8n's hosting, upgrades and node behavior; a version bump can change how a node works.

Custom code (Express)

Pros

  • Full control over performance, error handling, middleware, auth and response shape.
  • Lives in Git with code review, diffs, unit tests and CI.
  • Runs anywhere Node runs, at very low cost per request.
  • Easy to grow into a real application with a database, users and many routes.

Cons

  • You write and maintain the plumbing: server, parsing, validation, logging, deployment and uptime.
  • Every integration (Gmail, Sheets, CRMs) is an SDK to learn, configure and keep updated.
  • Non-developers can't change it; every tweak is a code change and a deploy.
  • Visibility into individual requests needs logging you set up yourself.

When to pick which

  • Pick n8n when the API is glue between services, changes often, or will be maintained by someone who doesn't code.
  • Pick custom code when the API is core product logic, needs high throughput or strict testing, or is going to grow into an application.
  • Both is common: a custom app for the core product, with n8n handling the integrations and automations around it.