Getting Started with Node.js on Raspberry Pi

Ben
Ben
@benjislab

The Raspberry Pi's versatility combined with Node.js's efficiency makes for a powerful combination, perfect for IoT projects, home automation, web servers, and more. This comprehensive guide will walk you through everything you need to know about running Node.js on your Raspberry Pi, from installation to creating and deploying real-world applications.

Why Use Node.js on Raspberry Pi?

Node.js offers several advantages that make it particularly well-suited for Raspberry Pi development:

  • Lightweight runtime: Runs efficiently even on the Pi's limited resources
  • Asynchronous, non-blocking I/O: Perfect for handling multiple connections simultaneously
  • Vast package ecosystem: Thousands of npm packages available for virtually any functionality
  • JavaScript: Use the same language for frontend and backend development
  • GPIO library support: Easy hardware interfacing with libraries like rpi-gpio or onoff
  • Real-time capabilities: Ideal for IoT applications requiring immediate responses

Prerequisites

Before we begin, make sure you have:

  • A Raspberry Pi (any model, though Pi 3 or newer is recommended for better performance)
  • Raspberry Pi OS installed and updated (formerly Raspbian)
  • Internet connection
  • Basic familiarity with Linux commands
  • Optional: SSH access to your Raspberry Pi for headless setup

Installing Node.js on Raspberry Pi

There are several ways to install Node.js on your Raspberry Pi. We'll cover the most reliable methods.

Method 1: Using the Node Version Manager (NVM) (Recommended)

NVM allows you to install and manage multiple Node.js versions easily:

# Install dependencies
sudo apt update
sudo apt install -y curl build-essential libssl-dev

# Download and install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash

# Load NVM in the current shell
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

# Install the latest LTS version of Node.js
nvm install --lts

# Set the LTS version as default
nvm use --lts

# Verify installation
node -v
npm -v

Method 2: Using NodeSource Repository

NodeSource provides up-to-date Node.js versions via APT:

# Install dependencies
sudo apt update
sudo apt install -y curl

# Download and execute the NodeSource installation script (for Node.js 18 LTS)
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -

# Install Node.js
sudo apt install -y nodejs

# Verify installation
node -v
npm -v

Method 3: Using the Default Package Manager (Simplest but may not be latest version)

sudo apt update
sudo apt install -y nodejs npm

# Verify installation
node -v
npm -v

Setting Up Your First Node.js Project

Now that Node.js is installed, let's create a simple project:

# Create a project directory
mkdir my-pi-node-project
cd my-pi-node-project

# Initialize a new Node.js project
npm init -y

# Create a basic application file
touch app.js

Open app.js in your favorite text editor and add:

const http = require('http');

const hostname = '0.0.0.0'; // Listen on all network interfaces
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello from Raspberry Pi!\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

Run the application:

node app.js

Access your application by opening a browser and navigating to http://[your-raspberry-pi-ip]:3000.

Working with GPIO Pins

One of the most powerful aspects of using a Raspberry Pi is the ability to interact with physical hardware through GPIO pins.

First, install the GPIO library:

npm install onoff

Here's a simple example that toggles an LED connected to GPIO pin 17:

const { Gpio } = require('onoff');
 
// Initialize GPIO pin 17 as output
const led = new Gpio(17, 'out');
 
// Toggle LED every second
let ledState = 0;
setInterval(() => {
  ledState = ledState === 0 ? 1 : 0;
  led.writeSync(ledState);
  console.log(`LED state: ${ledState}`);
}, 1000);
 
// Clean up on program exit
process.on('SIGINT', () => {
  led.unexport();
  console.log('Exiting application and freeing GPIO resources');
  process.exit(0);
});

Save this as led-blink.js and run it with:

sudo node led-blink.js

Note: sudo is required for GPIO access unless you've configured your system to allow non-root GPIO access.

Reading Sensor Data

Here's how to read data from a DHT22 temperature and humidity sensor:

npm install node-dht-sensor
const sensor = require('node-dht-sensor');
 
// Initialize sensor (11 for DHT11, 22 for DHT22)
const sensorType = 22;
const sensorPin = 4; // GPIO pin number
 
setInterval(() => {
  if (sensor.read(sensorType, sensorPin)) {
    console.log(`Temperature: ${sensor.read(sensorType, sensorPin).temperature.toFixed(1)}°C`);
    console.log(`Humidity: ${sensor.read(sensorType, sensorPin).humidity.toFixed(1)}%`);
  } else {
    console.warn('Failed to read sensor data');
  }
}, 2000);

Creating a Web Server to Control Hardware

Let's combine web capabilities with hardware control by creating a server to control an LED:

npm install express
const express = require('express');
const { Gpio } = require('onoff');
 
const app = express();
const port = 3000;
 
// Initialize GPIO pin 17 as output
const led = new Gpio(17, 'out');
 
// Serve static files
app.use(express.static('public'));
 
// API endpoints
app.get('/api/led/on', (req, res) => {
  led.writeSync(1);
  res.json({ status: 'LED turned ON' });
});
 
