npm package: @functionkit/functions-framework
Description: fnkit Functions Framework for Node.js — invoke functions via MQTT topics instead of HTTP
Author: functionkit
Latest version: 0.1.1

# fnkit — MQTT Functions Framework for Node.js

An open source FaaS (Function as a Service) framework for writing portable Node.js functions invoked via **MQTT messages** instead of HTTP requests.

Adapted from the [Google Cloud Functions Framework for Node.js](https://github.com/GoogleCloudPlatform/functions-framework-nodejs).

## How It Works

Instead of standing up an HTTP server, fnkit connects to an MQTT broker and subscribes to a topic based on your function name. When a message arrives, your function is invoked with familiar `(req, res)` arguments.

```
MQTT Message → Broker → fnkit subscribes → Your Function(req, res) → Optional MQTT Response
```

## Quick Start

### 1. Create your function

```js
// index.js
const fnkit = require('@functionkit/functions-framework')

fnkit.mqtt('hello', (req, res) => {
  console.log('Received:', req.body)
  res.send({ message: `Hello, ${req.body.name || 'World'}!` })
})
```

### 2. Install the framework

```sh
npm install @functionkit/functions-framework
```

### 3. Run it

```sh
npx fnkit --target=hello --broker=mqtt://localhost:1883
```

Your function is now listening on topic `fnkit/hello`. Publish a message to that topic and your function will be invoked.

### 4. Test it

```sh
# In another terminal, publish a message
mosquitto_pub -h localhost -t fnkit/hello -m '{"name": "Max"}'
```

## Function Signature

fnkit uses the same `(req, res)` pattern as the HTTP Functions Framework:

```js
fnkit.mqtt('myFunction', (req, res) => {
  // req — MQTT request object
  // res — MQTT response object
})
```

### Request Object (`req`)

| Property          | Type                     | Description                                                      |
| ----------------- | ------------------------ | ---------------------------------------------------------------- |
| `topic`           | `string`                 | The full MQTT topic the message was received on                  |
| `payload`         | `Buffer`                 | The raw message payload                                          |
| `body`            | `any`                    | Parsed payload (JSON if possible, otherwise raw string)          |
| `params`          | `Record<string, string>` | Topic segments beyond the function name (wildcard subscriptions) |
| `properties`      | `object`                 | MQTT v5 user properties (analogous to HTTP headers)              |
| `headers`         | `object`                 | Alias for `properties`                                           |
| `correlationData` | `Buffer`                 | MQTT v5 correlation data                                         |
| `responseTopic`   | `string`                 | MQTT v5 response topic                                           |
| `contentType`     | `string`                 | MQTT v5 content type                                             |
| `messageId`       | `number`                 | MQTT packet identifier                                           |
| `qos`             | `0 \| 1 \| 2`            | QoS level of the received message                                |
| `retain`          | `boolean`                | Whether the message was retained                                 |
| `executionId`     | `string`                 | Unique execution ID for this invocation                          |

### Response Object (`res`)

| Method            | Description                                                                      |
| ----------------- | -------------------------------------------------------------------------------- |
| `send(data)`      | Publish response. Accepts `string`, `Buffer`, or `object` (auto JSON-serialized) |
| `json(obj)`       | Publish JSON response (sets content type)                                        |
| `status(code)`    | Set a status code in response user properties (chainable)                        |
| `set(key, value)` | Set an MQTT v5 user property on the response (chainable)                         |
| `end()`           | Signal completion without sending a response                                     |

### Fire-and-Forget

Functions don't have to send a response. Just process the message and return:

```js
fnkit.mqtt('logger', (req, res) => {
  console.log('Event received:', req.body)
  // No res.send() needed — fire and forget
})
```

### Async Functions

Async functions are fully supported:

```js
fnkit.mqtt('process', async (req, res) => {
  const result = await doSomethingAsync(req.body)
  res.send(result)
})
```

## Response Topics

Responses are published to (in order of precedence):

1. **MQTT v5 Response Topic** — If the incoming message has a `responseTopic` property
2. **Default** — `fnkit/{functionName}/response`

Correlation data from the incoming message is automatically forwarded.

## Configuration

Configure via CLI flags or environment variables. CLI flags take precedence.

| CLI Flag                | Environment Variable       | Description                               | Default                 |
| ----------------------- | -------------------------- | ----------------------------------------- | ----------------------- |
| `--broker`              | `MQTT_BROKER`              | MQTT broker URL (`mqtt://` or `mqtts://`) | `mqtt://localhost:1883` |
| `--target`              | `FUNCTION_TARGET`          | Name of the function to invoke            | `function`              |
| `--topic-prefix`        | `MQTT_TOPIC_PREFIX`        | Topic prefix before function name         | `fnkit`                 |
| `--source`              | `FUNCTION_SOURCE`          | Path to function source code              | cwd                     |
| `--qos`                 | `MQTT_QOS`                 | Default QoS level (0, 1, 2)               | `1`                     |
| `--client-id`           | `MQTT_CLIENT_ID`           | MQTT client identifier                    | auto-generated          |
| `--username`            | `MQTT_USERNAME`            | Broker username                           | —                       |
| `--password`            | `MQTT_PASSWORD`            | Broker password                           | —                       |
| `--ca`                  | `MQTT_CA`                  | Path to CA certificate (TLS)              | —                       |
| `--cert`                | `MQTT_CERT`                | Path to client certificate (mTLS)         | —                       |
| `--key`                 | `MQTT_KEY`                 | Path to client private key (mTLS)         | —                       |
| `--reject-unauthorized` | `MQTT_REJECT_UNAUTHORIZED` | Reject unauthorized TLS certs             | `true`                  |

### package.json Configuration

```json
{
  "scripts": {
    "start": "fnkit --target=hello --broker=mqtt://localhost:1883"
  }
}
```

## Security

### TLS (Port 8883)

```sh
fnkit --target=hello --broker=mqtts://broker:8883 --ca=/path/to/ca.crt
```

### mTLS (Mutual TLS)

```sh
fnkit --target=hello \
  --broker=mqtts://broker:8883 \
  --ca=/path/to/ca.crt \
  --cert=/path/to/client.crt \
  --key=/path/to/client.key
```

### Username/Password

```sh
fnkit --target=hello --broker=mqtt://broker:1883 --username=user --password=pass
```

## MQTT v5 Features

fnkit uses MQTT v5 by default, enabling:

- **Response Topics** — Request-reply pattern via `responseTopic` property
- **Correlation Data** — Match responses to requests
- **User Properties** — Key-value metadata on messages (like HTTP headers)
- **Content Type** — Indicate payload format

## Comparison with HTTP Functions Framework

| Aspect       | HTTP Framework                        | fnkit                                      |
| ------------ | ------------------------------------- | ------------------------------------------ |
| Transport    | HTTP server (Express)                 | MQTT client (subscribes to broker)         |
| Routing      | URL path → function                   | Topic `fnkit/{name}` → function            |
| Request      | Express `req` (URL, headers, body)    | `MqttRequest` (topic, properties, payload) |
| Response     | Express `res` (status, headers, body) | `MqttResponse` (publish to reply topic)    |
| Invocation   | Per HTTP request                      | Per MQTT message                           |
| Signature    | `(req, res) => {}`                    | `(req, res) => {}` ✅ Same pattern!        |
| Registration | `functions.http('name', handler)`     | `fnkit.mqtt('name', handler)`              |
| Config       | `--port`, `--target`                  | `--broker`, `--target`, `--topic-prefix`   |

## Architecture

See the [design docs](https://github.com/functionkit/function-framework-nodejs/tree/main/design) for:

- Architecture — System overview and components
- Contract — The MQTT Functions Framework contract (language-agnostic)
- Decisions — Design decisions and rationale

## Based On

This framework is adapted from the [Google Cloud Functions Framework for Node.js](https://github.com/GoogleCloudPlatform/functions-framework-nodejs) (Apache-2.0 License). The function registry, loader, and options parsing patterns are reused. The HTTP transport layer has been replaced with MQTT.

## License

Apache-2.0 — See [LICENSE](LICENSE) for details.