0

The app works fine in development, and all data is returned as expected through the frontend.

However, when I run it in production I get an error of 304 and html is returned. This persists when I disable cache.

If I query this route on my server the expected json data is returned. https://two2062023.onrender.com/api/hotels/countByCity?cities=berlin,madrid,london

However, it is not returned directly within the app on the frontend. I noticed in my network the path seems to be missing the /api.

I have been told this may be a catch-all on all paths to ensure client side routing works.

Can anyone offer further advice?

enter image description here

enter image description here

Does anyone know what is happening?

Folder structure

folder structure

**mongodb settings **

mongo settings

**client index.js **

import express from "express";
import dotenv from "dotenv";
import mongoose from "mongoose";
import authRoute from "./routes/auth.js";
import usersRoute from "./routes/users.js";
import hotelsRoute from "./routes/hotels.js";
import roomsRoute from "./routes/rooms.js";
import cookieParser from "cookie-parser";
import cors from "cors";
import path from 'path';


const app = express();
dotenv.config();

const connect = async () => {
    try {
        await mongoose.connect(process.env.MONGO);
        console.log("Connected to mongoDB.")
    } catch (error) {
        throw error;
    }
};

mongoose.connection.on("disconnected", () => {
    console.log("mongoDB disconnected!");
});

//middlewares
app.use(cookieParser())
app.use(cors())
app.disable('etag')

// we use this middlware because by defauly you cannot send json unless you use this middleware

app.use(express.json());

app.use("/api/auth", authRoute);
app.use("/api/users", usersRoute);
app.use("/api/hotels", hotelsRoute);
app.use("/api/rooms", roomsRoute);

app.use((err, req, res, next) => {
    const errorStatus = err.status || 500;
    const errorMessage = err.message || "Something went wrong!";
    return res.status(errorStatus).json({
        success: false,
        status: errorStatus,
        message: errorMessage,
        stack: err.stack,
    });
});



const __dirname = path.resolve();

if (process.env.NODE_ENV == 'production') {

    //set static folder


    app.use(express.static(path.join(__dirname, '/client/build')))

    //any route that is not one of the listed api routes above, will be redircted to index.html

    app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html')))
    console.log('this has just run!!!')

} else {

    app.get('/', (req, res) => {
        res.send('Api is running')
    })
}



const port = process.env.PORT || 8800;

app.listen(port, () => {
    connect();
    console.log(` server running in ${process.env.NODE_ENV}. Connected to server at port ${port}.`);
    console.log(` this is your ${path.resolve()} `)
    console.log(process.env.NODE_ENV)


});

**client FeaturedProperties.jsx **

import useFetch from "../../hooks/useFetch";
import "./featuredProperties.css";

const FeaturedProperties = () => {
        const {
            data,
            loading,
            error
        } = useFetch("/hotels?featured=true&limit=4");
        console.log(data)

        return ( <
            div className = "fp" > {
                loading ? (
                    "Loading"
                ) : ( <
                        > {
                            data && Array.isArray(data).data.map((item) => ( <
                                    div className = "fpItem"
                                    key = {
                                        item._id && item._id
                                    } >
                                    <
                                    img src = {
                                        item.photos && item.photos[0]
                                    }
                                    alt = ""
                                    className = "fpImg" /
                                    >
                                    <
                                    span className = "fpName" > {
                                        item.name && item.name
                                    } < /span> <
                                    span className = "fpCity" > {
                                        item.city && item.city
                                    } < /span> <
                                    span className = "fpPrice" > Starting from $ {
                                        item.cheapestPrice && item.cheapestPrice
                                    } < /span> {
                                        item.rating && < div className = "fpRating" >
                                            <
                                            button > {
                                                item.rating && item.rating
                                            } < /button> <
                                            span > Excellent < /span> <
                                            /div>} <
                                            /div>
                                    ))
                            } <
                            />
                        )
                    } <
                    /div>
            );
        };

        export default FeaturedProperties;

**client useFetch hook **

import {
    useEffect,
    useState
} from "react";
import axios from "axios";

