DEV Community

Cover image for Watchlist application using ReactJS, NodeJS
Deepak Jaiswal
Deepak Jaiswal

Posted on

4 1 1 1 1

Watchlist application using ReactJS, NodeJS

today we create watchlist application using reactjs, nodejs.

Backend

config.js

{
    "DB_URL": "mongodb+srv://user:password@cluster0.wb0iyao.mongodb.net/?retryWrites=true&w=majority"
}
Enter fullscreen mode Exit fullscreen mode

default.js

module.exports.defaultSymbols=[
    {
        symbol:"SYY",
        bid:"sp",
        ask:"sp",
        bidSize:3,
        askSize:4,
        volume:5,
        description:"Fixed wrong interpolated variables with node-sass/Hugo"
    },
    {
        symbol:"KJJ",
        bid:"d",
        ask:"d",
        bidSize:3,
        askSize:4,
        volume:5,
        description:"Fixed wrong interpolated variables with node-sass/Hugo"
    },
    {
        symbol:"LLL",
        bid:"ru",
        ask:"ru",
        bidSize:3,
        askSize:4,
        volume:5,
        description:"Fixed wrong interpolated variables with node-sass/Hugo"
    },
    {
        symbol:"NHD",
        bid:"nd",
        ask:"nd",
        bidSize:2,
        askSize:6,
        volume:0,
        description:"Fixed wrong interpolated variables with node-sass/Hugo"
    },
    {
        symbol:"QWA",
        bid:"ts",
        ask:"ts",
        bidSize:7,
        askSize:4,
        volume:1,
        description:"Fixed wrong interpolated variables with node-sass/Hugo"
    }
]
Enter fullscreen mode Exit fullscreen mode

watchlist.controller.js

const WatchListModel = require("../models/WatchList")

module.exports.getWatchLists=async(req,res,next)=>{
    try {
        const watchLists=await WatchListModel.find({});
        res.status(200).send(watchLists);
    } catch (error) {
        next(error)
    }
}

module.exports.addSymbol=async(req,res,next)=>{
    try {
        const {symbol,bid,ask,bidSize,askSize,volume}=req.body;
        const symbolObj={
            symbol,
            bid,
            ask,
            bidSize,
            askSize,
            volume
        }
        const watchList=await WatchListModel.findOneAndUpdate({
            _id: req.params.id
        },{
          $push:{
            symbols:symbolObj
          }
        },{new:true})
        res.status(200).send(watchList);
    } catch (error) {
        next(error)
    }
}

module.exports.updateSymbol=async(req,res,next)=>{
    try {
        const {symbolId,symbol,bid,ask,bidSize,askSize,volume,description}=req.body;
        const symbolObj={
            symbol,
            bid,
            ask,
            bidSize,
            askSize,
            volume,description
        }
        let watchList=await WatchListModel.findOne({
            _id: req.params.id
        })
        const index=watchList?.symbols.findIndex((symbol)=>symbol?._id?.toString()==symbolId);
        watchList.symbols[index]=symbolObj;
        watchList=await WatchListModel.findOneAndUpdate({_id: req.params.id},{$set:{
            symbols:watchList?.symbols
        }},{new:true})
        res.status(200).send(watchList);
    } catch (error) {
        next(error)
    }
}

module.exports.removeSymbol=async(req,res,next)=>{
    try {
        const {symbolId}=req.body
        let watchList=await WatchListModel.findOne({
            _id: req.params.id
        })
        const updatedSymbols=watchList?.symbols.filter((symbol)=>symbol?._id?.toString()!==symbolId);
        watchList=await WatchListModel.findOneAndUpdate({_id: req.params.id},{$set:{
            symbols:updatedSymbols
        }},{new:true})
        res.status(200).send(watchList);
    } catch (error) {
        next(error)
    }
}


Enter fullscreen mode Exit fullscreen mode

createDefaultwatchlist.middleware.js

const WatchListModel = require("../models/WatchList")
const symbols=require('../constants/defaults')

module.exports.createDefaultWatchList=async(req,res,next)=>{
    try {
        let watchList=await WatchListModel.findOne({});
        console.log(watchList)
        if(!watchList){
            watchList=new WatchListModel({symbols:symbols.defaultSymbols});
            watchList=await watchList.save();
            next(watchList)
        }
    } catch (error) {
        next(error)
    }
}
Enter fullscreen mode Exit fullscreen mode