app.get('/api/led/off', (req, res) => {
  led.writeSync(0);
  res.json({ status: 'LED turned OFF' });
});
 
app.get('/api/led/status', (req, res) => {
  const status = led.readSync();
  res.json({ status: status === 1 ? 'ON' : 'OFF' });
});
 
// Start server
app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
 
// Clean up on exit
process.on('SIGINT', () => {
  led.unexport();
  console.log('Exiting application and freeing GPIO resources');
  process.exit(0);
});

Create a public directory with an index.html file:

mkdir public
touch public/index.html

Add this code to index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Raspberry Pi LED Control</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 0 auto;
      padding: 20px;
      text-align: center;
    }
    .button {
      display: inline-block;
      padding: 10px 20px;
      margin: 10px;
      font-size: 16px;
      cursor: pointer;
      background-color: #4CAF50;
      color: white;
      border: none;
      border-radius: 4px;
    }
    .button.off {
      background-color: #f44336;
    }
    #status {
      font-size: 18px;
      margin: 20px 0;
    }
  </style>
</head>
<body>
  <h1>Raspberry Pi LED Control</h1>
  <div id="status">LED Status: Unknown</div>
  <button class="button" id="on">Turn ON</button>
  <button class="button off" id="off">Turn OFF</button>
  
  <script>
    const statusElement = document.getElementById('status');
    
    // Update status function
    function updateStatus() {
      fetch('/api/led/status')
        .then(response => response.json())
        .then(data => {
          statusElement.textContent = `LED Status: ${data.status}`;
        })
        .catch(error => {
          console.error('Error fetching status:', error);
        });
    }
    
    // Initial status update
    updateStatus();
    
    // Button event listeners
    document.getElementById('on').addEventListener('click', () => {
      fetch('/api/led/on')
        .then(response => response.json())
        .then(data => {
          console.log(data);
          updateStatus();
        });
    });
    
    document.getElementById('off').addEventListener('click', () => {
      fetch('/api/led/off')
        .then(response => response.json())
        .then(data => {
          console.log(data);
          updateStatus();
        });
    });
  </script>
</body>
</html>

Run the server:

sudo node app.js

Access the control panel at http://[your-raspberry-pi-ip]:3000.

Creating a Real-time IoT Dashboard with WebSockets

For real-time applications, WebSockets are ideal. Let's create a dashboard that displays temperature data in real-time:

npm install express socket.io node-dht-sensor
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const sensor = require('node-dht-sensor');

const app = express();
const server = http.createServer(app);
const io = socketIo(server);
const port = 3000;

// Sensor configuration
const sensorType = 22; // DHT22
const sensorPin = 4;   // GPIO pin number

// Serve static files
app.use(express.static('public'));

// WebSocket connection
io.on('connection', (socket) => {
  console.log('A client connected');
  
  // Function to read sensor data
  function readSensor() {
    if (sensor.read(sensorType, sensorPin)) {
      const temperature = sensor.read(sensorType, sensorPin).temperature.toFixed(1);
      const humidity = sensor.read(sensorType, sensorPin).humidity.toFixed(1);
      
      // Send data to client
      socket.emit('sensorData', { temperature, humidity, timestamp: new Date() });
    } else {
      socket.emit('sensorError', { error: 'Failed to read sensor data' });
    }
  }
  
  // Send data every 2 seconds
  const interval = setInterval(readSensor, 2000);
  
  // Handle client disconnect
  socket.on('disconnect', () => {
    console.log('A client disconnected');
    clearInterval(interval);
  });
});

// Start server
server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

Create public/dashboard.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IoT Dashboard</title>
  <script src="/socket.io/socket.io.js"></script>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }
    .dashboard {
      display: flex;
      justify-content: space-around;
      margin-top: 30px;
    }
    .card {
      background-color: #f8f9fa;
      border-radius: 10px;
      padding: 20px;
      width: 45%;
      box-shadow: 0 4px 8px rgba(0,0,0,0.1);
      text-align: center;
    }
    .value {
      font-size: 3rem;
      font-weight: bold;
      margin: 15px 0;
    }
    .label {
      color: #666;
      font-size: 1.2rem;
    }
    #temperature { color: #e63946; }
    #humidity { color: #457b9d; }
    #timestamp {
      text-align: center;
      color: #666;
      margin-top: 20px;
    }
    h1 {
      text-align: center;
      color: #1d3557;
    }
  </style>
</head>
<body>
  <h1>Raspberry Pi IoT Dashboard</h1>
  
  <div class="dashboard">
    <div class="card">
      <div class="label">Temperature</div>
      <div class="value" id="temperature">--°C</div>
    </div>
    
    <div class="card">
      <div class="label">Humidity</div>
      <div class="value" id="humidity">--%</div>
    </div>
  </div>
  
  <div id="timestamp">Last updated: --</div>
  
  <script>
    const socket = io();
    
    socket.on('sensorData', (data) => {
      document.getElementById('temperature').textContent = `${data.temperature}°C`;
      document.getElementById('humidity').textContent = `${data.humidity}%`;
      document.getElementById('timestamp').textContent = `Last updated: ${new Date(data.timestamp).toLocaleTimeString()}`;
    });
    
    socket.on('sensorError', (data) => {
      console.error(data.error);
      document.getElementById('timestamp').textContent = `Error: ${data.error}`;
    });
  </script>
