How to Build a Read-Only Crypto Payment Dashboard with Node.js

A crypto payment dashboard does not always need to process payments directly. In many cases, the safer first step is to build a read-only dashboard that tracks public transaction data.

This kind of dashboard can show whether a payment is pending, confirmed, or not found by checking public blockchain data through an API.

In this guide, we will build a simple read-only crypto payment dashboard using:

Node.js
Express
EJS
HTML/CSS
A public blockchain API

The dashboard will not collect private keys, seed phrases, wallet passwords, or user funds. It will only read public transaction information.

What We Are Building

The dashboard will allow a user to enter a transaction hash and view basic payment details.

The flow looks like this:

User enters transaction hash
↓
Node.js backend receives the request
↓
Backend calls a public blockchain API
↓
API returns transaction data
↓
Dashboard displays payment status

The dashboard can show:

Transaction hash
Status
Confirmations
Amount
Sender address
Receiver address
Timestamp
Network

For this tutorial, we will keep the project simple and focus on the backend structure.

Important Security Rule

A read-only crypto dashboard should never ask for:

Private keys
Seed phrases
Wallet passwords
Exchange login details
Recovery phrases

A transaction hash and wallet address are usually public data. Private keys and seed phrases are secret data. Any app asking users to enter private keys or seed phrases is dangerous.

This tutorial only uses public blockchain data.

Project Structure

Create a new folder:

mkdir crypto-payment-dashboard
cd crypto-payment-dashboard

Initialize a Node.js project:

npm init -y

Install required packages:

npm install express axios ejs dotenv

Create this folder structure:

crypto-payment-dashboard/
│
├── app.js
├── .env
├── package.json
│
├── views/
│   ├── index.ejs
│   └── result.ejs
│
└── public/
    └── style.css

Step 1: Create the Express Server

Create an app.js file:

const express = require("express");
const axios = require("axios");
require("dotenv").config();

const app = express();
const PORT = process.env.PORT || 3000;

app.set("view engine", "ejs");

app.use(express.urlencoded({ extended: true }));
app.use(express.static("public"));

app.get("/", (req, res) => {
  res.render("index", {
    error: null
  });
});

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Run the server:

node app.js

Open this in your browser:

http://localhost:3000

At this stage, the app will not work fully because we have not created the view files yet.

Step 2: Create the Homepage

Create views/index.ejs:

<!DOCTYPE html>
<html>
<head>
  <title>Crypto Payment Dashboard</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body>
  <main class="container">
    <h1>Read-Only Crypto Payment Dashboard</h1>

    <p>
      Enter a transaction hash to check its public blockchain status.
    </p>

    <% if (error) { %>
      <div class="error"><%= error %></div>
    <% } %>

    <form action="/transaction" method="POST">
      <label for="txHash">Transaction Hash</label>
      <input 
        type="text" 
        id="txHash" 
        name="txHash" 
        placeholder="Enter transaction hash"
        required
      >

      <button type="submit">Check Transaction</button>
    </form>

    <p class="note">
      This dashboard is read-only. Never enter private keys or seed phrases.
    </p>
  </main>
</body>
</html>

This page contains a simple form where the user can enter a transaction hash.

Step 3: Add Basic Styling

Create public/style.css:

body {
  font-family: Arial, sans-serif;
  background: #f4f6f8;
  margin: 0;
  padding: 0;
}

.container {
  max-width: 700px;
  margin: 60px auto;
  background: #ffffff;
  padding: 30px;
  border-radius: 10px;
}

h1 {
  margin-bottom: 10px;
}

form {
  margin-top: 25px;
}

label {
  display: block;
  margin-bottom: 8px;
  font-weight: bold;
}

input {
  width: 100%;
  padding: 12px;
  font-size: 16px;
  margin-bottom: 15px;
}

button {
  padding: 12px 20px;
  font-size: 16px;
  cursor: pointer;
}

.error {
  background: #ffe5e5;
  color: #a10000;
  padding: 12px;
  border-radius: 6px;
  margin-top: 15px;
}

.note {
  margin-top: 20px;
  font-size: 14px;
  color: #555;
}

.card {
  background: #f9fafb;
  padding: 20px;
  border-radius: 8px;
  margin-top: 20px;
}

.status {
  font-weight: bold;
}

Step 4: Choose a Blockchain API

For a real dashboard, you need a blockchain API provider.

Examples include:

Blockchair
BlockCypher
Etherscan
Blockchain.com explorer API
Alchemy
Infura
Moralis

For this tutorial, we will use a simple API function structure. You can replace the API URL depending on the blockchain you want to support.

This guide uses Bitcoin-style transaction lookup as an example.

Step 5: Create the Transaction Route

Update app.js and add a POST route:

app.post("/transaction", async (req, res) => {
  const { txHash } = req.body;

  if (!txHash || txHash.trim().length < 10) {
    return res.render("index", {
      error: "Please enter a valid transaction hash."
    });
  }

  try {
    const transaction = await getTransactionData(txHash.trim());

    res.render("result", {
      transaction
    });
  } catch (error) {
    console.error(error.message);

    res.render("index", {
      error: "Transaction not found or API request failed."
    });
  }
});

Now add this helper function above app.listen():

async function getTransactionData(txHash) {
  const apiUrl = `https://blockchain.info/rawtx/${txHash}`;

  const response = await axios.get(apiUrl);

  const data = response.data;

  return {
    hash: data.hash,
    status: data.block_height ? "Confirmed" : "Pending",
    confirmations: data.block_height ? "Confirmed on-chain" : "Not confirmed yet",
    blockHeight: data.block_height || "Pending",
    timestamp: data.time
      ? new Date(data.time * 1000).toLocaleString()
      : "Not available",
    inputs: data.inputs?.length || 0,
    outputs: data.out?.length || 0,
    network: "Bitcoin"
  };
}

