How I built a dashboard for the iPad with JavaScript

By

How I built a signups counter dashboard for the iPad with JavaScript, using a small Node.js and Express server to serve an auto-refreshing full-screen page.

~~~

In this post I’m going to show you how I built a signups counter for the JavaScript Course, so I can see how many people signed up to the course (signups are open from Nov 15 to Nov 22!)

This counter is a totally superfluous thing, the ones that I do when I’m procrastinating and want to avoid the important work.

I’ve been thinking of different ways to do this, like creating an iOS widget with JavaScript, using Scriptable.

But then I thought, well let’s just make a web page. A web page that I can keep open on the iPad, and I’ll put the iPad next to the desk or somewhere I can look at it from time to time.

I can set the iPad to never put the display to sleep, and I can set the page to automatically refresh every 2 minutes.

The advantage of making a simple web page is that I can access it anywhere. From the iPad, the iPhone, the Mac.

I made a Node.js program that fetches the data I need.

The first step is to write a function that gets the data:

const getData = async () => {
  let count = 0
  //retrieve the count, for example with fetch()
  return count
}

I made it an async function as I use a fetch request, and it’s most likely you’re going to need it too. An async function always returns a promise, so you don’t need to wrap the code in new Promise(). You await what you need inside it and return the value.

If you’re curious, I store all signups into an Airtable record, so inside getData() I put all the code needed to retrieve the count from Airtable.

Then I made a very simple Express server that serves an HTML page that shows the count big, centered in the page.

The code below uses ES modules, so Express comes in with import. Add "type": "module" to your package.json, or name the file server.mjs. With CommonJS, const express = require('express') works too.

import express from 'express'

const app = express()

app.get('/', async (req, res) => {
  const count = await getData()
  const html = `
  <html>
  <head>
    <!-- refresh every 2 minutes -->
    <meta http-equiv="refresh" content="120">
    <!-- allow full screen on iOS -->
    <meta name="apple-mobile-web-app-capable" content="yes">

    <link href="https://fonts.googleapis.com/css2?family=Arvo:wght@400;500;600;700&amp;display=swap" rel="stylesheet" media="all">
    <style>
    html,
    body {
      font-family: Arvo,Helvetica,Apple Color Emoji,Segoe UI Emoji,NotoColorEmoji,Noto Color Emoji,Segoe UI Symbol,Android Emoji,EmojiSymbols,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Noto Sans,sans-serif;
      margin: 0;
      font-size: 200px
    }
    h1 {
      display: grid;
      place-items: center;
      height: 100vh;
    }
    @media (prefers-color-scheme: dark) {
      body {
        filter: invert(100%);
        background-color: #000;
      }
    }
    </style>
  </head>
  <body>
    <h1>${count}</h1>
  </body>
  </html>
  `
  res.end(html)
})

const listener = app.listen(3000)

This code is for Express 5, which is what npm install express gives you as of September 2026. Express 5 catches errors thrown inside an async route handler, like a failed request to Airtable, and passes them to its error handler, which replies with a 500 error. With Express 4 you had to catch the error yourself and call next(err).

Two <meta> tags in the <head> turn the page into a dashboard. The first reloads the page every 120 seconds, so the count stays fresh. With the second, the page opens full screen, without the Safari toolbar, when you add it to the Home Screen from the Share menu. Since iOS 26 and iPadOS 26, every page you add to the Home Screen opens that way by default (unless you turn off Open as Web App), so the tag only matters on older versions.

Here’s the result:

iPad displaying the number 25 in large white text on a dark background

iPad displaying the number 25 in large black text on a light background

And here’s the same page on the iPhone:

iPhone displaying the number 25 in large white text on a black background

You can run the program locally on your computer and access it from devices on the same wi-fi network, as long as the computer is running. If you prefer, you can use a localhost tunnel service, or put it somewhere on the Internet.

I put the app on a server I use for my Node scripts, so I can access it from anywhere, even if I’m on a 4G network.

If I want, I can also AirPlay that on the Apple TV, so I can see the number of signups on the big television screen.

Want me to talk about your product? You can sponsor this site.

~~~

Related posts about js: