I’m working on a podcast app that has an e-commerce section for which I am following this tutorial: https://www.youtube.com/watch?v=CDtPMR5y0QU
I’ve been getting the following error from the network tab when I click the Place Order button:
message: "Order validation failed: orderItems.0.product: Cast to ObjectId failed for value "1" (type string) at path "product", orderItems.0._id: Cast to ObjectId failed for value "1" (type string) at path "_id", orderItems.0.image: Path
image
is required., orderItems.1.image: Pathimage
is required."
In the console, there is this error:
POST http://localhost:3000/api/orders 500 (Internal Server Error)
I think this has to do with the images not showing up from the backend…it just has the <img> icon for when a photo does not show up. Could it be related to the folder structure? I’ve followed the code in the video exactly. This error came after implementing Video 28.
folder structure:
I added two "images" folders because they weren’t showing up and I wanted to test them in the src and public folders. It didn’t work for either.
Here is my code:
BACKEND
This is the data that is fed into the MongoDB models
const productData = {
users: [
{
name: "example",
email: "[email protected]",
password: bcrypt.hashSync("123456"),
isAdmin: true,
},
{
name: "example",
email: "[email protected]",
password: bcrypt.hashSync("123456"),
isAdmin: false,
},
],
products: [
{
name: "LTBT Sweater",
slug: "ltbt-sweater",
price: 70,
image: "/images/1.png",
category: "Hoodies",
countInStock: 10,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
{
name: "LTBT Keychain",
slug: "ltbt-keychain",
price: 50,
image: "/images/1.png",
category: "Accessories",
countInStock: 9,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
{
name: "LTBT Sticker",
slug: "ltbt-sticker",
price: 20,
image: "/images/1.png",
category: "Stickers",
countInStock: 17,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
},
],
};
export default productData;
This is the db model:
import mongoose from "mongoose";
const orderSchema = new mongoose.Schema(
{
orderItems: [
{
slug: { type: String, required: true },
name: { type: String, required: true },
quantity: { type: Number, required: true },
image: { type: String, required: true },
price: { type: Number, required: true },
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
},
],
shippingAddress: {
fullName: { type: String, required: true },
address: { type: String, required: true },
city: { type: String, required: true },
postalCode: { type: String, required: true },
country: { type: String, required: true },
},
paymentMethod: { type: String, required: true },
paymentResult: {
id: String,
status: String,
update_time: String,
email_address: String,
},
itemsPrice: { type: Number, required: true },
shippingPrice: { type: Number, required: true },
taxPrice: { type: Number, required: true },
totalPrice: { type: Number, required: true },
user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
isPaid: { type: Boolean, default: false },
paidAt: { type: Date },
isDelivered: { type: Boolean, default: false },
deliveredAt: { type: Date },
},
{
timestamps: true,
}
);
const Order = mongoose.model("Order", orderSchema);
export default Order;
This is the orderRoutes file
import express from "express";
import Order from "../models/orderModel.js";
import expressAsyncHandler from "express-async-handler";
import { isAuth } from "../utils.js";
const orderRouter = express.Router();
orderRouter.post(
"/",
isAuth,
expressAsyncHandler(async (req, res) => {
const newOrder = new Order({
orderItems: req.body.orderItems.map((x) => ({ ...x, product: x._id })),
shippingAddress: req.body.shippingAddress,
paymentMethod: req.body.paymentMethod,
itemsPrice: req.body.itemsPrice,
shippingPrice: req.body.shippingPrice,
taxPrice: req.body.taxPrice,
totalPrice: req.body.totalPrice,
user: req.user._id,
});
const order = await newOrder.save();
res.status(201).send({ message: "New Order Created", order });
})
);
export default orderRouter;
This is the server.js file
import express from "express";
import cors from "cors";
import mongoose from "mongoose";
import dotenv from "dotenv";
import seedRouter from "./routes/seedRoutes.js";
import productRouter from "./routes/productRoutes.js";
import userRouter from "./routes/userRoutes.js";
import orderRouter from "./routes/orderRoutes.js";
dotenv.config();
mongoose
.connect(process.env.MONGODB_URI)
.then(() => {
console.log("Connected to DB");
})
.catch((err) => {
console.log(err.message);
});
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use("/api/seed", seedRouter);
app.use("/api/orders", orderRouter);
app.use("/api/store", productRouter);
app.use("/api/users", userRouter);
app.use((err, req, res, next) => {
res.status(500).send({ message: err.message });
});
const port = process.env.PORT || 5000;
app.listen(port, () => {
console.log(`SERVER RUNNING ON PORT ${port}`);
});
FRONTEND
Here is the page that has the button (Place Order button)
import CheckoutSteps from "../components/CheckoutSteps";
import { Helmet } from "react-helmet-async";
import Row from "react-bootstrap/Row";
import Col from "react-bootstrap/Col";
import Card from "react-bootstrap/Card";
import Button from "react-bootstrap/Button";
import ListGroup from "react-bootstrap/ListGroup";
import { Link, useNavigate } from "react-router-dom";
import { useContext, useEffect, useReducer } from "react";
import { Store } from "../Store";
import { toast } from "react-toastify";
import { getError } from "../utils";
import axios from "axios";
import LoadingBox from "../components/LoadingBox";
//reducer for creating an order
const reducer = (state, action) => {
switch (action.type) {
case "CREATE_REQUEST":
return { ...state, loading: true };
case "CREATE_SUCCESS":
return { ...state, loading: false };
case "CREATE_FAIL":
return { ...state, loading: false };
default:
return state;
}
};
export default function PlaceOrder() {
const navigate = useNavigate();
const { state, dispatch: ctxDispatch } = useContext(Store);
const { cart, userInfo } = state;
const [{ loading }, dispatch] = useReducer(reducer, {
loading: false,
});
//round to 2 decimal places
const round = (num) => Math.round(num * 100 + Number.EPSILON) / 100;
//calculate items price
cart.itemsPrice = round(
cart.cartItems.reduce((a, c) => a + c.quantity * c.price, 0)
);
//if total price is more than 100, make shipping price 15 otherwise it is 10
cart.shippingPrice = cart.itemsPrice > 100 ? round(10) : round(15);
//tax is 13%
cart.taxPrice = round(0.13 * cart.itemsPrice);
//total price
cart.totalPrice = cart.itemsPrice + cart.shippingPrice + cart.taxPrice;
const placeOrderHandler = async () => {
try {
dispatch({ type: "CREATE_REQUEST" });
const { data } = await axios.post(
"/api/orders",
{
orderItems: cart.cartItems,
shippingAddress: cart.shippingAddress,
paymentMethod: cart.paymentMethod,
itemsPrice: cart.itemsPrice,
shippingPrice: cart.shippingPrice,
taxPrice: cart.taxPrice,
totalPrice: cart.totalPrice,
},
{
headers: {
authorization: `Bearer ${userInfo.token}`,
},
}
);
ctxDispatch({ type: "CART_CLEAR" });
dispatch({ type: "CREATE_SUCCESS" });
localStorage.removeItem("cartItems");
navigate(`/order/${data.order._id}`);
} catch (err) {
dispatch({ type: "CREATE_FAIL" });
toast.error(getError(err));
}
};
useEffect(() => {
if (!cart.paymentMethod) {
navigate("/payment");
}
}, [cart, navigate]);
return (
<div>
<CheckoutSteps step1 step2 step3 step4></CheckoutSteps>
<Helmet>
<title>LTBT | Preview Order</title>
</Helmet>
<h1 className="my-3">Preview Order</h1>
<Row>
<Col md={8}>
<Card className="mb-3">
<Card.Body>
<Card.Title>Shipping</Card.Title>
<Card.Text>
<strong>Name:</strong> {cart.shippingAddress.fullName} <br />
<strong>Address: </strong> {cart.shippingAddress.address},
{cart.shippingAddress.city}, {cart.shippingAddress.postalCode},
{cart.shippingAddress.country}
</Card.Text>
<Link to="/shipping">Edit</Link>
</Card.Body>
</Card>
<Card className="mb-3">
<Card.Body>
<Card.Title>Payment</Card.Title>
<Card.Text>
<strong>Method:</strong> {cart.paymentMethod}
</Card.Text>
<Link to="/payment">Edit</Link>
</Card.Body>
</Card>
<Card className="mb-3">
<Card.Body>
<Card.Title>Items</Card.Title>
<ListGroup variant="flush">
{cart.cartItems.map((item) => (
<ListGroup.Item key={item._id}>
<Row className="align-items-center">
<Col md={6}>
<img
src={item.image}
alt={item.name}
className="img-fluid rounded img-thumbnail"
></img>{" "}
<Link to={`/store/${item.slug}`}>{item.name}</Link>
</Col>
<Col md={3}>
<span>{item.quantity}</span>
</Col>
<Col md={3}>$ {item.price}</Col>
</Row>
</ListGroup.Item>
))}
</ListGroup>
<Link to="/cart">Edit</Link>
</Card.Body>
</Card>
</Col>
<Col md={4}>
<Card>
<Card.Body>
<Card.Title>Order Summary</Card.Title>
<ListGroup variant="flush">
<ListGroup.Item>
<Row>
<Col>Items</Col>
<Col>$ {cart.itemsPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Shipping</Col>
<Col>$ {cart.shippingPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>Tax</Col>
<Col>$ {cart.taxPrice.toFixed(2)}</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<Row>
<Col>
<strong> Order Total</strong>
</Col>
<Col>
<strong>$ {cart.totalPrice.toFixed(2)}</strong>
</Col>
</Row>
</ListGroup.Item>
<ListGroup.Item>
<div className="d-grid">
<Button
type="button"
onClick={placeOrderHandler}
disabled={cart.cartItems.length === 0}
>
Place Order
</Button>
</div>
{loading && <LoadingBox></LoadingBox>}
</ListGroup.Item>
</ListGroup>
</Card.Body>
</Card>
</Col>
</Row>
</div>
);
}
Store.js file
import { createContext, useReducer } from "react";
export const Store = createContext();
//define initial state in cart based on local storage
const initialState = {
//check if user exists
userInfo: localStorage.getItem("userInfo")
? JSON.parse(localStorage.getItem("userInfo"))
: null,
cart: {
//get shipping address, payment method and cart items based on user
shippingAddress: localStorage.getItem("shippingAddress")
? JSON.parse(localStorage.getItem("shippingAddress"))
: {},
paymentMethod: localStorage.getItem("paymentMethod")
? localStorage.getItem("paymentMethod")
: "",
cartItems: localStorage.getItem("cartItems")
? JSON.parse(localStorage.getItem("cartItems"))
: [],
},
};
//update state in cart
//instead of creating duplicate items of the same product, we increase the amount of the one product if button is pressed more than once
function reducer(state, action) {
switch (action.type) {
//add items to cart
case "CART_ADD_ITEM":
const newItem = action.payload;
const existItem = state.cart.cartItems.find(
(item) => item._id === newItem._id
);
const cartItems = existItem
? state.cart.cartItems.map((item) =>
item._id === existItem._id ? newItem : item
)
: [...state.cart.cartItems, newItem];
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
//second case: remove item from cart
case "CART_REMOVE_ITEM": {
const cartItems = state.cart.cartItems.filter(
(item) => item._id !== action.payload._id
);
localStorage.setItem("cartItems", JSON.stringify(cartItems));
return { ...state, cart: { ...state.cart, cartItems } };
}
//clear cart
case "CART_CLEAR":
return { ...state, cart: { ...state.cart, cartItems: [] } };
//update user info based on data from the backend
case "USER_SIGNIN":
return { ...state, userInfo: action.payload };
//case where user is signed out
case "USER_SIGNOUT":
return {
...state,
userInfo: null,
cart: {
cartItems: [],
shippingAddress: {},
paymentMethod: "",
},
};
//update shipping address with data from payload
case "SAVE_SHIPPING_ADDRESS":
return {
...state,
cart: { ...state.cart, shippingAddress: action.payload },
};
//save payment method
case "SAVE_PAYMENT_METHOD":
return {
...state,
cart: { ...state.cart, paymentMethod: action.payload },
};
default:
return state;
}
}
export function StoreProvider(props) {
const [state, dispatch] = useReducer(reducer, initialState);
const value = { state, dispatch: dispatch };
return <Store.Provider value={value}>{props.children}</Store.Provider>;
}
Edit: It works in Firefox, but not Chrome
2
Answers
I recommend to you set in the "orderRouter.post(‘/’…)"
try { //commands } catch(e){ console.log(e); throw e }
Could be that you are running the server on different port that your Front End?
In that case you are consuming from localhost:3000/api/… instead of localhost:5000/api/… I recommend that u test your apis with postman first.