Back to blog

How to Build a Simple Casino in Node.js

March 11, 2025

Learn to create a secure online casino platform using Node.js and Express. This comprehensive guide walks through setting up game sessions, handling bets, verifying outcomes, and building a user interface - perfect for developers looking to understand online gaming backend systems.

How to Build a Simple Casino in Node.js

In this comprehensive guide, we'll walk through the process of building a simple but secure casino platform using Node.js and Express. We'll cover everything from setting up the project to implementing game sessions, handling bets, verifying outcomes, and managing player balances.

Prerequisites

Before getting started, ensure you have the following installed:

  • Node.js (latest LTS version recommended)
  • NPM or Yarn
  • Basic knowledge of JavaScript and Express.js

Project Setup

Let's begin by creating a new project and installing the necessary dependencies:

# Create a new directory
mkdir nodejs-casino
cd nodejs-casino

# Initialize a new Node.js project
npm init -y

# Install required dependencies
npm install express crypto util tsscmp

Core Implementation

Our casino platform will consist of several key components:

  1. A server setup with Express.js
  2. Game session management
  3. Balance tracking and updates
  4. Secure bet placement
  5. Fair outcome verification

Let's implement these components one by one.

Setting Up the Express Server

Create an index.js file in your project directory with the following code to set up the basic server:

const http = require("http");
const crypto = require("crypto");
const util = require("util");
const express = require("express");
const tsscmp = require("tsscmp");

// Initialize Express app and HTTP server
const app = express();
app.use(express.json()); // Important for parsing JSON request bodies
const server = http.createServer(app);

// Configuration variables - replace with your actual values
const HEXZARD_API_URL_SESSION = "https://your-session-api-url.com";
const HEXZARD_API_SESSION_SECRET = "your-session-secret";
const HEXZARD_API_WEBHOOK_SECRET = "your-webhook-secret";

// In-memory database mock for demonstration
const db = [{ id: "test-client-id", balance: 238, bets: [] }];

// Start the server
const port = process.env.PORT || 3000;
server.listen(port, () => {
  console.log(`Casino server running on port ${port}`);
});

Implementing Helper Functions

Next, let's add some essential helper functions for cryptographic operations, game verification, and security:

// Current timestamp in seconds
const timeNow = () => Math.floor(Date.now() / 1000);

// SHA256 hash function
const sha256 = (body, secret) => {
  const hmac = crypto.createHmac("sha256", body);
  return secret ? hmac.update(secret).digest("hex") : hmac.digest("hex");
};

// Generate a random secure string
const randomSecureString = async () => {
  const LENGTH = 5;
  const promisifiedRandomBytes = util.promisify(crypto.randomBytes);
  const buffer = await promisifiedRandomBytes(LENGTH);
  return buffer.toString("hex");
};

// Create signature for game requests
const createGameSignature = (timestamp, body, secret) => {
  const VERSION_NUMBER = "v1";
  return `${VERSION_NUMBER}=${sha256(
    [VERSION_NUMBER, String(timestamp), JSON.stringify(body)].join(":"),
    secret
  )}`;
};

// Verify incoming game request signatures
const verifyGameRequest = (gameSignature, timestamp, body, secret) => {
  if (!gameSignature || !timestamp || !body) {
    throw new Error("Missing parameters");
  }

  const mySignature = createGameSignature(timestamp, body, secret);
  const FIVE_MINUTES = 60 * 5;

  if (Math.abs(timeNow() - timestamp) > FIVE_MINUTES) {
    // Potential replay attack, so let's ignore it
    throw new Error("Request is no longer valid");
  }

  // Using a library to prevent a potential timing-based attack
  if (!tsscmp(gameSignature, mySignature)) {
    throw new Error("Signature does not match");
  }
};

// Verify game results to ensure fairness
const verifyGameResult = (transactionId, serverSeed, result) => {
  const calculateFloats = (hex) => {
    const CHUNK_SIZE = 8;
    const floats = [];

    while (hex.length >= CHUNK_SIZE) {
      const hexChunk = hex.substring(0, CHUNK_SIZE);
      hex = hex.substring(CHUNK_SIZE);
      const int = parseInt(hexChunk, 16);
      const float = int / 2 ** 32;
      floats.push(float);
    }

    return floats;
  };

  // Find the bet in our database
  const client = db.find((client) =>
    client.bets.find((bet) => bet.transactionId === transactionId)
  );

  if (!client) {
    throw new Error("Transaction not found");
  }

  const bet = client.bets.find((bet) => bet.transactionId === transactionId);

  // Verify server seed
  if (bet.serverSeedHashed !== sha256(serverSeed)) {
    throw new Error("Server seed hash does not match");
  }

  // Calculate the game result
  const floats = calculateFloats(
    sha256(`${bet.clientSeed}:${serverSeed}:${bet.operatorSeed}`)
  );

  // Game event translation (for a dice game, for example)
  const myResult = (floats[0] * 10001) / 100;

  if (result !== myResult) {
    throw new Error("Game result does not match");
  }

  console.log(`✅ Game [${transactionId}] result verified successfully!`);
};

