Table of contents
Grafana is an open-source analytics and interactive visualization web application for creating charts, graphs, and alerts from diverse data sources. It helps to monitor metrics, logs, and traces across systems like servers, storage, and applications. Common use cases include dashboarding IT infrastructure, such as performance, CPU utilization, disk health, application status and anomaly detection.
For my use case, Grafana’s backend (running on IBM i) will use Node.js to send data in JSON format to the Grafana frontend (running on Linux).

graph TD
A[Grafana Backend on IBM i] --> |main directory| B[**/opt/grafana/**]
B --> |Node.js entry file| C[index.js]
B --> |dependencies| D[package.json]
D --> |project's manifest| E[package-lock.json]
B --> |installed packages| F[**node_modules**]
style A fill:#FFD8A8,stroke:#333,stroke-width:2px
style B fill:#cce5ff,stroke:#333,stroke-width:2px
style C fill:#ccffcc,stroke:#333,stroke-width:2px
style D fill:#ccffcc,stroke:#333,stroke-width:2px
style E fill:#fffdd0,stroke:#333,stroke-width:2px
style F fill:#fffdd0,stroke:#333,stroke-width:2px
yum install ca-certificates-mozilla.noarch gzip tar-gnu nodejs22 wget
<aside>
mkdir -p /opt/grafana && cd /opt/grafana → create Grafana folder
For the panels and dashboard examples I provide, I will extract data from this SQL query
select HOST_NAME,
MACHINE_TYPE, MACHINE_MODEL, SERIAL_NUMBER, PARTITION_ID,
RESTRICTED_STATE, ATTENTION_LIGHT, IPL_MODE, IPL_TYPE,
SYSTEM_ASP_USED, SYSTEM_ASP_STORAGE, MAIN_STORAGE_SIZE,
DEFINED_MEMORY, MAXIMUM_MEMORY,
ACTIVE_JOBS_IN_SYSTEM, TOTAL_JOBS_IN_SYSTEM, MAXIMUM_JOBS_IN_SYSTEM
from **QSYS2.SYSTEM_STATUS_INFO**
touch index.js → main entry point for backend (handling HTTP requests and data queries)
const express = require("express");
const { dbconn, dbstmt } = require("idb-connector");
const PORT = process.env.PORT || 3333;
/// SQL query
const QSYS2_SYSTEM_STATUS_INFO = "select * from QSYS2.SYSTEM_STATUS_INFO";
const app = express();
/// CORS for Grafana
app.use((req, res, next) => {
// Allow any origin (Grafana may be served from a different host)
res.setHeader("Access-Control-Allow-Origin", "*");
// Allow only GET and OPTIONS (endpoints are read-only)
res.setHeader("Access-Control-Allow-Methods", "GET,OPTIONS");
// Allow common request headers Grafana may send
res.setHeader("Access-Control-Allow-Headers", "content-type, accept");
// Respond to preflight requests immediately
if (req.method === "OPTIONS") return res.sendStatus(204);
next();
});
/// DB2 for i connection
// Create a new DB connection object
const connection = new dbconn();
// Open a local connection to DB2 for i
connection.conn("*LOCAL");
// Create a statement tied to the connection
const statement = new dbstmt(connection);
// Health-check route
app.get("/", (req, res) => res.status(200).send("OK"));
/// JSON API endpoint for Grafana
app.get("/QSYS2_SYSTEM_STATUS_INFO", (req, res) => {
try {
// Execute the SQL stored in the constant and return the rows synchronously
const rows = statement.execSync(QSYS2_SYSTEM_STATUS_INFO);
// Close the cursor/resources for the statement result set
statement.closeCursor();
// Return rows as JSON (or empty array if undefined)
res.json(rows || []);
} catch (err) {
// Ensure cursor is closed on error (best-effort)
try { statement.closeCursor(); } catch (e) {}
// Return HTTP 500 with the error message
res.status(500).json({ error: String(err) });
}
});
// Start HTTP server on all interfaces
app.listen(PORT, "0.0.0.0", () => {
console.log(`IBM i Grafana API is running on port ${PORT}`);
});