Display an image from multer and a database of type uploads / files.jpg - node.js

I just configured Multer in Back with NodeJS and everything works perfectly. I can save an image in my database 'uploads/img.jpg' and this image is also displayed in a folder named "uploads" in Back.
Now I am trying to display images with ReactJS and the map method and I am having difficulty : I can't display an image due to "uploads/". This is my code :
const Users = () => {
const [users, setUsers] = useState([]);
const classes = useStyles();
useEffect(() => {
const getUserss = async () => {
const result = await axios.get("/users");
setUsers(result.data);
};
getusers();
}, []);
const tableHeader = columns.map((column) => (
<TableCell key={column.id} align={column.align} style={{ minWidth: column.minWidth }} >
{column.label}
</TableCell>
));
const mainContent = users
.map((user) => {
console.log(user);
return (
<TableRow hover role="checkbox" tabIndex={-1} key={customer.code}>
<TableCell align="center" className={classes.test}>
<Avatar className={classes.avatar} src= {?} /> {/* My problem is here */}
</TableCell>
<TableCell align="center">{user.name}</TableCell>
<TableCell align="center">{user.details}</TableCell>
</TableRow>
);
});
return (
<TableContainer className={classes.container}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>{tableHeader}</TableRow>
</TableHead>
<TableBody>{mainContent}</TableBody>
</Table>
</TableContainer>
);
};
export default Users;
and the result of my console.log(user) :
I have also configured two files in NodeJS with the MVC model but I don't know if this can help me for the map method. A fist one in controllers :
class Images {
static sendImg = (req, res) => {
const { rep, img } = req.params;
res.sendFile(`uploads/${img}`, { root: "./" });
};
}
module.exports = Images;
And the second one in routes:
const imgCtrl = require("../controllers/images");
const images = require("express").Router();
images.get("/:img", imgCtrl.sendImg);
module.exports = images;
I have also an index in routes with :
const images = require('./images');
module.exports = (app) => {
app.use('/uploads', images);
};

Try to add this:
app.use('/uploads', express.static(path.join(__dirname, 'relative_path_to_uploads_folder')));

I finally found the answer to my question. Here is the code:
src={`http://xxx/${user.path}`}

Related

How to upload files from a backend (Heroku) to frontend in (Netlify) hosted on github

