I am new to Web Development so please don’t judge me here:
So, I am building a website where when I am sending a POST request, I am receiving an 409 error. Below is the code:
//Server side:
(index.js)
...
app.use("/jobs", jobRoutes);
...
(models/Job.js)
import mongoose from "mongoose";
const jobSchema = new mongoose.Schema(
{
clientId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Client',
required: true
},
title: {
type: String,
required: true,
min: 5,
},
categoryId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Category',
required: true
},
description: {
type: String,
required: true,
min: 5,
},
},
{ timestamps: true }
);
const Job = mongoose.model("Job", jobSchema);
export default Job;
(controllers/jobs.js)
import Job from "../models/Job.js";
/* CREATE */
export const postJob = async (req, res) => {
try {
const { clientId, title, categoryId, description } = req.body;
const newJob = new Job({
clientId,
title,
categoryId,
description,
});
await newJob.save();
const jobs = await Job.find();
res.status(201).json(jobs);
} catch (err) {
res.status(409).json({ message: err.message });
}
};
/* READ */
export const getJobs = async (req, res) => {
try {
const jobs = await Job.find();
res.status(200).json(jobs);
} catch (err) {
res.status(404).json({ message: err.message });
}
};
(routes/jobs.js)
import express from "express";
import { postJob, getJobs } from "../controllers/jobs.js";
import { verifyToken } from "../middleware/auth.js";
const router = express.Router();
/* CREATE */
router.post("/", verifyToken, postJob);
/* READ */
router.get("/", verifyToken, getJobs);
export default router;
//Client Side:
(components/JobsWidget.jsx)
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { setJobs, setCategories } from "../state";
import JobWidget from "./JobWidget";
const JobsWidget = () => {
const dispatch = useDispatch();
const { users, jobs, categories, token, userMode } = useSelector(
(state) => state
);
const getJobs = async () => {
const response = await fetch("http://localhost:3001/jobs", {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
dispatch(setJobs({ jobs: data }));
};
const getCategories = async () => {
const response = await fetch("http://localhost:3001/categories", {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
dispatch(setCategories({ categories: data }));
};
useEffect(() => {
if (categories.length < 1) getCategories();
getJobs();
}, [userMode]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<>
{jobs.length > 0 ? (
jobs.map(
({ _id, clientId, title, categoryId, description, createdAt }) => {
const user = users.find((usr) => usr._id === clientId);
const category = categories.find((cat) => cat._id === categoryId);
let date = new Date(createdAt);
date = date.toLocaleDateString();
return (
<JobWidget
key={_id}
jobId={_id}
title={title}
category={category.title}
description={description}
clientId={user._id}
clientPicture={user.picturePath}
clientName={user.fullName}
date={date}
/>
);
}
)
) : (
<p style={{ textAlign: "center", marginTop: "2rem" }}>
No jobs to show
</p>
)}
</>
);
};
export default JobsWidget;
(components/PostJobWidget.jsx)
import {
Box,
Modal,
Typography,
FormControl,
InputLabel,
TextField,
Select,
MenuItem,
useTheme,
Button,
useMediaQuery,
} from "@mui/material";
import FlexBetween from "./FlexBetween";
import WidgetWrapper from "./WidgetWrapper";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { Formik } from "formik";
import * as yup from "yup";
import { setJobs } from "../state";
const postJobSchema = yup.object().shape({
title: yup.string().required("required"),
description: yup.string().required("required"),
});
const initialValuesPostJob = {
title: "",
description: "",
};
const PostJobWidget = () => {
const dispatch = useDispatch();
const [sort, setSort] = useState("");
const [filter, setFilter] = useState("");
const [open, setOpen] = useState(false);
const [category, setCategory] = useState("");
const { palette } = useTheme();
const { _id } = useSelector((state) => state.user);
const token = useSelector((state) => state.token);
const userMode = useSelector((state) => state.userMode);
const categories = useSelector((state) => state.categories);
const isNonMobileScreens = useMediaQuery("(min-width: 1000px)");
const handleOpen = () => setOpen(true);
const handleClose = () => {
setCategory("");
setOpen(false);
};
const handleSort = (event) => {
setSort(event.target.value);
};
const handleFilter = (event) => {
setFilter(event.target.value);
};
const handleCategory = (event) => {
setCategory(event.target.value);
};
const postJob = async (values) => {
const { title, description } = values;
const formData = new FormData();
formData.append("clientId", _id);
formData.append("title", title);
formData.append("categoryId", category);
formData.append("description", description);
const response = await fetch(`http://localhost:3001/jobs`, {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: formData,
});
const jobs = await response.json();
dispatch(setJobs({ jobs }));
handleClose();
};
useEffect(() => {}, [userMode]);
return (
<WidgetWrapper>
<FlexBetween gap={"1rem"}>
<FormControl sx={{ minWidth: 80, width: "20%" }}>
<InputLabel id="sort-label">Sort</InputLabel>
<Select
labelId="sort-label"
id="sort-select"
value={sort}
label="Sort"
onChange={handleSort}
>
<MenuItem value={10}>Date</MenuItem>
<MenuItem value={20}>Job Status</MenuItem>
</Select>
</FormControl>
<FormControl sx={{ minWidth: 100, width: "30%" }}>
<InputLabel id="filter-label">Filter</InputLabel>
<Select
labelId="filter-label"
id="filter-select"
value={filter}
label="Filter"
onChange={handleFilter}
>
{categories.map((category) => (
<MenuItem key={category._id} value={category._id}>
{category.title}
</MenuItem>
))}
</Select>
</FormControl>
{userMode === "client" && (
<>
<Button
onClick={handleOpen}
sx={{
color: palette.background.alt,
backgroundColor: palette.primary.main,
borderRadius: "2rem",
width: "6rem",
height: "3rem",
"&:hover": {
color: palette.primary.main,
},
}}
>
POST JOB
</Button>
<Modal open={open} onClose={handleClose}>
<Box
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: isNonMobileScreens ? "24rem" : "20rem",
bgcolor: "white",
border: "2px solid #000",
borderRadius: "1rem",
boxShadow: 24,
p: 4,
}}
>
<Typography
fontSize={"1rem"}
fontWeight={"600"}
marginBottom={"1rem"}
textAlign={"center"}
>
Post Job
</Typography>
<Formik
onSubmit={postJob}
initialValues={initialValuesPostJob}
validationSchema={postJobSchema}
>
{({
values,
errors,
touched,
handleBlur,
handleChange,
handleSubmit,
}) => (
<form onSubmit={handleSubmit}>
<Box
display="grid"
gap="20px"
gridTemplateColumns="repeat(4, minmax(0, 1fr))"
>
<TextField
label="Title"
onBlur={handleBlur}
onChange={handleChange}
value={values.title}
name="title"
error={
Boolean(touched.title) && Boolean(errors.title)
}
helperText={touched.title && errors.title}
sx={{ gridColumn: "span 4" }}
/>
<FormControl sx={{ gridColumn: "span 4" }}>
<InputLabel id="category-label">Category</InputLabel>
<Select
labelId="category-label"
id="category-select"
value={category}
label="Category"
onChange={handleCategory}
required
>
{categories.map((category) => (
<MenuItem key={category._id} value={category._id}>
{category.title}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
label="Description"
multiline
maxRows={4}
onBlur={handleBlur}
onChange={handleChange}
value={values.description}
name="description"
error={
Boolean(touched.description) &&
Boolean(errors.description)
}
helperText={touched.description && errors.description}
sx={{ gridColumn: "span 4" }}
/>
</Box>
<Box>
<Button
fullWidth
type="submit"
sx={{
m: "2rem 0",
p: "1rem",
backgroundColor: palette.primary.main,
color: palette.background.alt,
"&:hover": { color: palette.primary.main },
}}
variant="outlined"
>
POST NOW
</Button>
</Box>
</form>
)}
</Formik>
</Box>
</Modal>
</>
)}
</FlexBetween>
</WidgetWrapper>
);
};
export default PostJobWidget;
I don’t understand where I am making a mistake, can someone please give me any clue where or what I might be doing wrong? This is the error that I am getting:
Error: Job validation failed: clientId: Path `clientId` is required., title: Path `title` is required., categoryId: Path `categoryId` is required., description: Path `description` is required.
at ValidationError.inspect (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooseliberrorvalidation.js:50:26)
at formatValue (node:internal/util/inspect:806:19)
at inspect (node:internal/util/inspect:365:10)
at formatWithOptionsInternal (node:internal/util/inspect:2273:40)
at formatWithOptions (node:internal/util/inspect:2135:10)
at console.value (node:internal/console/constructor:340:14)
at console.log (node:internal/console/constructor:377:61)
at postJob (file:///D:/SIBAU/BSCS-VIII/Project-II/nasrp/server/controllers/jobs.js:18:13)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
errors: {
clientId: ValidatorError: Path `clientId` is required.
at validate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1346:13)
at SchemaType.doValidate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1330:7)
at D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibdocument.js:2905:18
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
properties: [Object],
kind: 'required',
path: 'clientId',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
},
title: ValidatorError: Path `title` is required.
at validate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1346:13)
at SchemaType.doValidate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1330:7)
at D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibdocument.js:2905:18
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
properties: [Object],
kind: 'required',
path: 'title',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
},
categoryId: ValidatorError: Path `categoryId` is required.
at validate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1346:13)
at SchemaType.doValidate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1330:7)
at D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibdocument.js:2905:18
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
properties: [Object],
kind: 'required',
path: 'categoryId',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
},
description: ValidatorError: Path `description` is required.
at validate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1346:13)
at SchemaType.doValidate (D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibschematype.js:1330:7)
at D:SIBAUBSCS-VIIIProject-IInasrpservernode_modulesmongooselibdocument.js:2905:18
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) {
properties: [Object],
kind: 'required',
path: 'description',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'Job validation failed'
}
2
Answers
Thank you to everyone who helped. I solved the issue as follows:
(components/PostJobWidget.jsx)
Try sending the object as JSON to the API.