Node JS PC Hardware Monitor

If you want to see the video version of this post, you can find it on Youtube.

If you want to see the completed source code for this post, check it out on GitHub: https://github.com/bytemyke/system_monitor.

This tutorial walks through building a real-time PC system monitor. This program will let you display constantly updating CPU, RAM, and GPU usage data live in your browser.

Our stack will use:

  • Node.js + Express for the backend
  • Socket.IO for real-time communication
  • systeminformation to read system stats
  • React (no build step) for the frontend
  • Tailwind CSS for styling

Project Overview

Backend

  • Polls CPU and RAM every second
  • Polls GPU every 3 seconds (cached for performance)
  • Emits stats via WebSockets

Frontend

  • Receives live stats over Socket.IO
  • Displays animated progress bars
  • Uses React state with minimal re-renders

Since this project is a tutorial/demo, the program has been optimized so that we aren't over working our system. While this does keep the resource usage low, it also leads to a less accurate usage report. Please adjust the time values if you'd like the frontend to update more frequently. But keep in mind that it will make the program more demanding on your system.


Creating the Server

The first thing we have to do is install our dependencies. Open up your project in a terminal and type:

npm install express socket.io systeminformation

If you try to run the server.js code right now, you'll probably get an error. This is because our package.json file isn't setup for our code. Since we're using the the JS import system instead of require, we will need to configure out program to do so. If it isn't already included, inside of your package.json file, directly above dependencies, add the line "type": "module",.

Your file should now look something like this

{
"type": "module",
"dependencies": {
"express": "^5.2.1",
"socket.io": "^4.8.3",
"systeminformation": "^5.30.7"
}
}

Now create the file server.js at the root of your project. This will be the main/only JS file that we will use on the backend.

server.js
1import express from "express";
2import http from "http";
3import { Server } from "socket.io";
4import si from "systeminformation";
5
6// Create an Express app
7const app = express();
8
9// Wrap Express with an HTTP server so Socket.IO can hook into it
10const server = http.createServer(app);
11
12// Initialize Socket.IO on top of the HTTP server
13const io = new Server(server);
14
15// Serve static frontend files from the "public" folder
16app.use(express.static("public"));
17
18// Cache object for GPU usage
19// GPU polling can really strain the CPU, so we store the last value and timestamp. We'll use this to limit how often we check GPU usage
20let lastGpu = { value: 0, time: 0 };
21
22// Main polling loop
23// Runs every 1000ms (1 second)
24setInterval(async () => {
25
26// Get current CPU usage percentage
27const cpu = await si.currentLoad();
28
29// Get memory usage information
30const mem = await si.mem();
31
32// Only update GPU usage every 3 seconds to avoid stressing out the CPU
33if (Date.now() - lastGpu.time > 3000) {
34
35 // Get graphics controller info
36 const gfx = await si.graphics();
37
38 // Use the first detected GPU (most systems only have one)
39 const gpu = gfx.controllers[0];
40
41 // Store GPU usage and timestamp in cache
42 lastGpu = {
43 value: gpu?.utilizationGpu ?? 0,
44 time: Date.now(),
45 };
46}
47
48// Send live stats to frontend
49io.emit("stats", {
50 cpu: cpu.currentLoad,
51 ram: (mem.used / mem.total) * 100,
52 gpu: lastGpu.value,
53});
54
55}, 1000);
56
57server.listen(8080);
58console.log("live on port 8080");

I'd like to note a specific section of the code here. You'll see that we have:

lastGpu = {
value: gpu?.utilizationGpu ?? 0,
time: Date.now(),
};

Here, we are saying gpu?.utilizationGpu ?? 0 because not all PCs have GPU data available. For example, the laptop I'm writing this article on does not have a GPU. So when I run my application, I just see 0 for GPU usage. This is expected. If you want to go a step further, you can add a conditional on the frontend to only display GPU usage if value > 0.

Why GPU Is Cached

GPU stats are significantly heavier to query than CPU or RAM. Polling every second can cause system slowdowns, so we cache the value and only refresh every 3 seconds.