Game Session Initialization

We need a function to initialize a game session with our game provider:

// Function to initialize a game session
async function initializeGameSession(apiUrl, clientId) {
  try {
    const timestamp = timeNow();
    const body = { clientId };

    const response = await fetch(apiUrl, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Casino-Signature": createGameSignature(
          timestamp,
          body,
          GAME_PLATFORM_API_SESSION_SECRET
        ),
        "X-Casino-Request-Timestamp": timestamp,
      },
      body: JSON.stringify({ json: body }),
    });

    if (!response.ok) {
      throw new Error("Failed to initialize game session");
    }

    const data = await response.json();
    return data.result?.data?.json?.sessionId;
  } catch (error) {
    console.error("Session initialization error:", error);
    return null;
  }
}

API Endpoints

Now, let's implement the core API endpoints for our casino:

// Home route to serve our game interface
app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

// Session endpoint - creates a new game session
app.post("/session", async (req, res) => {
  try {
    // Retrieve client's unique identifier
    const clientId = db[0].id;

    const createdSession = await initializeGameSession(
      GAME_PLATFORM_API_URL_SESSION,
      clientId
    );

    if (!createdSession) {
      throw Error("Unable to create a session");
    }

    res.json({
      result: {
        data: {
          json: {
            createdSession,
          },
        },
      },
    });
  } catch (error) {
    res.status(500).json({
      error: "Session creation failed",
    });
  }
});

// Balance endpoint - for checking player balance
app.post("/balance", (req, res) => {
  try {
    verifyGameRequest(
      req.headers["x-hexzard-signature"],
      Number(req.headers["x-hexzard-request-timestamp"]),
      req.body.json,
      GAME_PLATFORM_API_WEBHOOK_SECRET
    );

    const { clientId } = req.body.json;
    const client = db.find((client) => client.id === clientId);

    if (!client) {
      return res.status(404).json({
        error: "Client not found",
      });
    }

    res.json({
      result: {
        data: {
          json: {
            balance: client.balance,
          },
        },
      },
    });
  } catch (error) {
    res.status(400).json({
      error: error.message,
    });
  }
});

// Bet endpoint - for placing bets
app.post("/bet", async (req, res) => {
  try {
    verifyGameRequest(
      req.headers["x-hexzard-signature"],
      Number(req.headers["x-hexzard-request-timestamp"]),
      req.body.json,
      GAME_PLATFORM_API_WEBHOOK_SECRET
    );

    const {
      betAmount,
      gameName,
      transactionId,
      clientId,
      clientSeed,
      serverSeedHashed,
    } = req.body.json;

    const clientIndex = db.findIndex((client) => client.id === clientId);

    if (clientIndex === -1) {
      return res.status(404).json({
        error: "Client not found",
      });
    }

    let betAllowed = false;
    let operatorSeed = null;

    // Check if the player has enough balance
    if (db[clientIndex].balance >= betAmount) {
      betAllowed = true;
      operatorSeed = await randomSecureString();

      // Deduct the bet amount from balance
      db[clientIndex].balance -= betAmount;

      // Store bet information
      db[clientIndex].bets.push({
        transactionId,
        betAmount,
        gameName,
        clientSeed,
        serverSeedHashed,
        operatorSeed,
      });
    }

    res.json({
      result: {
        data: {
          json: {
            betAllowed,
            operatorSeed,
          },
        },
      },
    });
  } catch (error) {
    res.status(400).json({
      error: error.message,
    });
  }
});

