skip to Main Content

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


  1. Chosen as BEST ANSWER

    Thank you to everyone who helped. I solved the issue as follows:

    (components/PostJobWidget.jsx)

      const postJob = async (values) => {
        const { title, description } = values;
    
        const response = await fetch(`http://localhost:3001/jobs`, {
          method: "POST",
          headers: {
            Authorization: `Bearer ${token}`,
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            clientId: _id,
            title: title,
            categoryId: category,
            description: description,
          }),
        });
    
        const jobs = await response.json();
        dispatch(setJobs({ jobs }));
        handleClose();
      };
    
    

  2. Try sending the object as JSON to the API.

    
    const response = await fetch(`http://localhost:3001/jobs`, {
          method: "POST",
          headers: { Authorization: `Bearer ${token}` },
          body: {clientId: _id, title: title, categoryId: category, description: description},
        });
    
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search