Creating the Frontend

Our project is using React via CDN. This is because it is easier to get up and running. This makes it a better solution for tinkering projects and demos. However, if you are building a production application with this, I would highly recommend you use a NPM react app for the frontend

At the root of your project, create a folder called public, and add the file index.html inside of it.

The frontend:

  • Uses React via CDN (no build tools)
  • Uses Tailwind CSS for styling
  • Connects to Socket.IO automatically
index.html
1<!doctype html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8" />
5 <title>System Monitor</title>
6
7 <!-- Tailwind CDN -->
8 <script src="https://cdn.tailwindcss.com"></script>
9
10 <!-- React -->
11 <script src="https://unpkg.com/react@18/umd/react.development.js"></script>
12 <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
13
14 <!-- Socket.IO -->
15 <script src="/socket.io/socket.io.js"></script>
16</head>
17
18<body class="bg-slate-900 text-slate-100 min-h-screen flex items-center justify-center">
19 <div id="root" class="w-2/5"></div>
20
21 <script>
22 const { useState, useEffect } = React;
23 const socket = io();
24
25 function Bar({ label, value, color }) {
26 return React.createElement(
27 "div",
28 { className: "mb-6 w-full" },
29 React.createElement(
30 "div",
31 { className: "flex justify-between mb-1" },
32 React.createElement("span", null, label),
33 React.createElement("span", null, value.toFixed(1) + "%"),
34 ),
35 React.createElement(
36 "div",
37 { className: "w-full bg-slate-700 rounded h-4 overflow-hidden" },
38 React.createElement("div", {
39 className: "h-4 + color transition-all duration-300",
40 style: { width: value + "%" },
41 }),
42 ),
43 );
44 }
45
46 function App() {
47 const [cpu, setCpu] = useState(0);
48 const [ram, setRam] = useState(0);
49 const [gpu, setGpu] = useState(0);
50
51 useEffect(() => {
52 socket.on("stats", (d) => {
53 setCpu((p) => (Math.abs(p - d.cpu) > 0.5 ? d.cpu : p));
54 setRam((p) => (Math.abs(p - d.ram) > 0.5 ? d.ram : p));
55 setGpu((p) => (p !== d.gpu ? d.gpu : p));
56 });
57 }, []);
58
59 return React.createElement(
60 "div",
61 { className: "bg-slate-800 p-8 rounded-xl shadow-xl" },
62 React.createElement(
63 "h1",
64 { className: "text-xl font-bold text-center mb-6" },
65 "PC System Monitor",
66 ),
67 React.createElement(Bar, { label: "CPU", value: cpu, color: "bg-emerald-500" }),
68 React.createElement(Bar, { label: "RAM", value: ram, color: "bg-sky-500" }),
69 React.createElement(Bar, { label: "GPU", value: gpu, color: "bg-purple-500" }),
70 );
71 }
72
73 ReactDOM.createRoot(document.getElementById("root")).render(
74 React.createElement(App),
75 );
76 </script>
77</body>
78</html>

Performance Optimizations

The code includes several performance optimizations. This can be seen on the backend, where we limit how often we check our system for new data. We can also see this on the frontend. Here we only re-render if there is a difference in data, instead of everytime we receive data.

  • GPU polling is throttled
  • State updates are filtered
  • No unnecessary re-renders
  • No heavy frontend libraries

This ensures that our Node JS program isn't too demanding on our pc.


Verifying Accuracy

You can use default programs in your OS to compare values. Some examples are:

  • Task Manager (Windows)
  • htop / top (Linux)
  • Activity Monitor (macOS)

Minor differences between our program and the OS monitoring program are normal. For example, on windows, you'll notice that task manager updates the performance stats more frequently than our program.


In Conclusion

This setup gives you:

  • Real-time system stats
  • A clean React UI
  • No build tools
  • Minimal performance overhead

If you found this guide helpful, be sure to subscribe to my Youtube channel for more NodeJS tutorials, tips, and tricks! Happy hacking :).