@inteli.city/node-red-contrib-http-plus 1.0.2

Enhanced HTTP nodes for Node-RED with built-in authentication, request validation, and caching.

npm install @inteli.city/node-red-contrib-http-plus

@inteli.city/node-red-contrib-http+

Enhanced HTTP nodes for Node-RED with built-in authentication, request validation, and caching.


What is this?

@inteli.city/node-red-contrib-http+ provides enhanced versions of Node-RED's HTTP nodes with built-in authentication, validation, and improved request handling.

These nodes are designed to be compatible with the standard Node-RED HTTP flow while adding capabilities commonly implemented manually in flows.


Key differences from standard HTTP nodes

Feature Standard nodes http+ nodes
Authentication Not built-in Built-in (Basic + Cognito)
Request validation Manual (function nodes) Built-in (Zod)
File upload handling Basic Structured (msg.files)
Data normalization Manual Built-in via Zod transforms
Streaming responses Limited/manual First-class support
Backend caching Not available Built-in (http.in+)
Browser cache control Manual headers Built-in (http.out+)

Compatibility

  • http.in+ and http.out+ follow the same flow model as the standard nodes
  • http.request+ is a drop-in replacement for the standard http request node
  • Existing flows can be migrated incrementally
Standard:  http in ──→ function ──→ http response
With http+: http.in+ ──→ (validated + authenticated) ──→ http.out+

When to use http+ nodes

Use http+ when you need:

  • Authentication at the HTTP boundary
  • Input validation without extra function nodes
  • Cleaner and more predictable request handling

Use standard nodes when:

  • You need minimal setup
  • You are prototyping quickly without constraints

Index


Nodes

Node Role
http.in+ Receives HTTP requests. Runs auth, Zod validation, and optional backend caching before sending msg downstream.
http.request+ Makes outgoing HTTP requests. Drop-in replacement for the standard http request node.
http.out+ Sends the HTTP response back to the caller. Optionally controls browser caching via Cache-Control.

Install

cd ~/.node-red
npm install @inteli.city/node-red-contrib-http+

Core Concepts

Message properties

Property Contains
msg.payload Request body (POST/PUT/PATCH) or query object (GET)
msg.req.query Query string as object — GET /search?term=abc{ term: "abc" }
msg.req.params Route parameters — GET /users/42{ id: "42" }
msg.req.headers Request headers
msg.validated Parsed + validated object from Zod (only when validation passes)
msg.user Mapped user identity. For Basic Auth: the username string. For Cognito: fields mapped from the JWT payload (only when "Expose user to flow" is enabled).
msg.res Response handle — passed to http.out+ to send the reply

Basic flow

http.in+ ──→ [your logic] ──→ http.out+
inject ──→ http.request+ ──→ debug

Caching

http+ supports two distinct caching mechanisms that operate at different layers and solve different problems.

Feature Backend cache (http.in+) Browser cache (http.out+)
Layer Server Client (browser)
When applied Before flow execution After response is sent
Effect Skips the flow entirely on a hit Browser may reuse the response
Default Disabled Disabled
Risk Serving stale or incorrect data Stale data in the browser

They can be used independently or combined.

How they work

Backend cache runs at the entry point of the flow. If a valid cached response exists for the incoming request, it is returned immediately — no downstream node runs.

Browser cache runs at the response level. It tells the client how long to keep the response locally. The flow still runs on every server request; the browser decides whether to send the request at all.


Backend cache (http.in+)

Caches full HTTP responses on the server. Repeated identical GET requests are served from cache without executing any downstream nodes.

Active only for GET requests. The Cache field controls the scope:

Mode Who shares the cache Auth required
Disabled
Public All requests with the same URL + query No
Per-user Each authenticated user has their own entry Yes
By claim Users sharing the same value for a chosen identity field Yes

Cache key — derived from URL path + sorted query parameters + identity (for per-user and by-claim modes). Headers, cookies, and request body are not part of the key.

Storage — responses are stored on disk:

<userDir>/cache/http+/

The directory is created automatically if it does not exist.

TTL — controlled by the "Cache duration (s)" field. Expired entries are ignored and replaced on the next request.

