npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

v2.0.0

Published

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

Readme

@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 with Nunjucks-templated headers and body, request queuing, and optional auth. | | http.out+ | Sends the HTTP response back to the caller. Optionally controls browser caching via Cache-Control. | | http.auth.out+ | Config node — manages outgoing authentication (Basic Auth, JWT from endpoint) for http.request+. |

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+)

In the node editor, backend cache is labelled "Server cache" to distinguish it from browser cache.

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>/.runtime_data/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 /docs/openapi.json | Raw OpenAPI spec (JSON) | | GET /docs | Redirects to /docs/ | | 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.

What gets documented

| Zod key | OpenAPI output | |---------|----------------| | body | requestBody → reusable schema in components.schemas | | files (uploads enabled) | requestBody multipart → reusable schema in components.schemas | | query | parameters (in: query) — inline, not a reusable schema | | params | parameters (in: path) — inline, not a reusable schema |

body and files schemas are promoted to components.schemas so Swagger UI displays them under the Schemas section and operations link back to them via $ref. Query and path parameters stay as inline parameter objects — converting them into shared schemas would distort their HTTP semantics without any benefit.

Schema naming

Schema names are derived from the HTTP method and path — they are stable and deterministic across restarts and redeploys:

POST  /users              → PostUsersBody
POST  /users/{id}/avatar  → PostUsersIdAvatarForm   (file upload)
PUT   /products/:name     → PutProductsNameBody
PATCH /                   → PatchRootBody

Type representation

Schemas always reflect post-validation (semantic) types, not the raw HTTP transport type:

z.coerce.number()   → { type: "number" }   (not "string")
z.coerce.boolean()  → { type: "boolean" }  (not "string")

Full example

Route: POST /users

Zod schema:

z.object({
  body: z.object({
    name:  z.string(),
    email: z.string(),
    age:   z.coerce.number().optional()
  }),
  query: z.object({
    dry_run: z.coerce.boolean().optional()
  }),
  params: z.object({})
})

Generated /docs/openapi.json (simplified):

{
  "paths": {
    "/users": {
      "post": {
        "parameters": [
          {
            "name": "dry_run",
            "in": "query",
            "required": false,
            "schema": { "type": "boolean" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PostUsersBody" }
            }
          }
        },
        "responses": { "200": { "description": "OK" } }
      }
    }
  },
  "components": {
    "schemas": {
      "PostUsersBody": {
        "type": "object",
        "properties": {
          "name":  { "type": "string" },
          "email": { "type": "string" },
          "age":   { "type": "number" }
        },
        "required": ["name", "email"]
      }
    }
  }
}

age is absent from required because it is .optional(). dry_run is a query parameter, not a schema entry. Swagger UI renders PostUsersBody in the Schemas section and links to it from the operation.

File upload example

Route: POST /documents (file uploads enabled)

Zod schema:

z.object({
  files: z.object({
    document: z.any(),
    thumbnail: z.any().optional()
  }),
  body: z.object({
    title: z.string()
  })
})

Generated schema (PostDocumentsForm):

{
  "type": "object",
  "properties": {
    "document":  { "type": "string", "format": "binary" },
    "thumbnail": { "type": "string", "format": "binary" },
    "title":     { "type": "string" }
  },
  "required": ["document", "title"]
}

File and body fields are merged into a single multipart/form-data schema. File fields are always represented as { type: "string", format: "binary" }.

What does NOT appear in the spec

  • Endpoints with Zod disabled
  • Endpoints where the schema failed to compile
  • Response schemas (out of scope — http.out+ does not guarantee response shape)
  • Query and path parameter schemas (they remain as parameters)

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+

Makes outgoing HTTP requests with Nunjucks-templated headers and body, configurable concurrency, and optional outgoing authentication via http.auth.out+.

Inputs

| Property | Type | Description | |----------|------|-------------| | msg.url | string | Target URL — overrides the node URL when set. Supports Mustache syntax | | msg.method | string | HTTP method when the node is configured to use msg.method | | msg.stop | boolean | Set to true to cancel all running and queued requests |

Outputs

| Property | Type | Description | |----------|------|-------------| | msg.payload | string | object | buffer | Response body (format controlled by Return) | | msg.statusCode | number | HTTP response status code | | msg.headers | object | Response headers | | msg.responseUrl | string | Final URL after any redirects |

Templates

Body and Headers are Nunjucks templates rendered against the full msg object at runtime. Objects coerce to JSON automatically when interpolated — no | dump filter needed.

{{ payload }}               → msg.payload, JSON if it is an object
{{ payload.id }}            → nested scalar, raw value
{{ topic }}                 → msg.topic

Body

Sent for POST, PUT, and PATCH only. If the rendered output is valid JSON it is sent with Content-Type: application/json; otherwise as a plain string. Leave empty to send no body.

{{ payload }}
{"id": {{ payload.id }}, "event": "{{ topic }}"}

Headers

Must render to a JSON object. Use {} to send no custom headers.

Static authentication (Bearer tokens, API keys) belongs here:

{"Authorization": "Bearer {{ payload.token }}"}
{"X-API-Key": "abc123"}

Authentication

Attach an http.auth.out+ config node via the Auth field for authentication requiring lifecycle management (credential storage, automatic token refresh). For static tokens or keys with no lifecycle needs, define them in the Headers template directly.

Header merge order: auth-generated headers are applied first; Headers template values are applied after and take precedence. A user-defined Authorization header in the Headers template silently overrides the auth-generated one.

Concurrency and queue

The Queue setting (default: 1) controls how many requests run in parallel. Incoming messages beyond that limit are held in a FIFO queue.

To abort all pending and in-flight requests:

  • Send msg.stop = true
  • Click the kill button () in the node editor

http.auth.out+

Config node that manages outgoing authentication for http.request+. Attach it via the Auth field in the request node editor.

Authentication types

None

No authentication. No headers added.

Basic Auth

Stores username and password using Node-RED's secure credentials system. Adds on every request:

Authorization: Basic <base64(username:password)>

Credentials are never included in exported flow definitions.

JWT (from endpoint)

Fetches an access token from a token URL, caches it, and adds:

Authorization: Bearer <token>

| Field | Description | |-------|-------------| | Token URL | URL of the token endpoint | | Method | HTTP method for the token request (POST, GET, PUT) | | Username / Password | Stored securely via Node-RED credentials. Available in Body and Headers templates as credentials.username and credentials.password | | Body | Nunjucks template for the token request body (POST / PUT only). Context: msg + credentials.* | | Headers | Nunjucks template for token request headers. Must render to a JSON object | | Token field | Key to read from the token endpoint response (default: access_token) | | Fallback TTL | Cache duration in seconds when expires_in is absent or exceeds this value (default: 1200) |

Body template example:

{"username": "{{ credentials.username }}", "password": "{{ credentials.password }}"}

The credentials namespace is only available within JWT token request templates — it is not injected into the main request.

Token caching

Tokens are cached in memory per config node instance. Effective TTL is min(expires_in, fallback TTL) with a 30-second safety margin. Concurrent fetches are deduplicated — if multiple requests arrive while a fetch is in progress they all wait for the same result. Cache is reset on redeploy.

401 auto-refresh

On a 401 from the target API, the cache is invalidated and a new token is fetched. The request is retried once with the fresh token. If the retry also returns 401, or the token fetch fails, a warning is emitted via node.warn and the response is passed through unchanged.


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/ + /docs/openapi.json route registration