> For the complete documentation index, see [llms.txt](https://blockchain-journal-hope-mabuza.gitbook.io/blockchain-journal-hope-mabuza-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://blockchain-journal-hope-mabuza.gitbook.io/blockchain-journal-hope-mabuza-docs/backend/express.js.md).

# Express.js

Express.js is a framework that makes writing them far less painful than doing it in raw Node http.

With raw http, a simple server with a GET route `/home` looks like this:

```javascript
const http = require("http");

const server = http.createServer((req, res) => {
  if (req.method === "GET" && req.url === "/home") {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("Hello");
  } else {
    res.writeHead(404, { "Content-Type": "text/plain" });
    res.end("Not found");
  }
});

server.listen(3000, () => {
  console.log("Server running on port 3000");
});
```

Every route is a manual `if` check, the status code and content type have to be set by hand, and there's no clean place for shared logic. With Express it collapses to this:

```javascript
const express = require("express");

const app = express();

app.get("/home", (req, res) => {
  res.status(200).json({message: "Hello"});
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});
```

Way simpler. Express handles the low level stuff like status codes and content types, so you only write the route and what to send back.

Here are the key differences between a raw http server and an Express.js server that I have noticed in my learning journey:

#### Middleware Pipeline

**Middleware** is the bridge or the filter that requests go through before reaching the endpoint designed to respond to them. It can be used for security, restricting endpoints, logging, and error handling. It is the backbone of every REST API server.

Raw http: there is no pipeline. You have one single function that handles every request. If you want something to run before every route, you have to call it manually inside every `if` branch. Forget it in one branch and that route is unprotected or unlogged.

Express.js: middleware runs automatically before any route handler. You define it once and it covers everything. The request flows through each `app.use()` in order before reaching any route.

#### Error Handling

We have error handling to stop requests from silently failing and leaving the client with no response.

Raw http: if an async function throws, Node has nowhere to send that error. The request hangs, the client waits forever. To fix it you need a try-catch in every single route, which is exhausting and easy to miss when you have a lot of code.

Express.js: you define one central error handler at the bottom that catches anything thrown anywhere. The key is getting async errors to reach it, which is where `asyncHandler` comes in. It wraps every route handler and if the request fails it forwards the error to Express's central error handler, so you never have to write that logic manually per route.

#### Router Modularity

Raw http: everything is forced into one file and one function. As routes grow, the `if/else` chain grows with them and there is no built-in way to split it. Imagine 30 routes across blocks, accounts, and transactions all in one block, it becomes hard to navigate.

Express.js: routes live in their own files with their own responsibilities. You can even have wrappers with specific duties that get imported into the main server file, like a wrapper that handles all blockchain communication. It keeps the code DRY and easy to follow.

***

Express also supports WebSockets, which is where things get interesting for blockchain.

#### WebSockets

The key difference between regular HTTP and WebSockets is that HTTP is one directional per request, the client asks, the server answers, and the connection closes. WebSockets keep the connection open so the server can push data to the client without the client having to ask first, enabling real time back and forth communication.

This matters in blockchain when the frontend needs to notify the user of activity like incoming transactions. Instead of the client repeatedly polling "did anything happen yet?", the server listens for onchain events and pushes them directly to every connected client the moment they happen.

Note: WebSockets don't replace HTTP, they start as an HTTP connection and then upgrade to a WebSocket connection, which is why Express is still involved even in a WebSocket server.

#### Blockchain RPC

A blockchain RPC (Remote Procedure Call) is how your server talks to the blockchain network without running a full node yourself. Instead of storing the entire blockchain locally, you connect to a node provider like Cloudflare or Alchemy over the network and call functions on it as if they were local, asking for blocks, balances, and transactions.

The full flow is: **Client → Server → Ethers.js → RPC node → Blockchain → RPC node → Ethers.js (formats) → Server → Client**

Each layer has one responsibility. The server handles HTTP, Ethers.js communicates with the RPC node and formats the raw blockchain data into readable values, the RPC node fetches the raw data from the blockchain on your behalf, and the blockchain is the source of truth. The raw data is not human friendly, balances come back as wei (so `1 ETH` is `1000000000000000000`) and numbers come back as BigInt. Ethers.js translates all of that into something your server and frontend can actually use.

The server doesn't know or care how that balance is fetched or formatted, those details are hidden inside `blockchain.js`. Same one responsibility principle as before.

#### Onchain Events

Onchain events are actions that happen on the blockchain itself. When a USDC transfer occurs, the smart contract emits a `Transfer` event. Your server listens for these using a separate WebSocket connection to the RPC node, not the client, the blockchain node itself.

So there are actually two separate WebSocket connections involved: one between your server and the blockchain node listening for onchain events, and one between your server and the frontend client broadcasting those events. The onchain event triggers the whole chain: the blockchain emits the event, Ethers.js picks it up, the server broadcasts it, and the client receives it, all without the client asking.

***

Here is the code for the REST API(a way for two systems to talk to each other over the internet using simple requests and responses) server I built to handle client requests, manage a WebSocket connection, and listen to onchain events.

Starting with the wrappers. The first one communicates with the blockchain and fetches the data we need:

```javascript
const {ethers} = require('ethers');

let provider = null;

function init(rpcUrl){
    provider = new ethers.JsonRpcProvider(rpcUrl);
}

async function getBlockNumber(){
    return provider.getBlockNumber();
}

async function getBalance(address){
    const balance = await provider.getBalance(address);
    return ethers.formatEther(balance);
}

async function getBlock(blockTag){
    return provider.getBlock(blockTag);
}

async function getTransaction(hash) {
    return provider.getTransaction(hash);
}

module.exports = {init, getBlockNumber, getBalance, getBlock, getTransaction};
```

The second wrapper handles the WebSocket connection to the blockchain node and listens for onchain events:

```javascript
const {ethers} = require('ethers');// library for communicating with the blockchain

const USDC_ADDRESS = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const TRANSFER_ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)'];

let provider = null;
let contract = null;

function init(wsUrl) {
    provider = new ethers.WebSocketProvider(wsUrl);
    contract = new ethers.Contract(USDC_ADDRESS, TRANSFER_ABI, provider);
}

function onBlock(callback) {
    provider.on('block', callback);
}

function onTransfer(callback) {
    contract.on('Transfer', (from, to, value) => callback({from, to, value: ethers.formatUnits(value, 6)}));
}

function destroy() {
    provider.destroy();
}

module.exports = {init, onBlock, onTransfer, destroy};
```

Both wrappers are then imported into the main server:

```javascript
const express = require('express');
const events = require('./events');
const blockchain = require('./blockchain');
const cors = require('cors');
const http = require('http');
const {WebSocketServer} = require('ws');

const app = express();

app.use(cors());//midlleware
app.use(express.json());//body parse

blockchain.init(process.env.RPC_URL || 'https://cloudflare-eth.com');
events.init(process.env.WS_RPC_URL || 'wss://eth-mainnet.g.alchemy.com/v2/demo');

//error wrapper
const asyncHandler = fn => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);

// middleware: log requests
app.use((req, res, next) => {
    console.log(req.method, req.path);
    next();
});

function isValidAddress(addr) {
  return typeof addr === 'string' && /^0x[0-9a-fA-F]{40}$/.test(addr);
}

// -----------ENDPOINTS---------------
app.get("/health", asyncHandler(async(req, res) => {
    res.json({status: "ok", uptime: process.uptime()});
}));

app.get("/blocks/latest", asyncHandler(async(req, res) => {
    const block = await blockchain.getBlock('latest');
    res.json(block);
}));

app.get("/blocks/:number", asyncHandler(async(req, res) => {
    const block = await blockchain.getBlock(Number(req.params.number));
    if (block == null) {
        return res.status(404).json({error: 'Block not found'});
    } 
    res.json(block);
}));

app.get("/accounts/:address", asyncHandler(async(req, res) =>{
    const addr = String(req.params.address);
    if (!isValidAddress(addr)) return res.status(400).json({error: "Invalid address"});
    const balance = await blockchain.getBalance(addr);
    res.status(200).json({address: addr, balance: balance});
}));

app.get("/txs/:hash", asyncHandler(async(req, res) => {
    const hash = String(req.params.hash);
    const txs = await blockchain.getTransaction(hash);
    if (txs == null) {
        return res.status(404).json({error: "Transaction not found"});
    } else {
        res.json(txs);
    }
}));

// ------------WEBSOCKET----------------
const server = http.createServer(app);
const wss = new WebSocketServer({server});//create web socket connection

function broadcast(message) {
    const data = JSON.stringify(message);
    for (const client of wss.clients) {
        if (client.readyState == 1) {
            client.send(data);
        }
    }
}

wss.on('connection', (ws) => {
    ws.send(JSON.stringify({type: 'welcome'}));

    ws.on('message', (raw) => {
        try {
            const msg = JSON.parse(raw.toString());
            if (msg.type === 'ping') {
                ws.send(JSON.stringify({type: 'pong'}));
            }
        } catch {
            ws.send(JSON.stringify({type: "error", message: "Invalid JSON"}));
        }
    });
});

// ---------SERVER SUBSCRIPTIONS/ EVENT LISTENERS------------
events.onBlock((blockNumber) => {
    broadcast({type: 'block', data: {number : blockNumber}});
});

events.onTransfer((transfer) => {
    broadcast({type: 'transfer', data: transfer});
});

// middleware: error handler
app.use((err, req, res, next) => {
    res.status(err.status || 500).json({error: err.message});
});

module.exports = {server, wss};

if (require.main === module) {
  server.listen(3000, () => console.log('Server running on http://localhost:3000'));
}
```