Observability — every cached response includes:

X-Cache: HIT         →  served from cache; flow was skipped
X-Cache: MISS        →  cache was empty or expired; flow ran
X-Cache-Scope: public | user | claim:<field>

Examples:

GET /products          → Public cache (shared across all callers)
GET /profile           → Per-user cache (isolated per authenticated user)
GET /dashboard         → By claim: tenantId (shared per tenant, isolated between tenants)

Browser cache (http.out+)

Instructs the browser to store the response locally and reuse it for subsequent requests. The server still handles every request that reaches it — the browser controls whether a request is sent at all.

What it does:

When enabled, http.out+ adds the following header to every response:

Cache-Control: public, max-age=<duration>

Override rule: if msg.headers['cache-control'] is set anywhere in the flow, it takes precedence over the node setting. This allows per-request control without changing node configuration.

Example:

[http.in+  GET /logo.png] ──→ [read file] ──→ [http.out+  browser cache: 86400 s]

The browser caches the image for 24 hours. On subsequent page loads, no network request is made.

⚠️ When NOT to use browser cache

  • Dynamic responses that change between requests
  • User-specific or session-specific data
  • Any endpoint where a stale cached response would cause incorrect behavior

Using both together

Backend cache and browser cache can be combined on the same endpoint:

[http.in+  GET /summary  cache: 60 s] ──→ [compute] ──→ [http.out+  browser cache: 60 s]
  • The server caches the response for 60 seconds — the flow is skipped on repeated hits.
  • The browser also caches it for 60 seconds — the request may not leave the client at all.

Align both TTLs to avoid a situation where the browser caches a response longer than the server would serve the same stale version.

For highly dynamic data, leave both disabled.


Authentication

Configure authentication by creating an http.auth.config+ config node and attaching it to http.in+.

Type Behaviour
None All requests pass through
Basic Auth Validates Authorization: Basic … header. Browser login popup triggered automatically via WWW-Authenticate. Supports single-user and multi-user modes (see below).
Cognito JWT Validates a Bearer token against a JWKS endpoint. Token accepted from Authorization: Bearer <token> or ?token=. JWKS keys are cached for 1 hour.

Failed authentication returns 401 and stops the flow.

Basic Auth modes

Single-user (default): Enter a username and password directly in the config node. Credentials are stored securely via Node-RED's credentials system (not in the exported flow).

Multiple users (JSON): Enable the "Use multiple users" option and provide a JSON object mapping usernames to passwords:

{
  "admin": "password123",
  "user": "abc123"
}

Warning: Passwords in JSON mode are stored in plain text in the flow configuration. Use only in trusted environments.

JWKS caching

Cognito public keys (JWKS) are cached in memory to reduce latency and avoid repeated requests to AWS.

  • Cache duration: 1 hour
  • Keys are cached per kid (key ID)
  • A new key is fetched automatically if an unknown kid is received

This improves performance while still supporting key rotation. If AWS rotates signing keys, new keys will be picked up automatically after cache expiration or when a new kid appears.

Cognito user mapping

After successful JWT validation, the Authorization header and ?token= query parameter are always removed from the request before it enters the flow.

To expose user identity in msg.user, enable "Expose user to flow" in the config node and provide a JSON mapping:

{
  "id": "sub",
  "email": "email",
  "roles": "cognito:groups"
}

Keys are the output field names in msg.user; values are the JWT claim names to read from. Fields missing from the token are set to undefined.

If "Expose user to flow" is disabled, msg.user is not set — no JWT data propagates into the flow.

Request sanitization

After successful authentication, credentials are removed from the request before dispatch:

Auth type Removed
Basic Auth msg.req.headers.authorization
Cognito JWT msg.req.headers.authorization, msg.req.query.token

This prevents tokens and passwords from leaking into downstream nodes.


Validation with Zod

http.in+ can validate every incoming request before passing it downstream.

Enabling validation

  1. Open the http.in+ node editor.
  2. Check Enable Zod validation.
  3. Enter a Zod schema expression in the Schema textarea.

The schema receives a single object:

