How to build a real-time Auction System with Socket.io and React.js 🤯
Tags: react, node, javascript, programming
Published: 2022-08-01 11:23:07

## What is this article about?
Like an actual auction, if you bid for a product, you get counterbids from other bidders. The auction runs on the "fast" decision bid, where somebody else will win or outbid you if you don't bid fast enough.
 
To use online bidding, We must stick to the same principles. We must give our bidder information as soon as a new bid comes.

![Auction](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z6llf8sobdd8lyr7rmmw.gif)


There are two ways to get live information from your server about a new bid:
1. Use **long-polling HTTP request**, basically an HTTP request every 5 - 10 seconds to get information about a new bid.

2. Use an open-socket (Websockets) to get information directly from the server when a new bid arrives.

In this article I will talk about Websockets and specifically on the Node.js library - Socket.io

## Novu - the first open-source notification architecture
Just a quick background about us. Novu is the first open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in Facebook), Emails, SMSs and so on.

### Looking for new contributors
Come help us out to build the best open-source notification infrastructure, get recognized by the community, and become a Community Hero here:
https://novu.co/contributors

[![Community Heroes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ik8vqph18dqqw0ycp0sj.png)](https://novu.co/contributors)
 

## So what the hack is Socket.io?
[Socket.io](https://socket.io/) is a JavaScript library that enables us to create real-time, bi-directional communication between web browsers and a Node.js server. It is a highly performant library capable of processing a large volume of data within the shortest possible time.

Usually, to get information from the server you need send an HTTP request. With websockets, the server lets you know when there is new information without asking it.

In this article, we'll leverage the real-time communication provided by Socket.io to create a bidding system that allows users to put items up for auction and bid for them. Socket.io will also notify users when an item is up for auction and after a user places a bid.

## How to add Socket.io to React & Node.js applications
In this section, we'll set up the project environment for our bidding system. You'll also learn how to add Socket.io to a React and Node.js application and connect both development servers for real-time communication via Socket.io.

Create the project folder containing two sub-folders named client and server.
```bash
mkdir bidding-system
cd bidding-system
mkdir client server
```

Navigate into the client folder via your terminal and create a new React.js project.

```bash
cd client
npx create-react-app ./
```

Install Socket.io client API and React Router. [React Router](https://reactrouter.com/docs/en/v6) is a JavaScript library that enables us to navigate between pages in a React application.

```bash
npm install socket.io-client react-router-dom
```

Delete the redundant files such as the logo and the test files from the React app, and update the `App.js` file to display Hello World as below.

```jsx
function App() {
  return (
    <div>
      <p>Hello World!</p>
    </div>
  );
}
```

Next, navigate into the server folder and create a `package.json` file.

```bash
cd server
npm init -y
```

Install Express.js, CORS, Nodemon, and Socket.io Server API.

[Express.js](https://expressjs.com/) is a fast, minimalist framework that provides several features for building web applications in Node.js. [CORS](https://www.npmjs.com/package/cors) is a Node.js package that allows communication between different domains.

[Nodemon](https://www.npmjs.com/package/nodemon) is a Node.js tool that automatically restarts the server after detecting file changes, and [Socket.io](https://socket.io/docs/v4/server-api/) allows us to configure a real-time connection on the server. 

```bash
npm install express cors nodemon socket.io 
```

Create an index.js file - the entry point to the web server.

```bash
touch index.js
```

Set up a simple Node.js server using Express.js. The code snippet below returns a JSON object when you visit the [`http://localhost:4000/api`](http://localhost:4000/api) in your browser.

```jsx
//index.js
const express = require('express');
const app = express();
const PORT = 4000;

app.get('/api', (req, res) => {
  res.json({
    message: 'Hello world',
  });
});

app.listen(PORT, () => {
  console.log(`Server listening on ${PORT}`);
});
```

Import the HTTP and the CORS library to allow data transfer between the client and the server domains.

```jsx
const express = require('express');
const app = express();
const PORT = 4000;

//New imports
const http = require('http').Server(app);
const cors = require('cors');

app.use(cors());

app.get('/api', (req, res) => {
  res.json({
    message: 'Hello world',
  });
});

http.listen(PORT, () => {
  console.log(`Server listening on ${PORT}`);
});
```

Next, add Socket.io to the project to create a real-time connection. Before the `app.get()` block, copy the code below.

```jsx
//New imports
.....
const socketIO = require('socket.io')(http, {
    cors: {
        origin: "http://localhost:3000"
    }
});

//Add this before the app.get() block
socketIO.on('connection', (socket) => {
    console.log(`⚡: ${socket.id} user just connected!`);
    socket.on('disconnect', () => {
      console.log('🔥: A user disconnected');
    });
});
```

From the code snippet above, the `socket.io("connection")` function establishes a connection with the React app, then creates a unique ID for each socket and logs the ID to the console whenever a user visits the web page.

When you refresh or close the web page, the socket fires the disconnect event showing that a user has disconnected from the socket.         

Next, configure Nodemon by adding the start command to the list of the scripts in the `package.json` file. The code snippet below starts the server using Nodemon.

```json
//In server/package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon index.js"
  },
```

You can now run the server with Nodemon by using the command below.

```bash
npm start
```

Open the App.js file in the client folder and connect the React app to the Socket.io server.

```jsx
import socketIO from 'socket.io-client';
const socket = socketIO.connect('http://localhost:4000');

function App() {
  return (
    <div>
      <p>Hello World!</p>
    </div>
  );
}
```

Start the React.js server.

```bash
npm start
```

Check the terminal where the server is running; the ID of the React.js client appears in the terminal. 

Congratulations 🥂 , the React app has been successfully connected to the server via Socket.io.

> 💡 For the remaining part of this article, I will walk you through creating the flow of the bidding system, and in the upcoming article in this series, I will guide you through sending messages between the client and the server and saving them in a JSON file.

> 💡 The JSON file will serve as the database for the application. Although this is not a secure way of saving data, this is just a demo, so feel free to use any database of your choice if necessary.

## The Workflow for the Bidding System

Before we start building each component, I'll walk you through the application's workflow.

Here is how it works:

- The Home page:
Users provide only their username, and the application saves this username for identification throughout the application. To keep the tutorial simple, we won't use any authentication library.
- The Products page:
Users can view all the products up for auction, click on each product to bid, and there is a call to action that redirects users to the page where they can add items for auction.
- The Add Products page:
This page allows users to add the name and price of the auction item, then redirects them to the Products page to view the recently added item.
- The Bid page:
Users can bid for the item they selected from the Products page. This page accepts URL parameters containing the name and the price of the chosen item; then displays a form input that allows users to bid up the product.
- The Nav component:
All the pages have the Nav component at the top and display notifications within it. When a user sets a bid or adds a new product, the Nav component notifies every other user.

![Nav](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/71cr59yszm4wes2x9ztn.png)

Without any further ado, create a component folder containing all the pages. Ensure that each page renders an HTML element.

```bash
cd src
mkdir components
cd components
touch Home.js Products.js AddProduct.js BidProduct.js Nav.js
```

Next, Import all the files within the components folder into the App.js file and create a route for each page using [React Router v6](https://reactrouter.com/docs/en/v6/upgrading/v5).

```jsx
//Pages import
import Home from './components/Home';
import AddProduct from './components/AddProduct';
import BidProduct from './components/BidProduct';
import Products from './components/Products';
import Nav from './components/Nav';
import socketIO from 'socket.io-client';
import { Route, Routes, BrowserRouter as Router } from 'react-router-dom';

const socket = socketIO.connect('http://localhost:4000');

function App() {
  return (
    <Router>
      <div>
        {/* Nav is available at the top of all the pages as a navigation bar */}
        <Nav socket={socket} />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<Products />} />
          <Route
            path="/products/add"
            element={<AddProduct socket={socket} />}
          />
          {/* Uses dynamic routing */}
          <Route
            path="/products/bid/:name/:price"
            element={<BidProduct socket={socket} />}
          />
        </Routes>
      </div>
    </Router>
  );
}

export default App;
```

The code snippet declares the route for each page and passes the Socket.io library into the necessary components.

Navigate into the `src/index.css` and copy the code below. It contains all the CSS required for styling this project.

```css
/* --------General Stylesheet for the project ------*/
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap');
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Poppins', sans-serif;
}
body {
  margin: 0;
}

/* --------Stylesheet for the Navigation component ------*/
.navbar {
  width: 100%;
  height: 10vh;
  background-color: #f0ebe3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 20px;
  margin-bottom: 30px;
}
.navbar .header {
  width: 70%;
}

/* --------Stylesheet for the Home component ------*/
.home__form {
  width: 100%;
  height: 80vh;
  padding: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
.home__input,
.addProduct__form input,
.bidProduct__form input {
  width: 70%;
  padding: 10px;
  border-radius: 5px;
  margin: 15px 0;
  outline: none;
  border: 1px solid #576f72;
}
.home__cta {
  width: 200px;
  padding: 10px;
  font-size: 16px;
  outline: none;
  border: none;
  cursor: pointer;
  color: #fff;
  background-color: rgb(67, 143, 67);
}

/* --------Stylesheet for the Products component ------*/
.editIcon {
  height: 20px;
  cursor: pointer;
}
table {
  width: 95%;
  border: 1px solid #576f72;
  margin: 0 auto;
  border-collapse: collapse;
}
tr,
td,
th {
  border: 1px solid #576f72;
  text-align: center;
  padding: 5px;
}
.table__container {
  display: flex;
  align-items: center;
  flex-direction: column;
}
.products__cta {
  width: 70%;
  background-color: rgb(67, 143, 67);
  padding: 15px;
  color: #fff;
  margin-bottom: 35px;
  border-radius: 5px;
  text-decoration: none;
  text-align: center;
}

/* --------Stylesheet for the AddProducts & BidProducts component ------*/
.addproduct__container,
.bidproduct__container {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
}
.addproduct__container h2,
.bidproduct__container h2 {
  margin-bottom: 30px;
}
.addProduct__form,
.bidProduct__form {
  display: flex;
  flex-direction: column;
  width: 80%;
  margin: 0 auto;
}
.addProduct__cta,
.bidProduct__cta {
  width: 200px;
  padding: 10px;
  font-size: 16px;
  outline: none;
  border: none;
  color: #fff;
  background-color: rgb(67, 143, 67);
  cursor: pointer;
}
.bidProduct__name {
  margin-bottom: 20px;
}
```

Congratulations 💃🏻, we can start coding every part of the project.

## Creating the Home page of the application

In this section, we'll create the home page for the bidding system. The page will accept the username from the user and then save it to the local storage for identification throughout the application.

Update the `Home.js` file to render a form field that accepts a minimum of six letters as username.

```jsx
import React, { useState } from 'react';

const Home = () => {
  const [userName, setUserName] = useState('');

  return (
    <div>
      <form className="home__form" onSubmit={handleSubmit}>
        <label htmlFor="username">Enter your username</label>
        <input
          type="text"
          name="username"
          className="home__input"
          value={userName}
          onChange={(e) => setUserName(e.target.value)}
          required
          minLength={6}
        />
        <button className="home__cta">SIGN IN</button>
      </form>
    </div>
  );
};

export default Home;
```

Create the `handleSubmit` function that stores the username in the local storage, then redirects the user to the Products page after submitting the form.

From the code snippet below, the `useNavigate` hook enables us to redirect users between pages.

```jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const Home = () => {
  const [userName, setUserName] = useState('');
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    localStorage.setItem('userName', userName);
    navigate('/products');
  };

  return <div>.....</div>;
};

export default Home;
```

## Creating the Products page

In this section, I'll walk you through creating a simple layout that displays each product and the related information. The product details include the name, price, owner, and the last bidder.
A table layout containing each product on every row is the least complicated layout for this data structure.
So, let's code it! 💪

![Products Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lmci2o6iivcuelm8mpzi.png)

Update the `Products.js` to display a table containing two products with four columns containing the name, price, last bidder, and the creator.

```jsx
import React from 'react';
const Products = () => {
  return (
    <div>
      <div className="table__container">
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Price</th>
              <th>Last Bidder</th>
              <th>Creator</th>
            </tr>
          </thead>
          {/* Data for display, we will later get it from the server */}
          <tbody>
            <tr>
              <td>Tesla Model S</td>
              <td>$30,000</td>
              <td>@david_show</td>
              <td>@elon_musk</td>
            </tr>

            <tr>
              <td>Ferrari 2021</td>
              <td>$50,000</td>
              <td>@bryan_scofield</td>
              <td>@david_asaolu</td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
};

export default Products;
```

We've been able to display the items available for auction to the users. Next, we need to allow users to add a product and bid on each item. An easy way is to create a hyperlink that links to the Add Products page and an edit button for bidding on items.

Update the `Products` page to contain the edit button and a call to action for adding products.

```jsx
import React from 'react';
import { Link } from 'react-router-dom';

const Products = () => {
  return (
    <div>
      <div className="table__container">
        <Link to="/products/add" className="products__cta">
          ADD PRODUCTS
        </Link>

        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Price</th>
              <th>Last Bidder</th>
              <th>Creator</th>
              <th>Edit</th>
            </tr>
          </thead>
          {/* Data for display, we will later get it from the server */}
          <tbody>
            <tr>
              <td>Tesla Model S</td>
              <td>$30,000</td>
              <td>@david_show</td>
              <td>@elon_musk</td>
              <td>
                <button>Edit</button>
              </td>
            </tr>

            <tr>
              <td>Ferrari 2021</td>
              <td>$50,000</td>
              <td>@bryan_scofield</td>
              <td>@david_asaolu</td>
              <td>
                <button>Edit</button>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  );
};

export default Products;
```

## Creating the Add Product page

In this section, we'll create the `AddProduct` page containing a form with two input fields for the name and price of the product up for auction and a submit button.

![Product Page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5kuhh57vg9h9w3to6z59.png)

```jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const AddProduct = () => {
  const [name, setName] = useState('');
  const [price, setPrice] = useState(0);
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log({ name, price, owner: localStorage.getItem('userName') });
    navigate('/products');
  };

  return (
    <div>
      <div className="addproduct__container">
        <h2>Add a new product</h2>
        <form className="addProduct__form" onSubmit={handleSubmit}>
          <label htmlFor="name">Name of the product</label>
          <input
            type="text"
            name="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            required
          />

          <label htmlFor="price">Starting price</label>
          <input
            type="number"
            name="price"
            value={price}
            onChange={(e) => setPrice(e.target.value)}
            required
          />

          <button className="addProduct__cta">SEND</button>
        </form>
      </div>
    </div>
  );
};

export default AddProduct;
```

From the code above, the `handleSubmit` button collects the user's input from the form and logs it to the console before redirecting to the Products page. The username saved to the local storage is also attached to the item as the product owner.

## Creating the Bid page

The Bid page is quite similar to the `AddProduct` page. It contains a form with an input field for the bid price of the selected product and a call to action. After a user places a bid, it redirects them to the Product page.

```jsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';

const BidProduct = () => {
  const [userInput, setUserInput] = useState(0);
  const navigate = useNavigate();

  const handleSubmit = (e) => {
    e.preventDefault();
    navigate('/products');
  };

  return (
    <div>
      <div className="bidproduct__container">
        <h2>Place a Bid</h2>
        <form className="bidProduct__form" onSubmit={handleSubmit}>
          <h3 className="bidProduct__name">Product Name</h3>

          <label htmlFor="amount">Bidding Amount</label>
          <input
            type="number"
            name="amount"
            value={userInput}
            onChange={(e) => setUserInput(e.target.value)}
            required
          />

          <button className="bidProduct__cta">SEND</button>
        </form>
      </div>
    </div>
  );
};

export default BidProduct;
```

## Creating the Nav component

The Nav component is at the top of every page (according to the App.js file). It represents the app's notification center - where users view the notifications from Socket.io.

Update the `Nav.js` file to render a `<nav>` element as below. The h2 element represents the logo, and the notification container is on the right-hand side of the screen.

```jsx
import React from 'react';

const Nav = () => {
  return (
    <nav className="navbar">
      <div className="header">
        <h2>Bid Items</h2>
      </div>

      <div>
        <p style={{ color: 'red' }}>My notifications are here</p>
      </div>
    </nav>
  );
};

export default Nav;
```

Congratulations, we've completed the first part of this series. Next week article in this series, I'll walk you through sending messages between the React app and the Node.js server.

You can find the full-source code here:
https://github.com/novuhq/blog/tree/main/bidding%20system%20using%20socketIO

Make sure you follow me to get a notification once I release the next part of the series!
https://dev.to/nevodavid

![Nevo David](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gl1lrmjo1w50shge7unc.png)


Thank you for reading! 🥂