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?
Does anyone know what is happening?
Folder structure
**mongodb 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)