The function sends the transaction hash to a public blockchain API and returns simplified data for the dashboard.

Step 6: Create the Result Page

Create views/result.ejs:

<!DOCTYPE html>
<html>
<head>
  <title>Transaction Result</title>
  <link rel="stylesheet" href="/style.css">
</head>
<body>
  <main class="container">
    <h1>Transaction Status</h1>

    <div class="card">
      <p>
        <strong>Network:</strong>
        <%= transaction.network %>
      </p>

      <p>
        <strong>Transaction Hash:</strong>
        <%= transaction.hash %>
      </p>

      <p>
        <strong>Status:</strong>
        <span class="status"><%= transaction.status %></span>
      </p>

      <p>
        <strong>Confirmations:</strong>
        <%= transaction.confirmations %>
      </p>

      <p>
        <strong>Block Height:</strong>
        <%= transaction.blockHeight %>
      </p>

      <p>
        <strong>Timestamp:</strong>
        <%= transaction.timestamp %>
      </p>

      <p>
        <strong>Inputs:</strong>
        <%= transaction.inputs %>
      </p>

      <p>
        <strong>Outputs:</strong>
        <%= transaction.outputs %>
      </p>
    </div>

    <p>
      <a href="/">Check another transaction</a>
    </p>

    <p class="note">
      This dashboard only reads public transaction data.
    </p>
  </main>
</body>
</html>

Now restart your server:

node app.js

Enter a valid Bitcoin transaction hash and submit the form. The dashboard should display the transaction status.

Step 7: Full app.js File

Here is the complete backend code:

const express = require("express");
const axios = require("axios");
require("dotenv").config();

const app = express();
const PORT = process.env.PORT || 3000;

app.set("view engine", "ejs");

app.use(express.urlencoded({ extended: true }));
app.use(express.static("public"));

app.get("/", (req, res) => {
  res.render("index", {
    error: null
  });
});

app.post("/transaction", async (req, res) => {
  const { txHash } = req.body;

  if (!txHash || txHash.trim().length < 10) {
    return res.render("index", {
      error: "Please enter a valid transaction hash."
    });
  }

  try {
    const transaction = await getTransactionData(txHash.trim());

    res.render("result", {
      transaction
    });
  } catch (error) {
    console.error(error.message);

    res.render("index", {
      error: "Transaction not found or API request failed."
    });
  }
});

async function getTransactionData(txHash) {
  const apiUrl = `https://blockchain.info/rawtx/${txHash}`;

  const response = await axios.get(apiUrl);

  const data = response.data;

  return {
    hash: data.hash,
    status: data.block_height ? "Confirmed" : "Pending",
    confirmations: data.block_height ? "Confirmed on-chain" : "Not confirmed yet",
    blockHeight: data.block_height || "Pending",
    timestamp: data.time
      ? new Date(data.time * 1000).toLocaleString()
      : "Not available",
    inputs: data.inputs?.length || 0,
    outputs: data.out?.length || 0,
    network: "Bitcoin"
  };
}

app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Step 8: How the Dashboard Works

The user enters a transaction hash. The backend validates the input and sends a request to the blockchain API.

The API returns transaction data. The backend converts that raw response into a cleaner object.

The frontend then displays the result.

The dashboard does not sign transactions, send funds, connect wallets, or manage private keys.

That is why this is safer for beginners.

Step 9: Improving the Dashboard

After the basic version works, you can improve it with:

Database storage
Transaction history
User authentication
Webhook updates
Multi-chain support
Pagination
Search by wallet address
CSV export
Admin dashboard
Better API error handling
Rate-limit protection

For example, if you want to avoid calling the API again and again for the same transaction, you can store checked transaction hashes in a database.

Possible databases:

SQLite
PostgreSQL
MongoDB
MySQL

Step 10: Add API Rate Limit Protection

Public APIs often have rate limits. Do not allow unlimited requests from users.

Install a rate limiter:

npm install express-rate-limit

Add it to app.js:

const rateLimit = require("express-rate-limit");

const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: 20,
  message: "Too many requests. Please try again later."
});

app.use(limiter);

This example allows 20 requests per minute per IP address.

Step 11: Add Better Input Validation

A Bitcoin transaction hash is usually a 64-character hexadecimal string.

You can add a helper function:

function isValidBitcoinTxHash(txHash) {
  return /^[a-fA-F0-9]{64}$/.test(txHash);
}

Then update the route:

if (!isValidBitcoinTxHash(txHash.trim())) {
  return res.render("index", {
    error: "Please enter a valid Bitcoin transaction hash."
  });
}

This prevents obviously invalid input before calling the API.

Step 12: Important Security Tips

A crypto dashboard should follow basic security practices:

Never ask for private keys
Never ask for seed phrases
Never store wallet passwords
Validate user input
Use HTTPS in production
Hide API keys in environment variables
Add rate limiting
Log errors carefully
Avoid exposing sensitive server errors
Use read-only API endpoints

If your app only needs to display transaction status, it should not request wallet permissions.

Final Thoughts

A read-only crypto payment dashboard is a good beginner project because it teaches backend APIs, request handling, frontend rendering, and basic blockchain concepts without touching user funds.

The safest approach is to start with public transaction data only. Once the read-only version is stable, you can add features like transaction history, database caching, webhook support, and multi-chain tracking.

The main rule is simple: read public blockchain data, but never collect private wallet secrets.

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.