Open In App

Stock Portfolio Tracker using MERN Stack

Investing in the stock market can be both exciting and daunting. With so many stocks to keep track of and market fluctuations to monitor, managing your portfolio can quickly become overwhelming. But fear not! With the power of technology, we can simplify this process through the creation of a Stock Portfolio Tracker using the MERN Stack.

Output Preview: Let us have a look at how the final output will look like.

Screenshot-2024-03-23-105114

Prerequisites:

Approach to create Stock Portfolio Tracker

Steps to Create the Project

Step 1: Create a folder for the backend

mkdir stock-portfolio-backend
cd stock-portfolio-backend

Step 2:Initialize the Node project using the following command.

npm init -y
npm install express mongoose dotenv

Backend Dependencies:-

"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.2.0"
}

Folder Structure(Backend):

Screenshot-2024-03-04-112718

Code Example: Create the required files as shown in folder structure and add the following codes.

//server.js

const express = require("express");
const mongoose = require("mongoose");
const bodyParser = require("body-parser");
const cors = require("cors");

const app = express();
const PORT = process.env.PORT || 3000;
const MONGODB_URI =
    process.env.MONGODB_URI || "mongodb://localhost:27017/stock_portfolio";

mongoose
    .connect(MONGODB_URI, { useNewUrlParser: true, useUnifiedTopology: true })
    .then(() => console.log("Connected to MongoDB"))
    .catch((err) => console.error("Failed to connect to MongoDB", err));

const Portfolio = mongoose.model("Portfolio", {
    name: String,
    stocks: [
        {
            symbol: String,
            quantity: Number,
            purchasePrice: Number,
            currentPrice: Number,
        },
    ],
});

app.use(bodyParser.json());

app.use(cors());