{
  body:   // request body (msg.payload)
  query:  // query string parameters (msg.req.query)
  params: // route parameters (msg.req.params)
  files:  // uploaded file metadata (when file upload is enabled)
}

Use the appropriate field depending on the HTTP method:

Method Use
GET, DELETE query, params only — no body
POST, PUT, PATCH body, query, params

Defining body in a GET or DELETE schema is ignored at runtime. A warning is logged at deploy time.

Full usage guidance (method table, type coercion, Swagger behavior) is available inline in the node editor UI.

If validation passes, msg.validated contains the parsed result and the flow continues normally.

If validation fails, the node returns HTTP 400 immediately — no downstream node is executed:

{
  "error": "invalid_request",
  "details": [
    { "code": "invalid_type", "path": ["body", "age"], "message": "Expected number, received string" }
  ]
}

Important: query and param values are always strings

HTTP query strings and route params arrive as strings. Use z.coerce to convert them:

z.coerce.number()   // "42" → 42
z.coerce.boolean()  // "true" → true

Transforms and normalization

Zod can transform values as part of validation — trimming whitespace, converting case, coercing types, etc. The transformed result is what ends up in msg.validated. msg.payload always remains the original, unmodified request body.

z.object({
  query: z.object({
    term: z.string().trim().toLowerCase(),
    page: z.coerce.number().int().min(1)
  })
})

The flow receives msg.validated.query.term already trimmed and lowercased — no manual cleanup needed.

File validation

Zod validates file metadata (mime type, size, etc.), not file content. Files from the upload are available in msg.files as an array — validate the first entry or a named field:

z.object({
  files: z.object({
    avatar: z.object({
      mimetype: z.enum(["image/png", "image/jpeg"]),
      size: z.number().max(2_000_000)
    })
  })
})

Examples

Example 1 — Body validation (POST)

Route: POST /users

Schema:

z.object({
  body: z.object({
    name: z.string().min(1),
    age:  z.number().int().min(0)
  })
})

Valid request body:

{ "name": "Alice", "age": 30 }

Invalid — returns 400:

{ "name": "", "age": -1 }

Example 2 — Query validation (GET)

Route: GET /search?term=node-red&page=2

Schema:

z.object({
  query: z.object({
    term: z.string().min(1),
    page: z.coerce.number().int().min(1).optional()
  })
})

page arrives as a string from the URL — z.coerce.number() converts it automatically.


Example 3 — Route parameter validation

Route: GET /users/:id

Schema:

z.object({
  params: z.object({
    id: z.string().uuid()
  })
})

Rejects any request where :id is not a valid UUID before your logic runs.


Example 4 — Combined: params + query + body

Route: PATCH /users/:id?notify=true

Schema:

z.object({
  params: z.object({
    id: z.string().uuid()
  }),
  query: z.object({
    notify: z.coerce.boolean().optional()
  }).optional(),
  body: z.object({
    email: z.string().email().optional(),
    role:  z.enum(["admin", "user", "viewer"]).optional()
  })
})

msg.validated will contain the fully parsed and typed object for use downstream.


Validation error format

{
  "error": "invalid_request",
  "details": [
    {
      "code":    "invalid_type",
      "path":    ["body", "age"],
      "message": "Expected number, received string"
    },
    {
      "code":    "too_small",
      "path":    ["body", "name"],
      "message": "String must contain at least 1 character(s)"
    }
  ]
}

details is the raw Zod ZodError.errors array. Each entry contains a path array indicating exactly which field failed.


Best practices

Scenario Recommendation
Required identifiers Use route params (:id) with z.string().uuid() or similar
Filters and options Use query string with z.coerce for number/boolean
Structured input data Use request body (body) — POST/PUT/PATCH only
GET / DELETE filtering Use query — never body
All query/param values Always use z.coerce.* — they arrive as strings
Optional sections Wrap entire query or params in .optional() if the route doesn't always have them
Schema errors at deploy The node logs the error and disables validation — it does not crash
Swagger visibility Only nodes with a valid Zod schema appear in /docs

Swagger / OpenAPI

http.in+ nodes with a valid Zod schema are automatically registered in an OpenAPI 3.0 spec. No extra configuration is required.