const useFetch = (url) => {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(false);
    const [error, setError] = useState(false);

    useEffect(() => {
        const fetchData = async () => {
            setLoading(true);
            try {
                const res = await axios.get(url);
                setData(res.data);
            } catch (err) {
                setError(err);
            }
            setLoading(false);
        };
        fetchData();
    }, [url]);

    const reFetch = async () => {
        setLoading(true);
        try {
            const res = await axios.get(url);
            setData(res.data);
        } catch (err) {
            setError(err);
        }
        setLoading(false);
    };

    return {
        data,
        loading,
        error,
        reFetch
    };
};

export default useFetch;

** server hotel routes **

import express from "express";
import {
    countByCity,
    countByType,
    createHotel,
    deleteHotel,
    getHotel,
    getHotelRooms,
    getHotels,
    updateHotel,
} from "../controllers/hotel.js";
import Hotel from "../models/Hotel.js";
import {
    verifyAdmin
} from "../utils/verifyToken.js"
const router = express.Router();

//CREATE
router.post("/", verifyAdmin, createHotel);

//UPDATE
router.put("/:id", verifyAdmin, updateHotel);
//DELETE
router.delete("/:id", verifyAdmin, deleteHotel);
//GET

router.get("/find/:id", getHotel);
//GET ALL

router.get("/", getHotels);
router.get("/countByCity", countByCity);
router.get("/countByType", countByType);
router.get("/room/:id", getHotelRooms);

export default router;

** server hotel controllers **

import Hotel from "../models/Hotel.js";
import Room from "../models/Room.js";


// this is async because we need to connect to mongo database first
//we handle our errors using express middlewares instead of
//catch(err) { res.status(500).json(err)}
//req can also be a query but here we are taking the request body


export const createHotel = async (req, res, next) => {
    const newHotel = new Hotel(req.body);

    try {
        const savedHotel = await newHotel.save();
        res.status(200).json(savedHotel);
    } catch (err) {
        next(err);
    }
};

// new true options means updateHotel returns the updated data & not the old data

export const updateHotel = async (req, res, next) => {
    try {
        const updatedHotel = await Hotel.findByIdAndUpdate(
            req.params.id, {
                $set: req.body
            }, {
                new: true
            }
        );
        res.status(200).json(updatedHotel);
    } catch (err) {
        next(err);
    }
};
export const deleteHotel = async (req, res, next) => {
    try {
        await Hotel.findByIdAndDelete(req.params.id);
        res.status(200).json("Hotel has been deleted.");
    } catch (err) {
        next(err);
    }
};
export const getHotel = async (req, res, next) => {
    try {
        const hotel = await Hotel.findById(req.params.id);
        res.status(200).json(hotel);
    } catch (err) {
        next(err);
    }
};



///within the Hotel.find() 
//if we use Hotel.find(req.query) it will return hotels that meet that query
//an example of a query is /hotels?featured= true
//we can take .limit on by Hotel.find(req.query).limit(req.query.limit)
//an example of a query is /hotels?featured= true?limit=3

//db.collection.find(query, projection, options)
// query selector $lt Matches values that are less than a specified value.
// $gt Matches values that are greater than a specified value.

export const getHotels = async (req, res, next) => {
    const {
        min,
        max,
        ...others
    } = req.query;
    try {
        const hotels = await Hotel.find({
            ...others,
            cheapestPrice: {
                $gt: min | 1,
                $lt: max || 999
            },
        }).limit(req.query.limit);
        res.status(200).json(hotels);
    } catch (err) {
        next(err);
    }
};


//it is req.query because the route is a query string i.e. /countbyCities?
//The cities query is split into an array using the split function

//the function returns the individual elements of the array 
//& then uses the MONGODB count function & asks for a count of each individual city


export const countByCity = async (req, res, next) => {
    const cities = req.query.cities.split(",");
    try {
        const list = await Promise.all(
            cities.map((city) => {
                return Hotel.countDocuments({
                    city: city
                });
            })
        );
        res.status(200).json(list);
    } catch (err) {
        next(err);
    }
};




//as we have a set number of types, we do not need to use req.params or req.query

