Back to Blog
IoT Engineering

Building a Production IoT Gateway with Raspberry Pi and Node.js

A Raspberry Pi sitting between your sensors and the cloud isn't just a bridge — it's a critical reliability layer that buffers data during outages, translates protocols, and reduces cloud egress costs. Here's how to build one that survives production.

February 19, 2024
12 min read
Raspberry PiIoT GatewayNode.jsMQTT

Building a Production IoT Gateway with Raspberry Pi and Node.js

A Raspberry Pi gateway is one of the most powerful architectural decisions you can make in an IoT deployment. It solves three hard problems simultaneously: protocol translation (BLE/Zigbee/Modbus sensors can't speak MQTT natively), offline resilience (data survives cloud outages), and bandwidth reduction (aggregate and compress locally, send summaries upstream).

This guide walks through a production-grade implementation used in smart building and agricultural deployments.

Gateway Architecture Overview

[BLE Sensors]   [Zigbee Sensors]   [Modbus PLCs]
       ↓                ↓                ↓
  noble (BLE)    zigbee2mqtt      modbus-serial
       ↓                ↓                ↓
   ┌─────────────────────────────────────┐
   │        Node.js Gateway Process       │
   │  ┌───────────┐  ┌────────────────┐  │
   │  │ Ingestor  │→ │ Local SQLite   │  │
   │  │ (multi-   │  │ Buffer         │  │
   │  │ protocol) │  └────────┬───────┘  │
   │  └───────────┘           │          │
   │                    ┌─────▼──────┐   │
   │                    │ Forwarder  │   │
   │                    │ (MQTT TLS) │   │
   │                    └─────┬──────┘   │
   └──────────────────────────┼──────────┘
                              ↓
                    AWS IoT Core / Cloud

The SQLite buffer is the key reliability mechanism. Data is written locally first, then forwarded to the cloud. If connectivity drops, the ingestor keeps writing to SQLite. When connectivity restores, the forwarder drains the buffer in order.

Node.js Gateway Core

// gateway.js — production IoT gateway core
const mqtt = require('mqtt')
const noble = require('@abandonware/noble')
const Database = require('better-sqlite3')
const fs = require('fs')

const CONFIG = { awsEndpoint: process.env.AWS_IOT_ENDPOINT, gatewayId: process.env.GATEWAY_ID || require('os').hostname(), bufferPath: '/var/lib/iot-gateway/buffer.db', certPath: '/etc/iot-gateway/certs', maxBufferRows: 50000, forwardBatchSize: 100, forwardIntervalMs: 2000, }

// ── Local SQLite buffer ────────────────────────────────────── const db = new Database(CONFIG.bufferPath) db.exec(` CREATE TABLE IF NOT EXISTS readings ( id INTEGER PRIMARY KEY AUTOINCREMENT, device_id TEXT NOT NULL, payload TEXT NOT NULL, ts INTEGER NOT NULL DEFAULT (unixepoch('now', 'subsec') * 1000), forwarded INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_forwarded ON readings(forwarded, id); `)

const insertReading = db.prepare( 'INSERT INTO readings (device_id, payload) VALUES (?, ?)' ) const getPending = db.prepare( 'SELECT id, device_id, payload, ts FROM readings WHERE forwarded = 0 ORDER BY id LIMIT ?' ) const markForwarded = db.prepare( 'UPDATE readings SET forwarded = 1 WHERE id IN (SELECT value FROM json_each(?))' ) const pruneForwarded = db.prepare( 'DELETE FROM readings WHERE forwarded = 1 AND id < (SELECT MAX(id) - 10000 FROM readings WHERE forwarded = 1)' )

function bufferReading(deviceId, data) { const count = db.prepare('SELECT COUNT(*) as c FROM readings WHERE forwarded = 0').get().c if (count >= CONFIG.maxBufferRows) { console.warn([buffer] Full (${count} rows), dropping oldest unforwarded) db.prepare('DELETE FROM readings WHERE id = (SELECT MIN(id) FROM readings WHERE forwarded = 0)').run() } insertReading.run(deviceId, JSON.stringify(data)) }

// ── AWS IoT Core MQTT client ───────────────────────────────── let cloudConnected = false const cloudClient = mqtt.connect(mqtts://${CONFIG.awsEndpoint}:8883, { cert: fs.readFileSync(${CONFIG.certPath}/device.crt), key: fs.readFileSync(${CONFIG.certPath}/device.key), ca: fs.readFileSync(${CONFIG.certPath}/AmazonRootCA1.pem), clientId: CONFIG.gatewayId, reconnectPeriod: 5000, keepalive: 30, })

cloudClient.on('connect', () => { cloudConnected = true console.log('[cloud] Connected to AWS IoT Core') }) cloudClient.on('offline', () => { cloudConnected = false console.warn('[cloud] Disconnected — buffering locally') })

// ── Forwarder: drain buffer to cloud ──────────────────────── function forwardBatch() { if (!cloudConnected) return

const rows = getPending.all(CONFIG.forwardBatchSize) if (!rows.length) return

let published = 0 const ids = []

for (const row of rows) { const topic = gateways/${CONFIG.gatewayId}/devices/${row.device_id}/telemetry const payload = JSON.stringify({ ...JSON.parse(row.payload), ts: row.ts, gatewayId: CONFIG.gatewayId })

cloudClient.publish(topic, payload, { qos: 1 }, (err) => { if (!err) published++ }) ids.push(row.id) }

markForwarded.run(JSON.stringify(ids)) if (published > 0) pruneForwarded.run() }