app.post("/api/portfolios", async (req, res) => {
    try {
        const { name, stocks } = req.body;
        const portfolio = new Portfolio({ name, stocks });
        await portfolio.save();
        res.json(portfolio);
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
});

app.get("/api/portfolios", async (req, res) => {
    try {
        const portfolios = await Portfolio.find();
        res.json(portfolios);
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
});

app.get("/api/portfolios/:id", async (req, res) => {
    try {
        const portfolio = await Portfolio.findById(req.params.id);
        if (!portfolio) throw new Error("Portfolio not found");
        res.json(portfolio);
    } catch (error) {
        res.status(404).json({ error: error.message });
    }
});

app.put("/:portfolioId/stocks/:stockId", async (req, res) => {
    try {
        const { portfolioId, stockId } = req.params;
        const { name, symbol, quantity, purchasePrice, currentPrice } = req.body;

        const portfolio = await Portfolio.findById(portfolioId);

        const stockIndex = portfolio.stocks.findIndex(
            (stock) => stock._id.toString() === stockId
        );

        if (stockIndex === -1) {
            return res
                .status(404)
                .json({ message: "Stock not found in the portfolio" });
        }

        portfolio.stocks[stockIndex].name = name;
        portfolio.stocks[stockIndex].symbol = symbol;
        portfolio.stocks[stockIndex].quantity = quantity;
        portfolio.stocks[stockIndex].purchasePrice = purchasePrice;
        portfolio.stocks[stockIndex].currentPrice = currentPrice;

        await portfolio.save();

        res.status(200).json(portfolio.stocks[stockIndex]);
    } catch (error) {
        console.error("Error updating stock:", error);
        res.status(500).json({ message: "Internal server error" });
    }
});

app.delete("/:portfolioId/stocks/:stockId", async (req, res) => {
    try {
        const { portfolioId, stockId } = req.params;

        const portfolio = await Portfolio.findById(portfolioId);

        portfolio.stocks = portfolio.stocks.filter(
            (stock) => stock._id.toString() !== stockId
        );

        await portfolio.save();

        res.status(200).json({ message: "Stock deleted successfully" });
    } catch (error) {
        console.error("Error deleting stock:", error);
        res.status(500).json({ message: "Internal server error" });
    }
});
app.put("/api/portfolios/:id", async (req, res) => {
    try {
        const { name, stocks } = req.body;
        const portfolio = await Portfolio.findByIdAndUpdate(
            req.params.id,
            { name, stocks },
            { new: true }
        );
        if (!portfolio) throw new Error("Portfolio not found");
        res.json(portfolio);
    } catch (error) {
        res.status(404).json({ error: error.message });
    }
});

app.delete("/api/portfolios/:id", async (req, res) => {
    try {
        const portfolio = await Portfolio.findByIdAndDelete(req.params.id);
        if (!portfolio) throw new Error("Portfolio not found");
        res.json({ message: "Portfolio deleted successfully" });
    } catch (error) {
        res.status(404).json({ error: error.message });
    }
});

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

To start the backend run the following commnand.

node server.js

Step 4: Create frontend porject using the following command:-

npx create-react-app stock-portfolio-tracker
cd stock-portfolio-tracker

Step 5: Install the required dependencies:

npm install axios

Frontend Dependnecies:

"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.7",
"html-entities": "^2.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}

Folder Structure(Frontend):

Screenshot-2024-03-04-112758


Code Example: Create the required files as shown in folder structure and add the following codes.

/* App.css */

body {
    font-family: Arial, sans-serif;
    background-color: #393e46;
    color: #fff;
}

.container {
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
}

.title {
    text-align: center;
    margin-bottom: 30px;
}

.sub-title {
    margin-bottom: 20px;
}

.input-container {
    margin-bottom: 20px;
}

.input-label {
    display: block;
    margin-bottom: 5px;
    color: #fff;
}

.input-field {
    width: 100%;
    padding: 10px;
    font-size: 16px;
    border-radius: 5px;
    border: 1px solid #fff;
    background-color: rgba(255, 255, 255, 0.1);
    color: #fff;
}

.add-button,
.edit-button,
.delete-button {
    background-color: #f0f1f1;
    color: #000000;
    border: none;
    padding: 10px 20px;
    font-size: 16px;
    border-radius: 5px;
    margin: 0.5%;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

.add-button:hover,
.edit-button:hover,
.delete-button:hover {
    background-color: #364860;
    color: #f0f1f1;
}

.portfolio {
    background-color: #f3f3f3;
    padding: 20px;
    margin-bottom: 20px;
    border-radius: 10px;
    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);
}

.stock-table {
    width: 100%;
    border-collapse: collapse;
    margin-bottom: 15px;
    border-radius: 20px;
}

.stock-table th,
.stock-table td {
    padding: 10px;
    text-align: left;
    background-color: #2d2d2d;
    border-bottom: 1px solid #ddd;

}

.stock-table th {

    color: #fff;

}

.delete-button,
.edit-button {
    margin-top: 10px;
}

.profit {
    background-color: #c5d86d;
}

.loss {
    background-color: #ff847c;
}
//App.js

import React, { useState, useEffect } from "react";
import axios from "axios";
import "./App.css";

const App = () => {
    const [portfolios, setPortfolios] = useState([]);
    const [formData, setFormData] = useState({
        name: "",
        symbol: "",
        quantity: "",
        purchasePrice: "",
        currentPrice: "",
    });
    const [loading, setLoading] = useState(true);
    const [editingPortfolio, setEditingPortfolio] = useState(null);
    const [showProfit, setShowProfit] = useState(false);
    const [showLoss, setShowLoss] = useState(false);
    const [editingStock, setEditingStock] = useState(null);

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

    useEffect(() => {
        if (editingPortfolio) {
            window.scrollTo({ top: 0, behavior: "smooth" });
        }
    }, [editingPortfolio]);

    const fetchPortfolios = async () => {
        try {
            const response = await axios.get("http://localhost:3000/api/portfolios");
            setPortfolios(response.data);
            setLoading(false);
        } catch (error) {
            console.error("Error fetching portfolios:", error);
        }
    };

    const calculateCurrentValue = (portfolio) => {
        let currentValue = 0;
        for (const stock of portfolio.stocks) {
            currentValue += stock.quantity * stock.currentPrice;
        }
        return currentValue;
    };

    const calculateInvestedAmount = (portfolio) => {
        let investedAmount = 0;
        for (const stock of portfolio.stocks) {
            investedAmount += stock.quantity * stock.purchasePrice;
        }
        return investedAmount;
    };

    const handleAddPortfolio = async () => {
        try {
            const newStock = { ...formData };
            const response = await axios.post(
                "http://localhost:3000/api/portfolios",
                { name: formData.name, stocks: [newStock] }
            );
            setPortfolios([...portfolios, response.data]);
            setFormData({
                name: "",
                symbol: "",
                quantity: "",
                purchasePrice: "",
                currentPrice: "",
            });
        } catch (error) {
            console.error("Error adding portfolio:", error);
        }
    };

    const deletePortfolio = async (id) => {
        try {
            await axios.delete(`http://localhost:3000/api/portfolios/${id}`);
            setPortfolios(portfolios.filter((portfolio) => portfolio._id !== id));
        } catch (error) {
            console.error("Error deleting portfolio:", error);
        }
    };

    const handleSubmitEdit = async () => {
        try {
            const response = await axios.put(
                `http://localhost:3000/api/portfolios/${editingPortfolio._id}`,
                formData
            );
            const updatedPortfolios = portfolios.map((portfolio) =>
                portfolio._id === response.data._id ? response.data : portfolio
            );
            setPortfolios(updatedPortfolios);
            setEditingPortfolio(null);
            setFormData({
                name: "",
                symbol: "",
                quantity: "",
                purchasePrice: "",
                currentPrice: "",
            });
        } catch (error) {
            console.error("Error editing portfolio:", error);
        }
    };

    const handleEditPortfolio = (portfolio) => {
        setEditingPortfolio(portfolio);
        setFormData({
            name: portfolio.name,
            symbol: portfolio.symbol,
            quantity: portfolio.quantity,
            purchasePrice: portfolio.purchasePrice,
            currentPrice: portfolio.currentPrice,
        });
    };

    const calculateProfitLossPercentage = (portfolio) => {
        const currentValue = calculateCurrentValue(portfolio);
        const investedAmount = calculateInvestedAmount(portfolio);
        const profitLoss = currentValue - investedAmount;
        return ((profitLoss / investedAmount) * 100).toFixed(2);
    };

    const getProfitLossClass = (portfolio) => {
        const currentValue = calculateCurrentValue(portfolio);
        const investedAmount = calculateInvestedAmount(portfolio);
        const profitLoss = currentValue - investedAmount;
        return profitLoss >= 0 ? "profit" : "loss";
    };

    const handleShowProfit = () => {
        setShowProfit(true);
        setShowLoss(false);
    };

    const handleShowLoss = () => {
        setShowProfit(false);
        setShowLoss(true);
    };

    const handleEditStock = (portfolioId, stockId) => {
        setEditingPortfolio(portfolioId);
        setEditingStock(stockId); 
    };

    const handleSaveEditedStock = async (portfolioId, stockId, updatedStock) => {
        try {
            const response = await axios.put(
                `http://localhost:3000/api/portfolios/${portfolioId}/stocks/${stockId}`,
                updatedStock
            );
            const updatedPortfolios = portfolios.map((portfolio) => {
                if (portfolio._id === portfolioId) {
                    const updatedStocks = portfolio.stocks.map((stock) => {
                        return stock._id === stockId ? response.data : stock;
                    });
                    return { ...portfolio, stocks: updatedStocks };
                }
                return portfolio;
            });
            setPortfolios(updatedPortfolios);
            setEditingPortfolio(null);
            setEditingStock(null); 
        } catch (error) {
            console.error("Error updating stock:", error);
        }
    };

    const handleDeleteStock = async (portfolioId, stockId) => {
        try {
            await axios.delete(
                `http://localhost:3000/api/portfolios/${portfolioId}/stocks/${stockId}`
            );
            const updatedPortfolios = portfolios.map((portfolio) => {
                if (portfolio._id === portfolioId) {
                    const updatedStocks = portfolio.stocks.filter(
                        (stock) => stock._id !== stockId
                    );
                    return { ...portfolio, stocks: updatedStocks };
                }
                return portfolio;
            });
            setPortfolios(updatedPortfolios);
        } catch (error) {
            console.error("Error deleting stock:", error);
        }
    };

    return (
        <div className="container">
            <h1 className="title">Stock Portfolio Tracker</h1>

            <div className="add-edit-form">
                <h2 className="sub-title">
                    {editingPortfolio ? "Edit Portfolio" : "Add New Portfolio"}
                </h2>
                <div className="input-container">
                    <label htmlFor="name" className="input-label">
                        Name:
                    </label>
                    <input
                        type="text"
                        id="name"
                        value={formData.name}
                        onChange={(e) => setFormData({ ...formData, name: e.target.value })}
                        className="input-field"
                    />
                </div>
                <div className="input-container">
                    <label htmlFor="symbol" className="input-label">
                        Symbol:
                    </label>
                    <input
                        type="text"
                        id="symbol"
                        value={formData.symbol}
                        onChange={(e) =>
                            setFormData({ ...formData, symbol: e.target.value })
                        }
                        className="input-field"
                    />
                </div>
                <div className="input-container">
                    <label htmlFor="quantity" className="input-label">
                        Quantity:
                    </label>
                    <input
                        type="number"
                        id="quantity"
                        value={formData.quantity}
                        onChange={(e) =>
                            setFormData({
                                ...formData,
                                quantity: e.target.value,
                            })
                        }
                        className="input-field"
                    />
                </div>
                <div className="input-container">
                    <label htmlFor="purchasePrice" className="input-label">
                        Purchase Price:
                    </label>
                    <input
                        type="number"
                        id="purchasePrice"
                        value={formData.purchasePrice}
                        onChange={(e) =>
                            setFormData({ ...formData, purchasePrice: e.target.value })
                        }
                        className="input-field"
                    />
                </div>
                <div className="input-container">
                    <label htmlFor="currentPrice" className="input-label">
                        Current Price:
                    </label>
                    <input
                        type="number"
                        id="currentPrice"
                        value={formData.currentPrice}
                        onChange={(e) =>
                            setFormData({ ...formData, currentPrice: e.target.value })
                        }
                        className="input-field"
                    />
                </div>
                <button
                    onClick={editingPortfolio ? handleSubmitEdit : handleAddPortfolio}
                    className="add-button"
                >
                    {editingPortfolio ? "Update Portfolio" : "Add Portfolio"}{" "}
                </button>
            </div>
            <br />
            <br />
            {loading ? (
                <p>Loading...</p>
            ) : (
                <>
                    <div className="filter-buttons">
                        <button onClick={handleShowProfit} className="delete-button">
                            Show Profit
                        </button>
                        <button onClick={handleShowLoss} className="delete-button">
                            Show Loss
                        </button>
                        <button
                            onClick={() => {
                                setShowProfit(false);
                                setShowLoss(false);
                            }}
                            className="delete-button"
                        >
                            Show All
                        </button>
                    </div>
                    <div className="portfolio-rows">
                        {portfolios.map(
                            (portfolio) =>
                                ((showProfit && getProfitLossClass(portfolio) === "profit") ||
                                    (showLoss && getProfitLossClass(portfolio) === "loss") ||
                                    (!showProfit && !showLoss)) && (
                                    <div
                                        key={portfolio._id}
                                        className={`portfolio ${getProfitLossClass(portfolio)}`}
                                    >
                                        <h3 className="portfolio-name">{portfolio.name}</h3>
                                        <table className="stock-table">
                                            <thead>
                                                <tr>
                                                    <th>Symbol</th>
                                                    <th>Quantity</th>
                                                    <th>Purchase Price</th>
                                                    <th>Current Price</th>
                                                    <th>Total Profit/Loss %</th>
                                                    <th>Actions</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                {portfolio.stocks.map((stock, index) => (
                                                    <tr key={index}>
                                                        <td>{stock.symbol}</td>
                                                        <td>{stock.quantity}</td>
                                                        <td>{stock.purchasePrice}</td>
                                                        <td>{stock.currentPrice}</td>
                                                        <td>{calculateProfitLossPercentage(portfolio)
                                                        }
                                                        %</td>
                                                        <td>
                                                            {editingPortfolio === portfolio._id &&
                                                                editingStock === stock._id ? (
                                                                <>
                                                                    <button
                                                                        onClick={() =>
                                                                            handleSaveEditedStock(
                                                                                portfolio._id,
                                                                                stock._id,
                                                                                formData
                                                                            )
                                                                        }
                                                                    >
                                                                        Save
                                                                    </button>
                                                                    <button onClick={() => 
                                                                    setEditingStock(null)}>
                                                                        Cancel
                                                                    </button>
                                                                </>
                                                            ) : (
                                                                <>
                                                                    <button
                                                                        onClick={() =>
                                                                            handleEditStock
                                                                           (portfolio._id, stock._id)
                                                                        }
                                                                        className="delete-button"
                                                                    >
                                                                        Edit
                                                                    </button>
                                                                    <button
                                                                        onClick={() =>
                                                                            handleDeleteStock(
                                                                                portfolio._id,
                                                                                stock._id
                                                                            )
                                                                        }
                                                                        className="delete-button"
                                                                    >
                                                                        Delete
                                                                    </button>
                                                                </>
                                                            )}
                                                        </td>
                                                    </tr>
                                                ))}
                                            </tbody>
                                        </table>
                                        <div>
                                            <strong>CURRENT VALUE</strong>: ₹
                                            {calculateCurrentValue(portfolio).toFixed(2)} 
                                            {calculateInvestedAmount(portfolio).toFixed(2)} Invested
                                        </div>
                                        <br />
                                        <button
                                            onClick={() => deletePortfolio(portfolio._id)}
                                            className="delete-button"
                                        >
                                            Delete
                                        </button>
                                        <button
                                            onClick={() => handleEditPortfolio(portfolio)}
                                            className="edit-button"
                                        >
                                            Edit
                                        </button>
                                    </div>
                                )
                        )}
                    </div>
                </>
            )}
        </div>
    );
};

export default App;

To start the frontend run the following commands:

npm start

Output:

678-ezgifcom-video-to-gif-converter

Article Tags :