node.jsmongodbaxiosmernreact-fullstack

MERN stack axios issue status 404 with server running fine


I'm having trouble sending a data from a form to my database using axios in my MERN stack app. The server seems to work fine as I can make the POST request using Postman, however when I try to submit I get an axios error in the console with a 404 status code. Any help would be appreciated and, yes, the server is running on port 3000.

import { useState } from "react";
import axios from "axios";
import { CircularProgress } from "@mui/material";

const CreateSpecForm = () => {
  const [name, setName] = useState("");
  const [method, setMethod] = useState("");
  const [glass, setGlass] = useState("");
  const [ice, setIce] = useState("");
  const [garnish, setGarnish] = useState("");
  const [ingredients, setIngredients] = useState([
    { name: "", amount: "", measurement: "" },
  ]);
  const [image, setImage] = useState(null);
  const [description, setDescription] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleNameChange = (event) => {
    setName(event.target.value);
  };

  const handleMethodChange = (event) => {
    setMethod(event.target.value);
  };

  const handleGlassChange = (event) => {
    setGlass(event.target.value);
  };

  const handleIceChange = (event) => {
    setIce(event.target.value);
  };

  const handleGarnishChange = (event) => {
    setGarnish(event.target.value);
  };

  const handleDescriptionChange = (event) => {
    setDescription(event.target.value);
  };

  const handleImageChange = (event) => {
    if (event.target.files && event.target.files[0]) {
      setImage(event.target.files[0]);
    }
  };

  const handleIngredientNameChange = (event, index) => {
    const values = [...ingredients];
    values[index].name = event.target.value;
    setIngredients(values);
  };

  const handleIngredientAmountChange = (event, index) => {
    const values = [...ingredients];
    values[index].amount = event.target.value;
    setIngredients(values);
  };

  const handleIngredientMeasurementChange = (event, index) => {
    const values = [...ingredients];
    values[index].measurement = event.target.value;
    setIngredients(values);
  };

  const handleAddIngredient = () => {
    const values = [...ingredients];
    values.push({ name: "", amount: "", measurement: "" });
    setIngredients(values);
  };

  const handleSubmit = async (event) => {
    event.preventDefault();
    setIsSubmitting(true);
    try {
      const formData = new FormData();
      formData.append("name", name);
      formData.append("method", method);
      formData.append("glass", glass);
      formData.append("ice", ice);
      formData.append("garnish", garnish);
      formData.append("description", description);
      formData.append("image", image);
      formData.append("ingredients", JSON.stringify(ingredients));

      console.log(
        name,
        method,
        glass,
        garnish,
        ice,
        description,
        JSON.stringify(ingredients),
        image
      );
      const headers = {
        "Content-Type": "multipart/form-data",
      };
      const response = await axios.post("/api/recipes", formData, { headers });
      console.log(response.data);
    } catch (error) {
      console.error(error);
    } finally {
      setIsSubmitting(false);
    }
  };

  
  return (
    <div>
      <h1>Create Recipe</h1>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">Name:</label>
          <input
            type="text"
            id="name"
            value={name}
            onChange={handleNameChange}
          />
        </div>
        <div>
          <label htmlFor="method">Method:</label>
          <select id="method" value={method} onChange={handleMethodChange}>
            <option value="shaken">Shaken</option>
            <option value="stirred">Stirred</option>
            <option value="built">Built</option>
            <option value="thrown">Thrown</option>
            <option value="Blended">Blended</option>
          </select>
        </div>
        <div>
          <label htmlFor="glass">Glass:</label>
          <select id="glass" value={glass} onChange={handleGlassChange}>
            <option value="coupe">Coupe</option>
            <option value="rocks">Rocks</option>
            <option value="flute">Flute</option>
            <option value="wine">Wine</option>
            <option value="snifter">Snifter</option>
            <option value="collins">Collins</option>
            <option value="hurricane">Hurricane</option>
          </select>
        </div>
        <div>
          <label htmlFor="ice">Ice:</label>
          <select id="ice" value={ice} onChange={handleIceChange}>
            <option value="cubed">Cubed</option>
            <option value="crushed">Crushed</option>
            <option value="none">None</option>
            <option value="thrown">Thrown</option>
          </select>
        </div>
        <div>
          <label htmlFor="garnish">Garnish:</label>
          <input
            type="text"
            id="garnish"
            value={garnish}
            onChange={handleGarnishChange}
          />
        </div>
        <div>
          <label htmlFor="description">Description:</label>
          <textarea
            id="description"
            value={description}
            onChange={handleDescriptionChange}
          />
        </div>
        <div>
          <label htmlFor="image">Image:</label>
          <input type="file" id="image" onChange={handleImageChange} />
        </div>
        <div>
          <label>Ingredients:</label>
          {ingredients.map((ingredient, index) => (
            <div key={index}>
              <input
                type="text"
                placeholder="Name"
                value={ingredient.name}
                onChange={(event) => handleIngredientNameChange(event, index)}
              />
              <input
                type="text"
                placeholder="Amount"
                value={ingredient.amount}
                onChange={(event) => handleIngredientAmountChange(event, index)}
              />
              <select
                value={ingredient.measurement}
                placeholder="measurement"
                onChange={(event) =>
                  handleIngredientMeasurementChange(event, index)
                }
              >
                <option value="ml">ml</option>
                <option value="oz">oz</option>
                <option value="dash">dash</option>
                <option value="brsp">brsp</option>
                <option value="whole">whole</option>
              </select>
            </div>
          ))}
          <button type="button" onClick={handleAddIngredient}>
            Add Ingredient
          </button>
          <button onClick={() => setIngredients(ingredients.slice(0, -1))}>
            Remove Last Ingredient
          </button>
        </div>
        <button type="submit" disabled={isSubmitting}>
          {isSubmitting ? <CircularProgress /> : "Create Recipe"}
        </button>
      </form>
    </div>
  );
};