error.middleware.js


module.exports=async(err,req,res)=>{
    try {
        if(err){
            return res.status(500).send({status:500,message:err})
        }
    } catch (error) {
        console.log(error.body);
    }
}
Enter fullscreen mode Exit fullscreen mode

validateObjectId.middleware.js

const mongoose= require("mongoose")

module.exports.validateObjectId=(req,res,next)=>{
    try {
        if(!mongoose.Types.ObjectId.isValid(req.params.id))
            return res.status(400).send({message:"Invalid Id",status:400});
        next(); 
    } catch (error) {
        next(error)
    }
}
Enter fullscreen mode Exit fullscreen mode

watchlist.model.js

const mongoose=require('mongoose');

const {Schema,model}=mongoose;

const watchlistSchema=new Schema({
    symbols:[
        {
            symbol:{
                type:String,
                required: true,
                index:true
            },
            bid:{
                type:String,
                default:''
            },
            ask:{
                type:String,
                default:''
            },
            bidSize:{
                type:Number,
                default:null
            },
            askSize:{
                type:Number,
                default:null
            },
            volume:{
                type:Number,
                default:null
            },
            description:{
                type:String,
                default: ""
            }
        }
    ]
})

const WatchListModel=model('watchlist',watchlistSchema);

module.exports=WatchListModel;
Enter fullscreen mode Exit fullscreen mode

watchlist.route.js

const express=require('express');
const { createDefaultWatchList } = require("../middlewares/createDefaultWatchList.middleware");
const WatchListModel = require("../models/WatchList.model");
const router=express.Router();
const symbols=require('../constants/defaults');
const { addSymbol, getWatchLists, removeSymbol, updateSymbol } = require("../controllers/watchlist.controller");
const { validateObjectId } = require('../middlewares/validateObjectId.middleware');

router.get('',getWatchLists);

router.post('/create-default',createDefaultWatchList);

router.post('/add-symbol/:id',validateObjectId,addSymbol);

router.patch('/update-symbol/:id',validateObjectId,updateSymbol);

router.patch('/remove-symbol/:id',validateObjectId,removeSymbol);

module.exports=router;


(async ()=>{
    let watchList=await WatchListModel.findOne({});
    if(!watchList){
        watchList=new WatchListModel({symbols:symbols.defaultSymbols});
        watchList=await watchList.save();
        console.log('default watch list created')
    }
})()
Enter fullscreen mode Exit fullscreen mode

connection.js

const mongoose=require('mongoose');
const config=require('config');

const DB_URL=config.get('DB_URL');

module.exports=mongoose.connect(DB_URL).then((conn)=>{
    console.log(`Database connected on mongo server ${DB_URL}`);   
}).catch((err)=>{
    console.log(`Database Error ->:  ${err}`);  
})
Enter fullscreen mode Exit fullscreen mode

app.js

const express=require('express');
const cors=require('cors');

require('./services/connection');
const errorMiddleware=require('./middlewares/error.middleware');
const watchListRouter=require('./routes/watchlist.route')

const app=express();
const PORT=process.env.PORT|| 8000;
//middlewares
app.use(express.json());
app.use(express.urlencoded({extended:false}));
app.use(cors());
//routes of app
app.use('/api/v1/watchlist',watchListRouter);
//global error middleware 

app.use(errorMiddleware)

app.listen(PORT,()=>{
    console.log(`server is running on the port ${PORT}`)
})

Enter fullscreen mode Exit fullscreen mode

package.json

{
  "name": "backend",
  "version": "1.0.0",
  "description": "",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node app.js",
    "dev": "nodemon app.js"
  },
  "keywords": [],
  "author": "Sandeep Jaiswal",
  "license": "MIT",
  "dependencies": {
    "config": "^3.3.9",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "mongoose": "^7.0.3"
  }
}

Enter fullscreen mode Exit fullscreen mode

Frontend

Header.js

import React from 'react'

const Header = () => {
    return (
        <div className="container-fluid bg-primary">
            <nav className="navbar navbar-expand-lg bg-body-tertiary">
                <div className="container-fluid">
                    <a className="navbar-brand text-white fw-bold" href="#">Watchlist</a>
                </div>
            </nav>
        </div>
    )
}

