skip to Main Content

Im trying to save imageUploads to a folder "/images". My issue is that req.files seems to be undefined for some reason. I will try to send the code I think is relevant. Feel free to ask any questions, and any help will be much appreciated! 🙂

//check if/else block
Output: "No files"

Backend code:

adminRouter.post(
  '/products/create',
  upload.array('images', 5),
  asyncHandler(async (req: Request, res: Response) => {
    if (!req.body) {
      res.status(400).json({ message: 'Product data not found' })
      return
    }

    const {
      name,
      rating,
      numReviews,
      reviews,
      slug,
      price,
      brand,
      category,
      countInStock,
      description,
    } = req.body

    const images = (req.files as Express.Multer.File[]).map(
      (file: Express.Multer.File) => file.path
    )

    if (req.files) {
      console.log('Files:', req.files)
    } else {
      console.log('No files')
    }

    const product = new ProductModel({
      name,
      slug,
      price,
      brand,
      category,
      countInStock,
      description,
      images,
      rating,
      numReviews,
      reviews,
    })

    try {
      const createdProduct = await product.save()
      res.status(201).json(createdProduct)
    } catch (error) {
      console.error('Error creating product:', error)
      res.status(500).json({ message: 'Failed to create product' })
    }
  })
)

Frontend hook:

export const useCreateProductMutation = () => {
  return useMutation({
    mutationFn: async (product: {
      name: string
      slug: string
      price: number
      description: string
      images: string[]
      brand: string
      category: string
      rating: number
      countInStock: number
      numReviews: number
      reviews: Review[]
    }) => {
      try {
        const response = await apiClient.post<{ message: string }>(
          `api/admin/products/create`,
          product
        )
        return response.data
      } catch (error) {
        throw new Error('Failed to create product')
      }
    },
  })
}

Frontend Form

export default function AdminCreateProductPage() {
  const [formData, setFormData] = useState({
    name: '',
    price: 0,
    images: [] as string[],
    brand: '',
    category: '',
    countInStock: 0,
    description: '',
    slug: '',
    rating: 0,
    numReviews: 0,
    reviews: [],
  })

  const { mutateAsync: createProduct } = useCreateProductMutation()

  const handleImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    if (e.target.files) {
      const newImages = Array.from(e.target.files).map((file) =>
        URL.createObjectURL(file)
      )
      setFormData({ ...formData, images: [...formData.images, ...newImages] })
    }
  }

  const submitHandler = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    createProduct(formData)
  }

  return (
    <div>
      <h1>Edit Product</h1>
...
...
      <Form onSubmit={submitHandler} encType="multipart/form-data">
          <Row className="w-100">
            <Col md={8}>
              <Form.Group controlId="images">
                <Form.Label>Images</Form.Label>
                <Form.Control
                  type="file"
                  name="images"
                  multiple
                  onChange={handleImageChange}
                />
              </Form.Group>
...
...

I have tried to find answers online, and communicating with chatGPT. But I can’t see to come any close.

2

Answers


  1. Multer only handle multipart/form-data and will ignore application/json

    You are probably just doing a post using a json object as body and not a FormData

    Instead of

            const response = await apiClient.post<{ message: string }>(
              `api/admin/products/create`,
              product
            )
    

    You will probably have to do something like

    const formData = new FormData();
    product.images.forEach(imgBlob => {
      // NOTE: you have to append the actual Blob/File and not an url created from URL.createObjectURL
      formData.append('images', imgBlob);
    });
    formData.append('name', product.name);
    ... and all other fields
    
    const response = await apiClient.post<{ message: string }>(
      `api/admin/products/create`,
      product
    );
    
    

    see https://developer.mozilla.org/en-US/docs/Web/API/FormData for more info

    Content-type is a header that help identify how the body of the request is formatted.
    Most of the time when you provide an object as body of your request, the client you are using will automatically fill Content-type with application/json in the request that is sent to your server

    Based on this header a middleware of your server will parse the body.

    For file upload, multipart/form-data is often used, and multer middleware only look for that header.

    Login or Signup to reply.
  2. Multer is ignoring application/json, have to pass into multipart/form-data .

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search