I've developed an app that's uploaded to Github and I'm using Heroku to host the (Backend folder) from Github using (automatic deployment) and also using Netlify to host the (Frontend folder) and it's working great in my local computer, but when I try to upload files from my form in frontend it sends a request to the backend and the backend it self saves the file to /uploads folder that's located in frontend directory.
My file structure is like this:
[Server]
- controllers
- - food.js
[Client]
- public
-- uploads
- src
-- pages
--- dashboard
---- food
----- AddFood.js
it's working great on localhost, and this is my code:
(client)
AddFood.js:
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import Axios from 'axios'
import useDocumentTitle from '../../../hooks/useDocumentTitle'
import Modal from '../../../components/Modal/Modal'
import { Success, Error, Loading } from '../../../components/Icons/Status'
import { createSlug } from '../../../functions/slug'
import goTo from '../../../functions/goTo'
const AddFood = () => {
useDocumentTitle('Add Food')
//Form States
const [foodName, setFoodName] = useState('')
const [foodPrice, setFoodPrice] = useState('')
const [foodDesc, setFoodDesc] = useState('')
const [foodFile, setFoodFile] = useState('')
const [preview, setPreview] = useState()
const [addFoodStatus, setAddFoodStatus] = useState()
const [addFoodMessage, setAddFoodMessage] = useState()
//Form errors messages
const ImgErr = document.querySelector('[data-form-img-msg]')
const foodNameErr = document.querySelector('[data-form-name-msg]')
const priceErr = document.querySelector('[data-form-price-msg]')
const descErr = document.querySelector('[data-form-desc-msg]')
const formMsg = document.querySelector('[data-form-msg]')
const modalLoading = document.querySelector('#modal')
const BASE_URL =
process.env.NODE_ENV === 'development'
? process.env.REACT_APP_API_LOCAL_URL
: process.env.REACT_APP_API_URL
const updateFoodImg = e => {
const file = e.target.files[0]
if (file) {
const fileType = file.type.split('/')[0]
if (fileType === 'image') setFoodFile(file)
const fileSizeToMB = file.size / 1000000
const MAX_FILE_SIZE = 1 //mb
if (fileSizeToMB > MAX_FILE_SIZE) {
if (ImgErr)
ImgErr.textContent = `file size can't be more than ${MAX_FILE_SIZE} MB`
} else {
ImgErr.textContent = ''
}
}
}
useEffect(() => {
// if there's an image
if (foodFile) {
const reader = new FileReader()
reader.onloadend = () => setPreview(reader.result)
reader.readAsDataURL(foodFile)
} else {
setPreview(null)
}
}, [foodFile])
const handleAddFood = async e => {
e.preventDefault()
//using FormData to send constructed data
const formData = new FormData()
formData.append('foodName', foodName)
formData.append('foodPrice', foodPrice)
formData.append('foodDesc', foodDesc)
formData.append('foodImg', foodFile)
if (
ImgErr.textContent === '' &&
foodNameErr.textContent === '' &&
priceErr.textContent === '' &&
descErr.textContent === ''
) {
//show waiting modal
modalLoading.classList.remove('hidden')
try {
const response = await Axios.post(`${BASE_URL}/foods`, formData)
const { foodAdded, message } = response.data
setAddFoodStatus(foodAdded)
setAddFoodMessage(message)
//Remove waiting modal
setTimeout(() => {
modalLoading.classList.add('hidden')
}, 300)
} catch (err) {
formMsg.textContent = `Sorry something went wrong ${err}`
}
} else {
formMsg.textContent = 'please add all details'
}
}
return (
<>
{addFoodStatus === 1 ? (
<Modal
status={Success}
msg='Added food'
redirectLink='menu'
redirectTime='3000'
/>
) : addFoodStatus === 0 ? (
<Modal
status={Error}
msg={addFoodMessage}
msg=''
/>
) : null}
<section className='py-12 my-8 dashboard'>
<div className='container mx-auto'>
<h3 className='mx-0 mt-4 mb-12 text-2xl text-center'>Add food</h3>
<div>
<div className='food'>
{/* Show Modal Loading when submitting form */}
<Modal
status={Loading}
modalHidden='hidden'
classes='text-blue-500 text-center'
msg='Please wait'
/>
<form
method='POST'
className='form'
encType='multipart/form-data'
onSubmit={handleAddFood}
>
<label className='flex flex-wrap items-center justify-center gap-4 mb-8 sm:justify-between'>
<img
src={
preview === null
? 'https://source.unsplash.com/random?food'
: preview
}
alt='food' //change with food image name
className='object-cover p-1 border border-gray-400 w-28 h-28 dark:border-gray-300 rounded-xl'
/>
<input
type='file'
name='foodImg'
id='foodImg'
accept='image/*'
onChange={updateFoodImg}
className='grow-[.7] cursor-pointer text-lg text-white p-3 rounded-xl bg-orange-800 hover:bg-orange-700 transition-colors'
required
/>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-img-msg
></span>
</label>
<label htmlFor='foodName' className='form-group'>
<input
type='text'
id='foodName'
className='form-input'
autoFocus
required
onChange={e => setFoodName(createSlug(e.target.value.trim()))}
/>
<span className='form-label'>Food Name</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-name-msg
></span>
</label>
<label htmlFor='foodPrice' className='form-group'>
<input
type='number'
id='foodPrice'
className='form-input'
min='5'
max='500'
required
onChange={e => setFoodPrice(e.target.value.trim())}
/>
<span className='form-label'>Price</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-price-msg
></span>
</label>
<label htmlFor='foodDescription' className='form-group'>
<textarea
name='foodDescription'
id='foodDescription'
minLength='10'
maxLength='300'
className='form-input'
required
onChange={e => setFoodDesc(e.target.value.trim())}
></textarea>
<span className='form-label'>Description</span>
<span
className='inline-block my-2 text-red-400 font-[600]'
data-form-desc-msg
></span>
</label>
<div
className='my-14 text-red-400 font-[600] text-center text-xl'
data-form-msg
></div>
<div className='flex items-center justify-evenly'>
<button
type='submit'
className='min-w-[7rem] bg-green-600 hover:bg-green-700 text-white py-1.5 px-6 rounded-md'
>
Add
</button>
<Link
to={goTo('menu')}
className='text-gray-800 underline-hover text-bold dark:text-white'
>
Food Menu
</Link>
</div>
</form>
</div>
</div>
</div>
</section>
</>
)
}
export default AddFood
(server)
foods.js:
const FoodsModel = require(`${__dirname}/../models/food-model.js`)
const { v4: uuidv4 } = require('uuid')
const sharp = require('sharp')
const deleteFile = require('../functions/deleteFile')
const addFood = async (req, res) => {
const { foodName, foodPrice, foodDesc } = req.body
const { foodImg } = req.files
const foodImgName = uuidv4() + foodImg.name
const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName}.webp`
const foodImgDisplayPath = `/uploads/${foodImgName}`
const foods = new FoodsModel({
foodImgDisplayPath,
foodName,
foodPrice,
foodDesc
})
sharp(foodImg.data)
.rotate()
.resize(200)
.jpeg({ mozjpeg: true, quality: 50 })
.toBuffer()
.then(newBuffer => {
//changing the old jpg image buffer to new webp buffer
foodImg.data = newBuffer
foodImg.mv(foodImgMovePath, err => {
if (err) {
res.json({
message: `Sorry something wrong with server! 😥: ${err}`
})
return
}
foods.save()
res.json({
message: 'Food Added Successfully',
foodAdded: 1
})
})
})
.catch(err => {
res.json({
//https://mhmdhidr-restaurant.netlify.app/uploads/20cc09a0-1811-48b0-bffa-49e7a1981537chicken-legs.webp
message: `Sorry! Something went wrong, check the error => 😥: \n ${err}`,
foodAdded: 0
})
})
}
const getFood = async (req, res) => {
res.json(res.paginatedResults)
}
const deleteFood = async (req, res) => {
const { prevFoodImg } = req.body
const { foodId } = req.params
deleteFile(prevFoodImg)
try {
await FoodsModel.findByIdAndRemove(foodId)
res.json({
message: 'Food Deleted Successfully',
foodDeleted: 1
})
} catch (error) {
res.json({
message: `Sorry! Something went wrong, check the error => 😥: \n ${error}`,
foodDeleted: 0
})
}
}
const updateFood = async (req, res) => {
const { foodName, foodPrice, foodDesc, prevFoodImg } = req.body
const { foodId } = req.params
const { foodImg } = req.files || ''
const foodImgName = uuidv4() + foodImg?.name || ''
const foodImgMovePath = `${__dirname}/../../client/public/uploads/${foodImgName || ''}`
const foodImgDisplayPath =
foodImg !== '' && foodImg !== undefined ? `/uploads/${foodImgName}` : prevFoodImg
try {
await FoodsModel.findByIdAndUpdate(foodId, {
foodImgDisplayPath,
foodName,
foodPrice,
foodDesc
})
if (foodImg !== '' && foodImg !== undefined) {
deleteFile(prevFoodImg)
foodImg.mv(foodImgMovePath, err => {
if (err) {
res.json({ message: `Sorry something wrong with server! 😥: ${err}` })
}
})
}
res.json({
message: 'Food Updated Successfully',
foodUpdated: 1
})
} catch (error) {
res.json({
message: `Sorry! Something went wrong, check the error => 😥: \n ${error}`,
foodUpdated: 0
})
}
}
module.exports = { addFood, getFood, deleteFood, updateFood }
But when I try to upload a file in the Netlify app this error shows up:
Error: ENOENT: no such file or directory, open '/app/controllers/../../client/public/uploads/9631bb96-e41d-4c9a-aa35-d22b551ab662MASHAWI-IN-DUBAI.jpeg.webp'
I've tried to Google it a lot but unfortunately didn't find a solution.
Thanks for your help.
A two part answer:
Your back-end has no business putting files into your front-end's directory structure.
A better choice might be to use an uploads/ folder in the back-end project, exposing those over HTTPS, and linking to them from your front-end.
But that won't work on Heroku due to its ephemeral filesystem.
An even better choice would be to save them to a cloud-based object store like Amazon S3 or Azure Blob Storage, or a more specialized service like Cloudinary if they're images. Heroku tends to recommend S3.
Your back-end now just needs to store the URL to each file and provide that link to your front-end upon request.
Even on other hosts that allow you to save files into your back-end's filesystem, using a third-party service has many benefits. You can trivially scale horizontally (adding new nodes), your application becomes less stateful, etc.
User uploads never belong in your code repository, no matter how and where you choose to host them. They are content, not code, and should not be tracked and versioned alongside your code.

Cannot import meta data from mdx file in getStaticProps nextjs

I have a problem while trying to require meta data from an mdx file in my Next.js project.
MDX file example:
export const meta = {
title: 'title',
date: new Date('May 09, 2019'),
};
Content
export const getStaticProps = async context => {
const postFilenames = await recRead(process.cwd() + '/pages', ['*.tsx']);
const postMetadata = await Promise.all(
postFilenames.map(async p => {
const { meta } = require(p);
return meta;
}),
);
return {
props: {
postMetadata: postMetadata,
},
};
};
It is a modified version of this: https://sarim.work/blog/dynamic-imports-mdx. While accessing a website I get an error:
Cannot find module '/home/oliwier/webDev/oliwierwpodrozy/pages/balkany/1.mdx'.
BTW recRead is this https://www.npmjs.com/package/recursive-readdir.
What is going on? Outside of getStaticProps I can import data.
I found something ridiculous when trying to solve the problem.
// 1)console.log(postFilenamesToImport[0]);
// 2) const meta = await import('../pages/wielka-brytania/1.mdx');
// 3) const meta = await import(postFilenamesToImport[0]);
// console.log(meta.meta);
shows: ../pages/wielka-brytania/1.mdx which is a string
This one works
But this one doesn't. Shows error: Error: Cannot find module '../pages/wielka-brytania/1.mdx'
It is not a const problem. It is written for tests and i know that using 2) and 3) together would cause problem. This error occurs when 1) is commented.
You can import metadata like follows.
First, we export the metadata from within the .mdx file
// in /pages/posts/example.mdx
import Layout from "../../components/layout";
export const meta = {
title: "example",
date: "2021-12-27",
slug: "example",
};
Lorem ipsum.
export default ({ children }) => (
<Layout subtitle={meta.title}>{children}</Layout>
);
Then we use getStaticProps to scan the file system at runtime, importing each .mdx file as a module, then mapping out their metadata exports. Since we are displaying the metadata on the index page, we will pop index.js from the array.
// in /pages/posts/index.js
export const getStaticProps = async (context) => {
const postDirectory = path.join(process.cwd(), "src/pages/posts");
let postFilenames = fs.readdirSync(postDirectory);
postFilenames = removeItemOnce(postFilenames, "index.js");
const postModules = await Promise.all(
postFilenames.map(async (p) => import(`./${p}`))
);
const postMetadata = postModules.map((m) => (m.meta ? m.meta : null));
return {
props: {
postMetadata: postMetadata,
},
};
// thanks https://sarim.work/blog/dynamic-imports-mdx
};
function removeItemOnce(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
// thanks https://stackoverflow.com/a/5767357/13090245
}
Here is one way of using the prop to render a list of posts
// in /pages/posts/index.js
export default function PostsIndex({ postMetadata }) {
return (
<Layout subtitle="blog index">
<ul>
{postMetadata.map(({ slug, date, title }) => (
<li key={slug}>
<Link href={`/posts/${slug}`} a={title} />
<br />
{date}
</li>
))}
</ul>
</Layout>
);
}

Running ffmpeg (WASM/NodeJS) on multiple input files in a React App

I recently followed a tutorial by Fireship.io going over making a React App that enables a user to input a video file and convert it into a gif. Here is the source GitHub Repo.
The packages used by the project are #ffmpeg/ffmpeg and #ffmpeg/core, which take care of converting the video into a GIF (although this can be changed to whatever, like the FFmpeg CLI tool).
I wanted to take this a step further and make it possible for me to convert multiple videos at once, each into their own separate gif, however, I am having trouble running the next task when the first is finished.
Here is documentation I found about the ffmpeg wasm package. I also read this example given by the package providers to have multiple outputs from a single file.
Here is my code (App.jsx):
import { createFFmpeg, fetchFile } from '#ffmpeg/ffmpeg';
const ffmpeg = createFFmpeg({ log: true });
function App() {
const [ready, setReady] = useState(false);
const [videos, setVideos] = useState([]);
const [gifs, setGifs] = useState([]);
const load = async () => {
await ffmpeg.load();
setReady(true);
};
useEffect(() => {
load();
}, []);
const onInputChange = (e) => {
for (let i = 0; i < e.target.files.length; i++) {
const newVideo = e.target.files[i];
setVideos((videos) => [...videos, newVideo]);
}
};
const batchConvert = async (video) => {
const name = video.name.split('.mp4').join('');
ffmpeg.FS('writeFile', name + '.mp4', await fetchFile(video));
await ffmpeg.run(
'-i',
name + '.mp4',
'-f',
'gif',
name + '.gif',
);
const data = ffmpeg.FS('readFile', name + '.gif');
const url = URL.createObjectURL(
new Blob([data.buffer], { type: 'image/gif' }),
);
setGifs((gifs) => [...gifs, url]);
};
const convertToGif = async () => {
videos.forEach((video) => {
batchConvert(video);
}
);
return ready ? (
<div className="App">
{videos &&
videos.map((video) => (
<video controls width="250" src={URL.createObjectURL(video)}></video>
))}
<input type="file" multiple onChange={onInputChange} />
{videos && <button onClick={convertToGif}>Convert to Gif</button>}
{gifs && (
<div>
<h3>Result</h3>
{gifs.map((gif) => (
<img src={gif} width="250" />
))}
</div>
)}
</div>
) : (
<p>Loading...</p>
);
}
export default App;
The error I am getting is along the lines of "Cannot run multiple instances of FFmpeg at once", which I understand, however, I have no idea how to make the batchConvert function only run one instance at a time, whether it's outside or inside the function.
Thank you!
I think you need to put await before batchConvert(video);
const convertToGif = async () => {
videos.forEach((video) => {
await batchConvert(video);
}
);

React-Admin: Using "getList", I am getting "Error: cannot read property 'map' of undefined"

With react-admin, i'm trying to get the users list from API Restful Node server,and I have this error:
Error: cannot read property 'map' of undefined
Here's the getUsers in server user.controller.js:
const getUsers = catchAsync(async (req, res) => {
const users = await userService.getUsers(req.query);
const data = users.map(user => user.transform());
const total = users.length;
res.type('json');
res.set('Access-Control-Expose-Headers', 'Content-Range');
res.set('Content-Range', `users 0-2/${total}`);
res.set('X-Total-Count', total);
response = '{ data: ' + JSON.stringify(data) + ', total: ' + total + ' }';
res.send(response);
});
Here the data response received:
{
data: [
{"id":"5e6f5e3b4cf60a67701deeae","email":"admin#test.com","firstname":"Ad","lastname":"Min","role":"admin"},
{"id":"5e6f5e3b4cf60a67701deeaf","email":"test#test.com","firstname":"Jhon","lastname":"Doe","role":"user"}
],
total: 2
}
In react-admin, getList in dataProvider.js:
export default {
getList: (resource, params) => {
console.log(params);
const { field, order } = params.sort;
const query = {
...fetchUtils.flattenObject(params.filter),
sortBy: field
};
const url = `${apiUrl}/${resource}?${stringify(query)}`;
return httpClient(url).then(({ headers }, json) => ({
data: json,
total: parseInt(
headers
.get("Content-Range")
.split("/")
.pop(),
10
)
}));
},
Update:
UserList.tsx
import React from "react";
import {
TextField,
Datagrid,
DateInput,
Filter,
List,
EmailField,
SearchInput
} from "react-admin";
import { useMediaQuery, Theme } from "#material-ui/core";
import SegmentInput from "./SegmentInput";
import MobileGrid from "./MobileGrid";
const UserFilter = (props: any) => (
<Filter {...props}>
<SearchInput source="q" alwaysOn />
<DateInput source="createdAt" />
<SegmentInput />
</Filter>
);
const UserList = (props: any) => {
const isXsmall = useMediaQuery<Theme>(theme => theme.breakpoints.down("xs"));
return (
<List
{...props}
filters={<UserFilter />}
sort={{ field: "createdAt", order: "desc" }}
perPage={25}
>
{isXsmall ? (
<MobileGrid />
) : (
<Datagrid optimized rowClick="edit">
<TextField source="id" />
<EmailField source="email" />
<TextField source="firstname" />
<TextField source="lastname" />
<TextField source="role" />
<DateInput source="createdAt" />
<DateInput source="updatedAt" />
</Datagrid>
)}
</List>
);
};
export default UserList;
Here the documentation with somes examples for getList:
https://marmelab.com/react-admin/DataProviders.html#writing-your-own-data-provider
I don't understand, i need help please, what wrong?
Thanks & Regards
Ludo
Here's a detailed explanation of what's happening with the map() in reactjs.
And this source is on point for resolving this in nodejs
In your case, let's take a closer look here:
// user.controller.js
const getUsers = catchAsync(async (req, res) => {
// Actually, it might be wiser to first declare users
+ let users = [];
// You await for users (querying) to be resolved or rejected, cool
- const users = await userService.getUsers(req.query);
// And we then we assign "users" as before
+ users = await userService.getUsers(req.query);
// But here, map() requires users to be defined before iteration.
// So for this to work, we need the value of users to be resolved first, right?
- const data = users.map(user => user.transform());
// Could you change the above line to this, and see?
// This doesn't because we gave map(), a synchronous parameter (function)
- const data = Promise.all(users.map(user => user.transform()));
// Ok, break this up for easier understanding
// let's declare a userList, where we give map() an "async" parameter
// At this point, userList will be an "Array of Promises"
+ const userList = users.map(async user => user.transform());
// Now we resolve all the Promises, and we should have our clean data
+ const data = await Promise.all(userList);
});
With that change, we make sure that map() runs after our users is resolved.
Does that work for you? Let me know.

Why formData does not work with multiple files?

I'm facing a problem with a React project I'm working on: I'm trying to upload multiple images to a Node Express API. I'm using a formData object and I used the append() method to append the form fields from the component State.
In the Express code I'm using multer, all the attributes in the req.body are there but req.files is empty.
I changed the code to upload a single image also using formData() and it works; the problem seems to be only when I try with multiple files using the formData object. I also tested using a regular form (not react) and that also worked!
I'm wondering if there is something I'm missing when I use formData with a file input with multiple files?
import React, { Component } from "react";
import axios from "axios";
class Form extends Component {
constructor() {
super();
this.state = { images: {} };
}
onChangeImages = e => {
this.setState({ images: e.target.files })
};
onSubmit = e => {
e.preventDefault();
const { images } = this.state;
const formData = new FormData();
formData.append("images", images);
axios
.post("/api/post/create", formData)
.then(res => console.log(res.data))
.catch(err => console.error(err));
};
render() {
return (
<form onSubmit={this.onSubmit}>
<input
onChange={this.onChangeImages}
type="file"
name="images"
multiple
accept="image/png, image/jpeg, image/jpg"
/>
<br />
<input type="submit" value="Send" />
</form>
);
}
}
export default Form;
Express code
const express = require('express');
const router = express.Router();
const multer = require('multer');
const upload = multer({ dest: 'uploads/' });
router.post('/create', upload.array('images', 2), (req, res) => {
console.log(req.files);
console.log(req.body);
res.status(200).json(req.body);
});
module.exports = router;
formData.append("images", images);
You need to append each file in turn. FormData doesn't support a FileList object.
for (let i = 0 ; i < images.length ; i++) {
formData.append("images", images[i]);
}
there are a lot of other alternatives than using the backend. You can use cloudinary or firebase to upload all images and get their urls and let your backend store those URLs in the database.

Resources