export default Header
Enter fullscreen mode Exit fullscreen mode

UpdateWatch.js

import React, { useEffect, useState } from 'react'

const UpdateWatch = ({ data, updateSymbol }) => {
  const [form, setForm] = useState({
    symbolId: '', symbol: '', bid: '', ask: '', bidSize: null, askSize: null, volume: null, description: ''
  })
  useEffect(() => {
    setForm({ symbolId: data?._id, symbol: data?.symbol,
       bid: data?.bid, ask: data?.ask, bidSize: data?.bidSize||0,
        askSize: data?.askSize||0 ,volume:data?.volume||0,description:data?.description});
  }, [data?._id])

  const change = (event) => {
    const { name, value } = event.target;
    setForm({ ...form, [name]: value })
  }

  const submit = () => {
    updateSymbol(form);
    setForm({
      symbolId: '', symbol: '', bid: '', ask: '', bidSize: 0, askSize: 0, volume: 0, description: ''
    })
  }
  return (
    <div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
      <div class="modal-dialog">
        <div class="modal-content">
          <div class="modal-header">
            <h1 class="modal-title fs-5" id="exampleModalLabel">Update Symbol</h1>
            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
          </div>
          <div class="modal-body">
            <form>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Symbol:</label>
                <input type="text" name="symbol" value={form.symbol} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Bid:</label>
                <input type="text" name="bid" value={form.bid} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">ask:</label>
                <input type="text" name="ask" value={form.ask} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Bid Size:</label>
                <input type="number" name="bidSize" value={form.bidSize} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Ask Size:</label>
                <input type="number" name="askSize" value={form.askSize} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Volume:</label>
                <input type="number" name="volume" value={form.volume} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
              <div class="mb-3">
                <label for="recipient-name" class="col-form-label">Description:</label>
                <input type="text" name="description" value={form.description} onChange={(e) => change(e)} class="form-control" id="recipient-name" />
              </div>
            </form>
          </div>
          <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
            <button type="button" class="btn btn-primary" data-bs-dismiss="modal" onClick={() => submit()}> Update</button>
          </div>
        </div>
      </div>
    </div>
  )
}

export default UpdateWatch
Enter fullscreen mode Exit fullscreen mode

watchlist.js

import React, { useContext, useState } from 'react'
import WatchListContext from "../contexts/WatchListContext";
import { addSymbols, removeSymbols, updateSymbols } from "../services/watchList.service";
import UpdateWatch from "./common/UpdateWatch";

const WatchList = () => {
    const [watchlist, setWatchlist] = useContext(WatchListContext);
    const [updateObject,setUpdateObject]=useState({});
    const [selectedSymbol,setSelectedSymbol]=useState([])
    const addSymbol = async (event) => {
        if (event.keyCode === 13) {
            const res = await addSymbols(watchlist?._id, { symbol: event.target.value });
            setWatchlist(res.data);
            event.target.value = '';
        }
    }

    const removeSymbol = async (symbolId) => {
            const res = await removeSymbols(watchlist?._id, { symbolId });
            setWatchlist(res.data);
    }

    const updateSymbol = async (data) => {
        const res = await updateSymbols(watchlist?._id, data)
        setWatchlist(res.data);
    }
    const toggleSelect=(symbol)=>{
        const check=selectedSymbol.find(s=>s?._id===symbol?._id)
        if(check){
            let s=selectedSymbol.filter(sym=>sym?._id!==symbol?._id)
            setSelectedSymbol(s);
        }else{
            let s=[...selectedSymbol,symbol]
            setSelectedSymbol(s);
        }
    }
    return (
        <div>
            <div className="p-4">
                <div className="row">
                    <div className="col-3 fs-2">
                        Quote Details
                    </div>
                    <div className="col-9">
                        {selectedSymbol?.length ? 
                            selectedSymbol?.map((s)=>(
                                <div key={s?._id}>
                                    <h5>{s?.symbol}</h5>
                                    <p>{s?.description}</p>
                                </div>
                            )
                        ):"No Any row selected"}
                    </div>
                </div>
                <div className="border">
                    <div className="bg-light p-2 fw-bold">My Watchlist</div>
                    <table className="table table-bordered">
                        <thead>
                            <tr>
                                <th scope="col"></th>
                                <th scope="col">Bid</th>
                                <th scope="col">Ask</th>
                                <th scope="col">Bid Size</th>
                                <th scope="col">Ask Size</th>
                                <th scope="col">Volume</th>
                                <th scope="col">Action</th>
                            </tr>
                        </thead>
                        <tbody>
                        {watchlist?.symbols.map((symbol) => (
                        <tr key={symbol?._id} onClick={()=>toggleSelect(symbol)} className={selectedSymbol?.find(s=>s?._id===symbol?._id)?'table-success':''}>
                        <th scope="row">{symbol?.symbol}</th>
                        <td>{symbol?.bid}</td>
                        <td>{symbol?.ask}</td>
                        <td>{symbol?.bidSize}</td>
                        <td>{symbol?.askSize}</td>
                        <td>{symbol?.volume}</td>
                        <td>
                            <div className="d-flex justify-content-around">
                            <button className="btn btn-primary" data-bs-toggle="modal" data-bs-target="#exampleModal" data-bs-whatever="@mdo" onClick={()=>setUpdateObject(symbol)}>Edit</button>
                            <button className="btn btn-danger" onClick={()=>removeSymbol(symbol?._id)}>Delete</button>
                            </div></td>
                    </tr>    
                    ))}

                        </tbody>
                    </table>
                    <div className="bg-light p-1 w-100" style={{ height: 40 }}>
                        Add Symbol <input className="ms-2 rounded" placeholder="" onKeyUp={(e) => addSymbol(e)} />
                    </div>
                </div>
                <UpdateWatch data={updateObject} updateSymbol={updateSymbol}/>
            </div>
        </div>
    )
}