setInterval(forwardBatch, CONFIG.forwardIntervalMs)

BLE Protocol Bridge

// ble-ingestor.js — reads BLE advertisements from Nordic sensors
const noble = require('@abandonware/noble')

const KNOWN_DEVICES = { 'aa:bb:cc:dd:ee:ff': { name: 'soil-sensor-01', type: 'soil_moisture' }, 'aa:bb:cc:dd:ee:fe': { name: 'temp-sensor-01', type: 'temperature' }, }

noble.on('stateChange', (state) => { if (state === 'poweredOn') noble.startScanning([], true) // allow duplicates })

noble.on('discover', (peripheral) => { const addr = peripheral.address const device = KNOWN_DEVICES[addr] if (!device) return

const mfr = peripheral.advertisement.manufacturerData if (!mfr || mfr.length < 6) return

// Parse custom manufacturer payload (device-specific) const value = mfr.readInt16LE(2) / 100 const battery = mfr.readUInt8(4) const rssi = peripheral.rssi

bufferReading(device.name, { type: device.type, value, battery, rssi, unit: device.type === 'soil_moisture' ? '%' : '°C', }) })

Watchdog and Systemd Service

A gateway process that silently exits is worse than one that crashes loudly. Use Node.js's built-in watchdog pattern and systemd's Restart=always to guarantee automatic recovery.

/etc/systemd/system/iot-gateway.service

[Unit] Description=IoT Gateway After=network-online.target Wants=network-online.target

[Service] Type=simple User=iot WorkingDirectory=/opt/iot-gateway ExecStart=/usr/bin/node /opt/iot-gateway/gateway.js Restart=always RestartSec=5 WatchdogSec=60 NotifyAccess=main Environment=NODE_ENV=production Environment=AWS_IOT_ENDPOINT=your-endpoint.iot.us-east-1.amazonaws.com Environment=GATEWAY_ID=gateway-building-a-floor-2 StandardOutput=journal StandardError=journal SyslogIdentifier=iot-gateway

[Install] WantedBy=multi-user.target

The WatchdogSec=60 setting requires the process to call sd_notify(WATCHDOG=1) at least once per minute. Add this to the Node.js side:

// Systemd watchdog notification
if (process.env.WATCHDOG_USEC) {
  const intervalMs = parseInt(process.env.WATCHDOG_USEC) / 2000 // half the timeout
  const sd = require('@twooster/sd-notify')
  setInterval(() => sd.watchdog(), intervalMs)
}

Offline Buffering Strategy

The SQLite buffer provides durability guarantees that in-memory queues cannot. Key design decisions:

  • 1. Write-ahead logging (WAL): db.pragma('journal_mode = WAL') — concurrent reads don't block writes
  • 2. Synchronous mode: db.pragma('synchronous = NORMAL') — safe against OS crashes, fast enough for sensor rates
  • 3. Max buffer size: cap at 50,000 rows (~72 hours at 10 readings/minute) to prevent disk exhaustion
  • 4. FIFO draining: always forward oldest records first to maintain time-series order in the cloud
  • For a deeper look at how this data lands in cloud storage, see [IoT Data Pipeline: Sensor to Dashboard](/blog/iot-data-pipeline-sensor-to-dashboard).

    The gateway pattern also fits naturally within the [Edge-Cloud Hybrid architecture](/blog/edge-computing-iot-on-device-vs-cloud) discussed in our edge computing guide.

    Raspberry Pi Hardware Choices

    For production:

  • Pi 4 (4GB): enough RAM and CPU for simultaneous BLE scanning, Zigbee coordinator, and MQTT forwarding for up to 50 sensors
  • Industrial SD cards (SanDisk Max Endurance, Kingston Endurance): standard SD cards wear out within months under database write loads
  • UPS HAT (Witty Pi, PiJuice): prevents file system corruption during power loss
  • Aluminum enclosure with DIN rail mount: for industrial/panel installations
  • Need help building a production IoT gateway? [Contact Code Caracal](/contact) — we've shipped these systems for clients across 15+ countries.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    IoT Dashboard · 12 min read

    Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    Embedded Systems · 14 min read

    IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 10 min read

    MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    Embedded Systems · 14 min read

    Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

    IoT Engineering · 12 min read

    WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options