// Outcome endpoint - for processing game results
app.post("/outcome", (req, res) => {
  try {
    verifyGameRequest(
      req.headers["x-hexzard-signature"],
      Number(req.headers["x-hexzard-request-timestamp"]),
      req.body.json,
      GAME_PLATFORM_API_WEBHOOK_SECRET
    );

    const { transactionId, multiplier, serverSeed, result } = req.body.json;

    // Verify the game result is fair
    verifyGameResult(transactionId, serverSeed, result);

    // Find the client and bet
    const clientIndex = db.findIndex((client) =>
      client.bets.find((bet) => bet.transactionId === transactionId)
    );

    if (clientIndex === -1) {
      return res.status(404).json({
        error: "Transaction not found",
      });
    }

    const betIndex = db[clientIndex].bets.findIndex(
      (bet) => bet.transactionId === transactionId
    );

    // Update balance with winnings
    db[clientIndex].balance +=
      db[clientIndex].bets[betIndex].betAmount * multiplier;

    // Remove the processed bet
    db[clientIndex].bets.splice(betIndex, 1);

    res.sendStatus(200);
  } catch (error) {
    res.status(400).json({
      error: error.message,
    });
  }
});

The Frontend Interface

To complete our casino, we need a simple frontend interface. Create an index.html file in your project directory:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Node.js Casino</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        background-color: #121212;
        color: #ffffff;
        margin: 0;
        padding: 0;
      }
      .container {
        max-width: 1200px;
        margin: 0 auto;
        padding: 20px;
      }
      header {
        text-align: center;
        padding: 20px 0;
        background-color: #2e2e2e;
        border-bottom: 2px solid #4caf50;
      }
      .game-container {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin-top: 40px;
        background-color: #1e1e1e;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
      }
      #game-frame {
        width: 100%;
        height: 600px;
        border: none;
        margin-top: 20px;
        background-color: #2e2e2e;
        visibility: hidden;
      }
    </style>
  </head>
  <body>
    <header>
      <h1>Node.js Casino</h1>
    </header>
    <div class="container">
      <div class="game-container">
        <iframe id="game-frame" src="https://hexzard.com/games/dice"></iframe>
      </div>
    </div>
    <script>
      const GAME_HOST = "https://hexzard.com";

      // Listen for messages from the game iframe
      window.addEventListener(
        "message",
        async (event) => {
          if (event.origin !== GAME_HOST) return;

          // Handle initialization
          if (event.data.type === "init") {
            const sessionId = await retrieveSessionToken();

            const message = {
              sessionId,
              languageCode: "en",
              currencyCode: "USD",
              minimumBet: 0.01,
              theme: {
                // Theme variables can go here
                backgroundColor: "#121212",
                primaryColor: "#4caf50",
              },
            };

            event.source.postMessage(message, GAME_HOST);
          }
          // Handle ready state
          else if (event.data.type === "ready") {
            // Make iframe visible once loaded
            document.querySelector("#game-frame").style.visibility = "visible";
          }
        },
        false
      );

      // Fetch session token from our backend
      async function retrieveSessionToken() {
        try {
          const res = await fetch("/session", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
          });

          const data = await res.json();
          const sessionId = data?.result?.data?.json?.createdSession;

          if (res.status !== 200 || !sessionId) {
            throw new Error("Failed to retrieve a session id");
          }

          return sessionId;
        } catch (error) {
          console.error("Session retrieval error:", error);
          alert(
            "Unable to connect to the game server. Please try again later."
          );
        }
      }
    </script>
  </body>
</html>

Security Considerations

When building a real-world casino application, consider these critical security aspects:

  1. Use a Production Database: Replace the in-memory database with a proper database system like MongoDB, PostgreSQL, or MySQL.

  2. Implement Proper Authentication: Add user authentication with secure password storage and session management.

  3. Protect API Keys: Store secrets and API keys in environment variables, not in your code.

  4. Rate Limiting: Implement rate limiting to prevent abuse of your API endpoints.

  5. SSL/TLS: Always use HTTPS in production to encrypt data in transit.

  6. Compliance: Ensure your implementation meets regulatory requirements for online gambling in your jurisdiction.

Testing Your Casino

To test your implementation:

  1. Start the server:

    node index.js
    
  2. Open your browser and navigate to http://localhost:3000

  3. You should see the casino interface and, when the game loads, be able to place bets and see your balance update.

Conclusion

You've now built a simple but functional casino platform using Node.js and Express. This implementation includes the core components needed for a secure gambling platform:

  • Secure session management
  • Balance tracking
  • Bet placement and verification
  • Fair outcome verification
  • Player interface

While this implementation is suitable for learning purposes, remember that a production casino platform requires additional security measures, proper databases, and compliance with local gambling regulations.

Happy coding and responsible gambling!