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.
1import express from "express";2import http from "http";3import { Server } from "socket.io";4import si from "systeminformation";56// Create an Express app7const app = express();89// Wrap Express with an HTTP server so Socket.IO can hook into it10const server = http.createServer(app);1112// Initialize Socket.IO on top of the HTTP server13const io = new Server(server);1415// Serve static frontend files from the "public" folder16app.use(express.static("public"));1718// Cache object for GPU usage19// 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 usage20let lastGpu = { value: 0, time: 0 };2122// Main polling loop23// Runs every 1000ms (1 second)24setInterval(async () => {2526// Get current CPU usage percentage27const cpu = await si.currentLoad();2829// Get memory usage information30const mem = await si.mem();3132// Only update GPU usage every 3 seconds to avoid stressing out the CPU33if (Date.now() - lastGpu.time > 3000) {3435 // Get graphics controller info36 const gfx = await si.graphics();3738 // Use the first detected GPU (most systems only have one)39 const gpu = gfx.controllers[0];4041 // Store GPU usage and timestamp in cache42 lastGpu = {43 value: gpu?.utilizationGpu ?? 0,44 time: Date.now(),45 };46}4748// Send live stats to frontend49io.emit("stats", {50 cpu: cpu.currentLoad,51 ram: (mem.used / mem.total) * 100,52 gpu: lastGpu.value,53});5455}, 1000);5657server.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
1<!doctype html>2<html lang="en">3<head>4 <meta charset="UTF-8" />5 <title>System Monitor</title>67 <!-- Tailwind CDN -->8 <script src="https://cdn.tailwindcss.com"></script>910 <!-- 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>1314 <!-- Socket.IO -->15 <script src="/socket.io/socket.io.js"></script>16</head>1718<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>2021 <script>22 const { useState, useEffect } = React;23 const socket = io();2425 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 }4546 function App() {47 const [cpu, setCpu] = useState(0);48 const [ram, setRam] = useState(0);49 const [gpu, setGpu] = useState(0);5051 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 }, []);5859 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 }7273 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 :).