export const countByType = async (req, res, next) => {
    try {
        const hotelCount = await Hotel.countDocuments({
            type: "hotel"
        });
        const apartmentCount = await Hotel.countDocuments({
            type: "apartment"
        });
        const resortCount = await Hotel.countDocuments({
            type: "resort"
        });
        const villaCount = await Hotel.countDocuments({
            type: "villa"
        });
        const cabinCount = await Hotel.countDocuments({
            type: "cabin"
        });

        res.status(200).json([{
                type: "hotel",
                count: hotelCount
            },
            {
                type: "apartments",
                count: apartmentCount
            },
            {
                type: "resorts",
                count: resortCount
            },
            {
                type: "villas",
                count: villaCount
            },
            {
                type: "cabins",
                count: cabinCount
            },
        ]);
    } catch (err) {
        next(err);
    }
};

//promise.all takes an iterable of promises and returns as a single promise.
//here we are returning all rooms that match the id. Each Rooom.findbyid is an individual promise


export const getHotelRooms = async (req, res, next) => {
    try {
        const hotel = await Hotel.findById(req.params.id);
        const list = await Promise.all(
            hotel.rooms.map((room) => {
                return Room.findById(room);
            })
        );
        res.status(200).json(list)
    } catch (err) {
        next(err);
    }
};

** server hotel model **

import mongoose from "mongoose";
const HotelSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
    },
    type: {
        type: String,
        required: true,
    },
    city: {
        type: String,
        required: true,
    },
    address: {
        type: String,
        required: true,
    },
    distance: {
        type: String,
        required: true,
    },
    photos: {
        type: [String],
    },
    title: {
        type: String,
        required: true,
    },
    desc: {
        type: String,
        required: true,
    },
    rating: {
        type: Number,
        min: 0,
        max: 5,
    },

    // it is an array because it is going to include room ids
    rooms: {
        type: [String],
    },
    cheapestPrice: {
        type: Number,
        required: true,
    },
    featured: {
        type: Boolean,
        default: false,
    },
});

export default mongoose.model("Hotel", HotelSchema)
stmb
  • 11
  • 2
  • Just try to clear the cache, and hard reload the browser. And could please post the backend code as well? – nuser137 Jun 22 '23 at 09:19
  • Thank you for your help. I have tried as you suggested and the problem persists. I have now posted some backend code. – stmb Jun 22 '23 at 10:16
  • Does this answer your question? https://stackoverflow.com/questions/18811286/nodejs-express-cache-and-304-status-code – nuser137 Jun 22 '23 at 11:12
  • Thank you. I have put it in my middleware as per below but it still is responding the same. Should I position it elsewhere in my code. ```app.use(cookieParser()) app.use(cors()) app.disable('etag');``` – stmb Jun 22 '23 at 12:10
  • After deploying still you are getting the same issue? Im sorry, I was bit stuck in my another ques :( that's why I couldn't reply to you. – nuser137 Jun 22 '23 at 14:45
  • Yes, sadly I am. I feel like it is probably a cache issue but I don't know how to fix it. – stmb Jun 22 '23 at 16:06
  • Can you please me right now to my [question](https://stackoverflow.com/questions/76533929/how-to-select-multiple-hearts-as-per-user-will-select-in-react-js) ? Actually I am stuck in my question that's why I couldn't help you here :( – nuser137 Jun 22 '23 at 17:01
  • I think someone has posted an answer. Did that work? – stmb Jun 22 '23 at 17:25
  • I asked them to provide a demo so I could properly understand because the codesandbox is using by another user and it is MicroVM so I couldn't so any edit using of that answer if someone left only then I can do. – nuser137 Jun 22 '23 at 17:27
  • Hi, I get to work on that posted answer. Thank you so much for helping me. I think I could help you now! – nuser137 Jun 22 '23 at 17:46

1 Answers1

0

The problem was with my package.json file in the frontend. It routed to localhost:8800/api during development so it all worked.

In production, api was no longer included in the axios requests from the frontend. I adjusted the axios routes to include api/hotels/etc.... and they now work fine.

Sagar Darekar
  • 982
  • 9
  • 14
stmb
  • 11
  • 2