export default CreateSpecForm;

and here is my server side code

const express = require("express");
const multer = require("multer");
const AWS = require("aws-sdk");
const sharp = require("sharp");
const mongoose = require("mongoose");
require("dotenv").config();

const app = express();
app.use(express.json());

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true,
});
const db = mongoose.connection;
db.on("error", console.error.bind(console, "connection error:"));
db.once("open", function () {
  console.log("Connected to MongoDB");
});

// Define recipe schema and model
const recipeSchema = new mongoose.Schema({
  name: { type: String, required: true },
  description: { type: String, required: true },
  image: { type: String },
  ingredients: { type: [String] },
  glass: { type: String, required: true },
  ice: { type: String, required: true },
  garnish: { type: String, required: true },
  method: { type: String, required: true },
});

const Recipe = mongoose.model("Recipe", recipeSchema);

const s3 = new AWS.S3({
  accessKeyId: process.env.AWS_ACCESS_KEY_ID,
  secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
});

const upload = multer({ dest: "uploads/" });

app.post("/api/recipes", upload.single("image"), async (req, res) => {
  const { name, description, ingredients, method, ice, glass, garnish } =
    req.body;
  const imageFile = req.file;
  try {
    const compressedImage = await compressImage(imageFile);
    const imageUrl = await uploadImageToS3(compressedImage);
    const recipe = new Recipe({
      name,
      description,
      image: imageUrl,
      ingredients,
      method,
      ice,
      glass,
      garnish,
    });
    await recipe.save();
    console.log(recipe);
    res.status(201).json({ message: "Recipe created successfully" });
  } catch (error) {
    console.error(error);
    res.status(500).json({ message: "Error creating recipe" });
  }
});

const compressImage = async (imageFile) => {
  if (!imageFile) return null;
  const maxWidthOrHeight = 500;
  const maxFileSizeInMB = 5;
  const imageMaxSizeInBytes = maxFileSizeInMB * 5024 * 5024;
  const imageBuffer = await sharp(imageFile.path).toBuffer();
  const metadata = await sharp(imageBuffer).metadata();
  const resize =
    metadata.width > metadata.height
      ? { width: maxWidthOrHeight }
      : { height: maxWidthOrHeight };
  const compressedImageBuffer = await sharp(imageBuffer)
    .resize(resize)
    .jpeg({ quality: 80 })
    .toBuffer();
  if (compressedImageBuffer.byteLength > imageMaxSizeInBytes) {
    throw new Error("The compressed image is still too large.");
  }
  const compressedImage = {
    buffer: compressedImageBuffer,
    originalName: imageFile.originalname,
  };
  return compressedImage;
};

const uploadImageToS3 = async (image) => {
  const bucketName = process.env.AWS_S3_BUCKET_NAME;
  const objectKey = `recipe-images/${Date.now()}-${image.originalName}`;
  const params = {
    Bucket: bucketName,
    Key: objectKey,
    Body: image.buffer,
    ContentType: "image/jpeg",
    ACL: "private",
  };
  const s3Result = await s3.upload(params).promise();
  return s3Result.Location;
};

app.listen(3000, () => {
  console.log("Server started");
});

I've console.logged out all the variables being passed and they seem to match and I can make the POST request to MongoDB using Postman.


Solution

  • I think the problem can be that you're calling axios.post("/api/recipes",...) but the URL should include the full server address together with the server port, or maybe there's some code that specifies the server that here is missing?

    e.g. axios.post("<ip address or server name>:3000/api/recipes",...)