</body>
</html>

Run this with:

sudo node app.js

Access the dashboard at http://[your-raspberry-pi-ip]:3000/dashboard.html.

Running Node.js Applications as Services

To ensure your Node.js applications run at startup and restart after crashes, you can configure them as system services using systemd:

sudo nano /etc/systemd/system/my-node-app.service

Add the following content:

[Unit]
Description=My Node.js Application
After=network.target

[Service]
ExecStart=/usr/local/bin/node /home/pi/my-pi-node-project/app.js
WorkingDirectory=/home/pi/my-pi-node-project
StandardOutput=inherit
StandardError=inherit
Restart=always
User=pi

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl enable my-node-app
sudo systemctl start my-node-app

Check the status:

sudo systemctl status my-node-app

Performance Optimization for Raspberry Pi

Node.js can run well on Raspberry Pi with some optimizations:

  1. Use the latest Node.js LTS version: Newer versions typically have better performance.

  2. Minimize dependencies: Each npm package adds overhead. Use lightweight alternatives when possible.

  3. Implement caching: Cache results of expensive operations or external API calls.

  4. Use worker threads for CPU-intensive tasks:

    const { Worker, isMainThread, parentPort } = require('worker_threads');
    
    if (isMainThread) {
      const worker = new Worker(__filename);
      worker.on('message', (result) => {
        console.log(`Result: ${result}`);
      });
      worker.postMessage(30); // Calculate fibonacci(30)
    } else {
      // This code runs in the worker thread
      parentPort.once('message', (n) => {
        function fibonacci(n) {
          if (n <= 1) return n;
          return fibonacci(n - 1) + fibonacci(n - 2);
        }
    
        const result = fibonacci(n);
        parentPort.postMessage(result);
      });
    }
    
  5. Reduce logging: Excessive console output affects performance.

  6. Monitor memory usage:

    setInterval(() => {
      const memoryUsage = process.memoryUsage();
      console.log(`Memory usage: ${Math.round(memoryUsage.heapUsed / 1024 / 1024 * 100) / 100} MB`);
    }, 30000);
    
  7. Use PM2 for process management:

    npm install -g pm2
    pm2 start app.js --name "my-app"
    pm2 startup
    pm2 save
    

Security Considerations

When deploying Node.js applications on Raspberry Pi, consider these security measures:

  1. Keep software updated:

    sudo apt update && sudo apt upgrade -y
    nvm install --lts
    
  2. Use environment variables for secrets:

    require('dotenv').config();
    const apiKey = process.env.API_KEY;
    
  3. Implement proper authentication and authorization:

    npm install passport passport-local
    
  4. Set up a firewall:

    sudo apt install ufw
    sudo ufw allow 22
    sudo ufw allow 3000
    sudo ufw enable
    
  5. Use HTTPS:

    const https = require('https');
    const fs = require('fs');
    
    const options = {
      key: fs.readFileSync('privkey.pem'),
      cert: fs.readFileSync('cert.pem')
    };
    
    https.createServer(options, app).listen(443);
    
  6. Input validation and sanitization:

    npm install express-validator
    

Debugging Node.js Applications on Raspberry Pi

Debugging in a headless environment:

  1. Remote debugging:

    node --inspect=0.0.0.0:9229 app.js
    

    Connect from Chrome on your development machine by going to chrome://inspect

  2. Using debug logs:

    DEBUG=app:* node app.js
    
  3. Tracking file operations:

    strace -e trace=file node app.js
    

Common Raspberry Pi Node.js Projects

Here are some popular projects you might want to build:

  1. Home Automation Hub: Control lights, thermostats, and other smart devices.
  2. Weather Station: Collect and display temperature, humidity, and pressure data.
  3. Media Server: Stream videos and music to devices on your network.
  4. Security Camera: Monitor your home with motion detection.
  5. Voice Assistant: Create your own Alexa/Google Home alternative.
  6. Digital Signage: Display information on screens around your home or office.
  7. Retro Gaming Server: Host multiplayer classic games.
  8. Network Monitor: Track devices on your network and monitor bandwidth.

Conclusion

Node.js on Raspberry Pi opens up a world of possibilities for developers. Its lightweight nature, powerful asynchronous capabilities, and excellent GPIO libraries make it a perfect match for IoT projects, home automation, and much more.

By following this guide, you've learned how to:

  • Install Node.js on your Raspberry Pi
  • Create basic web servers and applications
  • Interact with GPIO pins and sensors
  • Build web interfaces to control hardware
  • Create real-time applications with WebSockets
  • Run Node.js applications as system services
  • Optimize performance and improve security

Now it's time to build your own projects and explore the amazing potential of Node.js on the Raspberry Pi platform!

Resources and Further Reading