Endpoint Returns
GET /openapi.json Raw OpenAPI spec (JSON)
GET /docs Swagger UI

Only endpoints where Zod validation is enabled and the schema compiled successfully appear in the spec. The spec updates automatically on each deploy — no stale entries.

For schema field usage, method rules, and type coercion guidance, see the inline help panel in the node editor.


File uploads

http.in+ supports multipart (multipart/form-data) file uploads on POST routes. The upload option is not available for GET, PUT, PATCH, or DELETE.

If file upload is enabled, uploaded files are available in msg.files — they are not included in msg.payload.

Enabling uploads

  1. Open the http.in+ node editor.
  2. Check Enable file uploads.
  3. Choose Storage: Memory (buffer) or Disk (temp files).
  4. Set Max size (MB) (default: 5 MB).
  5. For disk storage, optionally set a Temp dir (defaults to the OS temp directory).

msg.files

When files are uploaded, msg.files is an array of objects:

Property Present Contains
fieldname always Form field name used for the upload
originalname always Original filename from the client
mimetype always MIME type (e.g. image/png)
size always File size in bytes
storage always "memory" or "disk"
buffer memory only Buffer containing the file data
path disk only Absolute path to the saved file

Size limit

If a file exceeds the configured limit, the node returns 413 immediately:

{ "error": "file_too_large" }

Disk storage — cleanup

When using disk storage, files are written to the temp directory and not deleted automatically. Your flow is responsible for removing them after use (e.g. via a function node calling fs.unlink).


http.request+

Drop-in replacement for the standard http request node with identical behaviour.

Input message properties used:

Property Effect
msg.url Target URL (overrides node config)
msg.method HTTP method (when node is set to "use msg.method")
msg.payload Request body
msg.headers Additional request headers

Output message properties set:

Property Contains
msg.payload Response body
msg.statusCode HTTP status code
msg.headers Response headers
msg.responseUrl Final URL after redirects
msg.redirectList List of redirects followed

Supports TLS config, proxy config (http.proxy+), Basic/Digest/Bearer auth, and persistent connections.


http.out+

Sends the HTTP response back to the original caller. Must be connected to the same flow started by http.in+.

Controlling the response via msg:

Property Effect
msg.payload Response body
msg.statusCode HTTP status code (default: 200)
msg.headers Extra response headers
msg.cookies Cookies to set or clear

If msg.payload is an object, the response is sent as JSON automatically.

Browser caching

http.out+ can automatically set browser cache headers without requiring a function node.

Enable Enable browser cache in the node editor and set Cache duration (s). The node will add:

Cache-Control: public, max-age=<duration>

This does not affect server-side execution — the flow always runs when a request reaches the server. The browser uses the header to decide whether to skip the request entirely on subsequent calls.

If msg.headers['cache-control'] is set anywhere in the flow, it overrides the node setting. This allows per-request cache control without changing node configuration.

Use for static or infrequently-changing responses (images, scripts, reference data). Avoid for user-specific or time-sensitive responses.

For a full explanation including TTL guidance, safety rules, and combining with backend cache, see the Caching section.

Streaming responses

You can stream a response instead of sending a buffer. Useful for large files or when proxying a stream from another source.

Set msg.stream to any readable Node.js stream:

msg.stream = fs.createReadStream("/tmp/file.zip");

If msg.stream is set, the stream is piped directly to the response. If it is not set, msg.payload is sent normally.

msg.headers and msg.statusCode behave the same in both modes.


File structure

httpproxy+.js/.html         — http.proxy+ config node (proxy for http.request+); includes proxy resolution utility
httpin+.js/.html            — http.in+ and http.out+ nodes
httprequest+.js/.html       — http.request+ node
http-auth-config+.js/.html  — http.auth.config+ config node
libs/swagger.js             — OpenAPI spec generation and /docs + /openapi.json route registration

Node Info

Version: 1.0.2
Updated 1 day ago
License: Apache-2.0
Rating: not yet rated

Categories

Actions

Rate:

Downloads

125 in the last week

Nodes

  • http proxy+
  • http.auth.config+
  • http.in+
  • http.out+
  • http.request+

Keywords

  • node-red

Maintainers