export default WatchList
Enter fullscreen mode Exit fullscreen mode

watchlist.service.js

import { toast } from "react-toastify";

const { http, URL } = require("./http.service")


export const getWatchList = async () => {
    try {
        const res = await http.get(URL + '/watchlist');
        return res;
    } catch (error) {
        handleError(error);
    }
}

export const addSymbols = async (watchListId,payload) => {
    try {
        const res = await http.post(URL + '/watchlist/add-symbol/'+watchListId, payload);
        return res;
    } catch (error) {
        handleError(error);
    }
}

export const removeSymbols = async (watchListId, payload) => {
    try {
        const res = await http.patch(URL +'/watchlist/remove-symbol/' + watchListId, payload);
        return res;
    } catch (error) {
        handleError(error);
    }
}

export const updateSymbols = async (watchListId, payload) => {
    try {
        const res = await http.patch(URL +'/watchlist/update-symbol/' + watchListId, payload);
        return res;
    } catch (error) {
        handleError(error);
    }
}

export const isLogged = () => {
    const token = localStorage.getItem('token');
    return token ? true : false;
}

export const logoutUser = () => {
    localStorage.removeItem('token');
}

export const getToken = () => {
    return localStorage.getItem('token');
}

export const handleError = (error) => {
    toast.error(error?.message)
}

export const setCredentials=(token)=>{
    http.defaults.headers.common['x-auth-token'] = token;
}
Enter fullscreen mode Exit fullscreen mode

App.js

import { useEffect, useState } from "react";
import './App.css';
import WatchList from "./components/WatchList";
import WatchListContext from "./contexts/WatchListContext";
import { getWatchList } from "./services/watchList.service";
import Header from "./components/common/Header";
import Loader from "./components/common/Loader";

function App() {
  const [watchlist,setWatchlist]=useState();
  const [isLoading,setIsLoading]=useState(false);

  useEffect(()=>{
    getWatchListData()
  },[]);

  const getWatchListData=async ()=>{
    setIsLoading(true)
    const result=await getWatchList();
    if(result.status ===200 ){
      setWatchlist(result?.data?.[0])
      setIsLoading(false)
    }else{
      setIsLoading(false)
    }
  }

  return (
    <WatchListContext.Provider value={[watchlist,setWatchlist]}>
         <Header />
         {isLoading?<Loader />:<WatchList />}
    </WatchListContext.Provider>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

this is not of the complete code if you have any doubt regarding this comment in this post. I give the answer as soon as possible. thank you for reading my post.

Billboard image

Monitoring as code

With Checkly, you can use Playwright tests and Javascript to monitor end-to-end scenarios in your NextJS, Astro, Remix, or other application.

Get started now!

Top comments (0)

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay