-1

First of all I tried to look for a solution to this specific issue in stackOverflow but i didn't find an answer for it.

The suggested link (POST Request with Fetch API?) doesn't answer my question. So please reopen the question.

The problem is with fetching an API endpoint from an express server. "GET" requests succeed while "POST" requests fail from the frontend (React). I should mention that there is no problem at all when i try fetching the API with POSTMAN, everything works as it should be.

Here is the code snippet :

BACKEND (Express JS) :

1. app.js

require('dotenv').config();

const express = require("express");
const mongoose = require("mongoose");
const { urlencoded } = require("express");
const WorkoutRoutes = require("./Routes/workouts");
const cors = require('cors');

// Start the App
const app = express();
app.use(cors());

// Must be specified before the Routes
app.use(express.json());

// The routes
app.use("/api/workouts", WorkoutRoutes);

// For Form Data
app.use(urlencoded({extended : true}));

// Connect to MongoDB using Mongoose
mongoose.set("strictQuery", false);
mongoose.connect(process.env.DB_URI, {useNewUrlParser: "true",useUnifiedTopology: "true"})
.then(()=>{
app.listen(process.env.PORT, ()=>{
    console.log("app started on Port" + process.env.PORT);
})
});

2. Workouts Routes :

const express = require("express");
const mongoose = require("mongoose");
const router = express.Router();
const workoutscontrollers = require("../Controllers/workoutsControllers");

// Get All Workouts
router.get("/", workoutscontrollers.getAllWorkouts);

// Get a single Workout
router.get("/:id", workoutscontrollers.deleteAsingleWorkout);

// Post a new Workout
router.post("/", workoutscontrollers.postAnewWorkout);

// Delete a Workouts
router.delete("/:id", workoutscontrollers.deleteAsingleWorkout);

// Update a Workout
router.patch("/:id", workoutscontrollers.updateAworkout);

module.exports = router;

3. Workout Controller :

const express = require("express");
const mongoose = require("mongoose");
const router = express.Router();
const Workout = require("../models/workoutModel");

const getAllWorkouts = async (req, res) => {
  // find() with or without {}
  const allworkouts = await Workout.find();
  res.status(200).json(allworkouts);
};

const getAsingleWorkout = async (req, res) => {
  const { id } = req.params;

  if (!mongoose.Types.ObjectId.isValid(id)) {
    return res.status(404).json({ error: "There is no such Workout" });
  }

  const workout = await Workout.findById(id);

  res.status(200).json(workout);
};

const postAnewWorkout = async (req, res) => {
    const { title, reps, load } = req.body;
  
    try {
      const workout = await Workout.create({ title, reps, load });
      res.status(200).json(workout);
    } catch (error) {
      res.status(404).json({ error: error.message });
    }
  }

  const deleteAsingleWorkout = async (req, res) => {
    const { id } = req.params;
  
    if (!mongoose.Types.ObjectId.isValid(id)) {
      return res.status(404).json({ error: "There is no such Workout" });
    }
  
    const workout = await Workout.findOneAndDelete({ _id: id });
  
    res.status(200).json(workout);
  }

  const updateAworkout = async (req, res) => {
    const { id } = req.params;

    if (!mongoose.Types.ObjectId.isValid(id)) {
      return res.status(404).json({ error: "There is no such Workout" });
    }
  
    const workout = await Workout.findOneAndUpdate({ _id: id }, {
        ...req.body
    });
  
    res.status(200).json(workout);
}

module.exports = {
    getAllWorkouts,
    getAsingleWorkout,
    postAnewWorkout,
    deleteAsingleWorkout,
    updateAworkout
}

4. Workout Model :

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const workoutsSchema = new Schema({
    title : {
        type: String,
        required:true
    },

    reps: {
        type: Number,
        required:true
    },
    load : {
        type: Number,
        required:true
    }

}, {timestamps : true});


module.exports = mongoose.model("Workout", workoutsSchema);

Package.json (Backend)

{
  "name": "backend",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "nodemon app.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^1.2.1",
    "cors": "^2.8.5",
    "dotenv": "^16.0.3",
    "express": "^4.18.2",
    "g": "^2.0.1",
    "mongoose": "^6.8.1",
    "node": "^19.3.0",
    "nodemon": "^2.0.20"
  },
  "description": ""
}

Now the Frontend

1. App.js (React) :

import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Navbar />
        <Routes>
          <Route
          path='/'
          element={<Home/>}
          />
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

2. Home.js

import { useState, useEffect } from "react";
import AddWorkout from "../components/AddWorkout";

const Home = () => {
  const [workouts, setWorkouts] = useState(null);

  useEffect(() => {
    const fetchWorkouts = async () => {
      const res = await fetch("http://localhost:5000/api/workouts");
      const json = await res.json();

      if (res.ok) {
        setWorkouts(json);
      }
    };
    fetchWorkouts();
  }, []);

  return (
    <div className="home">
      <div className="workouts">
        {workouts &&
          workouts.map((workout) => (
            <div key={workout._id}>
              <p>{workout.title}</p>
              <p>{workout.reps}</p>
              <p>{workout.load}</p>
            </div>
          ))}
      </div>
      <div className="addForm">
        <AddWorkout />
      </div>
    </div>
  );
};

export default Home;

3. AddWorkout.js

import { useState } from 'react';

const AddWorkout = () => {

  const [workout, setWorkout] = useState(null);
  const [text, setText] = useState('');
  const [reps, setReps] = useState(0);
  const [load, setLoad] = useState(0);

       

        const handleText = (e) => {
         const text = e.target.value;
         setText(text);
        };

        const handleReps = (e) => {
          const reps = e.target.value;
          setReps(reps);
  
        };

        const handleLoad = (e) => {
          const load = e.target.value;
          setLoad(load);

        };
       
        const handleForm = (e) => {
          e.preventDefault();
          const workout = {text, reps, load};
          setWorkout(workout);

          fetch("http://localhost:5000/api/workouts", {
            method:'POST',
            body:JSON.stringify(workout),
            headers:{'content-Type': 'application/json'}
          }).then(data => console.log(data))
            .catch(err => console.log(err));

        };

  return (
  
    <form onSubmit={handleForm}>
      <label>Title</label>
      <input type="text" value={text} onChange={handleText} />
      <label>Reps</label>
      <input type="text" value={reps} onChange={handleReps} />
      <label>Load</label>
      <input type="text" value={load} onChange={handleLoad} />
      <input type="submit"/>

    </form>

  );
};

export default AddWorkout;

Here are some screenshots for more details :

GET REQUEST WORKS

and

More Details

So what am i doing wrong?

Thanks

1heone
  • 17
  • 6
  • The [request options](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) key you're looking for is `headers`, not `header`. Also, keeping the `workout` state seems unnecessary – Phil Dec 29 '22 at 09:50
  • hello Phil, i've made the change to "headers" but the problem still there, POST requests fail as usual – 1heone Dec 29 '22 at 10:25
  • 1
    The 404 is coming from your controller. Your schema property is `title`, not `text`. You should have just checked the response body – Phil Dec 29 '22 at 13:51
  • hello Phil! thank you for your reply. it was just that!! now it works properly. i guess i must get familiar with such typo errors as i prefer to write the code from begin to end and not just copy paste. Thank you again – 1heone Dec 29 '22 at 14:52
  • Use Typescript and you won't have these sorts of issues – Phil Dec 29 '22 at 20:26
  • Hello @Phil, thanks for the advice. I plan to do it actually. Happy new year! – 1heone Dec 30 '22 at 09:05

1 Answers1

0

The issue is that you are not sending the correct headers with the POST request. When making a POST request, you need to set the headers to indicate the content type. In this case, it would be 'application/json'.

To do this, you can add the following line of code:

headers: { 'Content-Type': 'application/json' }

The final code should look something like this:

fetch("http://localhost:5000/api/workouts", {
    method:'POST',
    body:JSON.stringify(workout),
    headers: { 'Content-Type': 'application/json' }
  }).then(data => console.log(data))
    .catch(err => console.log(err));
Branch
  • 88
  • 7
  • hello @Branch, as you can see, in my code snippet i've done just that, maybe you just saw the GET request. – 1heone Dec 29 '22 at 10:26
  • hello @1heone I changed "header" to "headers". Please try this. – Branch Dec 30 '22 at 11:02
  • hello @Branch, Phil already figured it out. (see the comments on my original post). it was weird that a typo error on a property name lead to 404 Endpoint not found for a POST request.. weird Express i guess. – 1heone Dec 30 '22 at 17:22