Build an Instagram Analytics Dashboard in Node.js
This tutorial builds a small dashboard for one Instagram account. It runs on your computer. It shows the followers, the totals of the last 7 days, the live stories with their insights and the recent posts.
What you build
The dashboard is one web page with four parts:
- The account name and the follower count.
- The views, reach, likes, comments, shares and replies of the account in the last 7 days.
- Each live story, with its views, reach and replies.
- The 6 most recent posts and reels.
It uses Node.js 20.6 or later, the built-in node:http server, and the free @storylinkpro/instagram-stories-api client. It needs no framework and no database.
You also need an access token and the Instagram account id. For the steps, read Get an Instagram Graph API access token.
Set up the project
mkdir ig-dashboard && cd ig-dashboard
npm init -y
npm install @storylinkpro/instagram-stories-apiMake a file with the name .env for the token and the account id:
INSTAGRAM_ACCESS_TOKEN=…
INSTAGRAM_USER_ID=…Do not commit this file. If the folder is a git repository, add .env to .gitignore.
Read the data
Save this file as data.mjs. It reads everything that the page shows:
import { createClient } from "@storylinkpro/instagram-stories-api";
const ig = createClient({
accessToken: process.env.INSTAGRAM_ACCESS_TOKEN,
igUserId: process.env.INSTAGRAM_USER_ID,
});
export async function loadDashboard() {
const week = new Date(Date.now() - 7 * 86_400_000);
const [account, totals, stories, recent] = await Promise.all([
ig.account(),
ig.accountInsights({ since: week }),
ig.stories(),
ig.media({ limit: 6 }),
]);
// One insights call for each live story, one after the other.
const storyRows = [];
for (const story of stories) {
storyRows.push({ ...story, ...(await ig.storyInsights(story.id)) });
}
return { account, totals, stories: storyRows, posts: recent.data };
}accountInsights() returns one row for each metric, with its name and its total. An insight that Meta does not give is null, not 0. Meta keeps back some story numbers when fewer than 5 people saw the story.
Show it on a page
Save this file as server.mjs. It keeps the data for 10 minutes, so a reload does not call the API again:
import { createServer } from "node:http";
import { loadDashboard } from "./data.mjs";
const CACHE_MS = 10 * 60 * 1000;
let cache = null;
const esc = (v) =>
String(v ?? "-").replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
const num = (n) => (typeof n === "number" ? n.toLocaleString("en-US") : "-");
function page(d) {
const totals = d.totals.map((t) => `<li>${esc(t.name)}: <b>${num(t.total)}</b></li>`).join("");
const stories = d.stories
.map((s) => `<tr><td>${esc(s.timestamp)}</td><td>${num(s.views)}</td><td>${num(s.reach)}</td><td>${num(s.replies)}</td></tr>`)
.join("");
const posts = d.posts
.map((p) => `<li><a href="${esc(p.permalink)}">${esc(p.media_product_type)} · ${esc(p.timestamp)}</a></li>`)
.join("");
return `<!doctype html><meta charset="utf-8"><title>@${esc(d.account.username)}</title>
<body style="font-family:system-ui;max-width:720px;margin:2rem auto;padding:0 1rem">
<h1>@${esc(d.account.username)}</h1>
<p>${num(d.account.followers_count)} followers</p>
<h2>Last 7 days</h2><ul>${totals}</ul>
<h2>Live stories</h2>
<table><tr><th>Posted</th><th>Views</th><th>Reach</th><th>Replies</th></tr>${stories || '<tr><td colspan="4">No live stories</td></tr>'}</table>
<h2>Recent posts</h2><ul>${posts}</ul>`;
}
createServer(async (req, res) => {
if (req.url !== "/") {
res.writeHead(404).end();
return;
}
try {
if (!cache || Date.now() - cache.at > CACHE_MS) cache = { at: Date.now(), data: await loadDashboard() };
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page(cache.data));
} catch (e) {
console.error(e);
res.writeHead(502, { "content-type": "text/plain" }).end("Could not read Instagram. See the terminal.");
}
}).listen(3000, "127.0.0.1", () => console.log("Open http://localhost:3000"));The esc() function encodes all text from Instagram before the page shows it. Keep it, and use it for each new field that you add. A caption, for example, can contain HTML.
Run the dashboard
node --env-file=.env server.mjsOpen http://localhost:3000. The first load can take some seconds, because the server makes one call for each live story. If the page shows an error, read the message in the terminal:
- Code 190. The token expired or is not valid. Get a new token.
- Code 10 or 200. The token does not have a permission, such as
instagram_manage_insights. - Code 4, 17, 32 or 80002. Meta throttled the app. Wait before you load the page again. For more, read Instagram Graph API rate limits explained.
Keep it safe
The server listens on 127.0.0.1, so only your computer can open the page. The token stays in the server process, and the page never shows it.
Do not put this server on the internet as it is. Anyone who can open the page can see the data, and each visit after the cache time uses your API limit. Add a login before you publish it.
The dashboard shows only what is live now. Stories leave it after 24 hours, and their numbers are lost. To keep them, add the export from Export Instagram Story insights to CSV.
How Storylink does it
Storylink is a full version of this dashboard, with no code to keep. It saves the insights of each story before the story expires. The Analytics page shows the totals, the top stories and the trend for the range that you select. It works for more than one account.
To build on the saved numbers, use the Storylink API and MCP server. To see the Analytics page with sample data and no sign-up, open the live demo.
Put this into practice with Storylink
Free forever to start. No card required.