I'm trying to figure out how I can upload images with NextJS api routes and isomorphic-unfetch. Everything I've seen is utilizing express, but I feel like I should be able to accomplish it with api routes.
So far I have an input of type file that fires an onClick handler:
const handleImage = async (theImg) => {
try {
const res = await fetch("/api/admin", {
method: "POST",
body: theImg,
});
const json = await res.json();
console.log(json);
} catch (error) {
console.log(error);
}
};
return(
<input
onChange={(e) => handleImage(e.target.files[0])}
type="file"
name="uploaded_img"
placeholder="Upload File"
/>
);
but when I try to read the request in the API route, the body appears to be corrupt, and I can't open the resulting file.
api route:
import fs from "fs";
export default async (req, res) => {
try {
const data = fs.writeFile("./uploads/test.jpg", req.body);
//file written successfully
console.log("wrote file");
} catch (err) {
console.error(err);
}
};
the resulting file cannot be opened and contains a bunch of missing characters. I'm probably doing this entirely wrong, but any push in the right direction would be hugely appreciated!
Related
I'm trying to create a small social network in which we can send posts (with or without images).
I manage to create posts without an image (just text), it works very well, but as soon as I add an image to my form and submit the form, it is impossible to find the image. Normally it should be saved in the "images" folder but it is always empty.
I am using multer to do that, here is my code :
My form component :
const WhatsUpForm = ({ className, id, name, placeholder }) => {
const [inputValue, setInputValue] = useState("");
const inputHandler = (e) => {
setInputValue(e.target.value);
};
const submitHandler = async (e) => {
e.preventDefault();
const post = {
author_firstname: JSON.parse(localStorage.getItem("user")).user_firstname,
author_lastname: JSON.parse(localStorage.getItem("user")).user_lastname,
message: inputValue,
date_creation: dayjs().format(),
image_url: ""
};
// POST request
await POST(ENDPOINTS.CREATE_POST, post);
// document.location.reload()
};
return (
<form className={className} onSubmit={submitHandler} method="POST" action="/api/post" enctype="multipart/form-data">
<input className="testt" type="text" id={id} name={name} placeholder={placeholder} required value={inputValue} onChange={inputHandler}/>
<div className="icons_container">
<input type="file" name="image" id="image" className="icons_container__add_file" />
<label for="image">
<FontAwesomeIcon icon={faImages} />
</label>
<button type="submit" className="icons_container__submit">
<FontAwesomeIcon icon={faPaperPlane} />
</button>
</div>
</form>
);
};
My routes and the multer's code :
const multer = require("multer");
const path = require("path");
const storage = multer.diskStorage({
destination: (req, file, callback) => {
callback(null, "../images");
},
filename: (req, file, callback) => {
console.log("multer");
console.log("file :", file);
callback(null, Date.now() + path.extname(file.originalname));
},
});
const upload = multer({ storage: storage });
// Post CRUD
router.get("/", auth, postCtrl.getAllPosts);
router.post("/", auth, upload.single("image"), postCtrl.createPost);
router.delete("/:id", auth, postCtrl.deletePost);
router.put("/:id", auth, postCtrl.updatePost);
console.log("multer") is not trigger, and when i look the payload in network tab in my browser, i don't see any images.
And finally, my controller for createPost function :
exports.createPost = (req, res, next) => {
let { body } = req;
delete(req.body.image_url)
body = {
...body,
likes: "",
};
const sql = "INSERT INTO posts SET ?";
db.query(sql, body, (err, result) => {
if (err) {
res.status(404).json({ err });
throw err;
}
res.status(200).json({ msg: "Post added..." });
});
};
For now, i don't want to put the image's URL in my SQL DB, i just want to save the image in my images folders. I have verified the path (../images) and it's coorect.
How do I save the image in my image folder?
I don't see the file data gets sent to server from your POST request.
// object post doesn't have the file data
await POST(ENDPOINTS.CREATE_POST, post);
Consider using FormData
const submitHandler = async (e) => {
e.preventDefault();
const post = new FormData();
// non form data
formData.append("author_firstname", JSON.parse(localStorage.getItem("user")).user_firstname);
...
// form data
formData.append("image", document.getElementById("image").files[0]);
...
// POST request
await POST(ENDPOINTS.CREATE_POST, post);
// document.location.reload()
};
I have created an API in Node js for file upload. It is working fine with the postman.
I made a form for uploading Excel files in Next Js. I can able to see selected files in the console.
But I am not able to set the file in formdata. I am getting empty form data in the console.
<div>
<input
class="form-control w-25"
multiple={false}
type="file"
id="ExcelFile"
onChange={uploadFile}
required
></input>
{/* </label> */}
<button
type="button"
// disabled={!selectedImage}
class="btn btn-primary "
>
ADD SOLUTION
</button>
</div>
const uploadFile = ({ target: { files } }) => {
console.log(files[0]);
// let data = new formData();
let FilesData = new FormData();
FilesData.append("excel_file", files[0]);
console.log("Files in multipart");
console.log(FilesData);
// data.append("file", files[0]);
};
https://codesandbox.io/embed/next-js-forked-th22n?fontsize=14&hidenavigation=1&theme=dark
If you try to console.log FormData object, you will just get empty object.Instead you should call the entries method on the FormData object.
for (const pair of FilesData.entries()){
console.log(pair)
}
It will return list of arrays of key-value pairs.
Notice that you can`t see your formData in console.log
If you want to pass data with formData you must use one middleware in your server like this: https://nextjs.org/docs/api-routes/api-middlewares
And i just use one example maybe be usefull:
in your formData:
var FormData = require("form-data");
let data = new FormData()
data.append("urlOrContent", urlOrContent)
and then send your formData in your server side
in your server side:
import middleware from "./middleware/middleware";
import nextConnect from "next-connect";
const handler = nextConnect();
handler.use(middleware);
handler.post(async (req, res) => {
//in formData: req.body.urlOrcontent[0]
try {
const response = await fetch(
req.body?.urlOrContent[0],
);
res.status(200).send({
data: {
message: "Success",
data: response.json(),
},
});
} catch (err) {
let e = {
func: "states.handler",
message: "خطای داخلی سرور رخ داده است!",
error: "Internal Server Error!",
code: 500,
};
res.status(500).json(e);
}
});
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
Here's a little example on a simple form submission in next.js using multer to parse the form data.
Client
This is the client page, containing a super simple HTML form (can work without JS too)
// pages/my-form.ts
export default function Page() {
return (
<div>
<form id="data" method="post" action='/api/on-form-submit' encType="multipart/form-data">
<input name="title" label="Title"/>
<input name="video" label="Video"/>
<button type="submit">Submit</button>
</form>
</div>
);
};
Server
This is the server function that will receive the form submission.
// pages/api/on-form-submit.ts
import multer from "multer";
import { NextApiRequest, NextApiResponse } from "next";
async function parseFormData(
req: NextApiRequest & { files?: any },
res: NextApiResponse
) {
const storage = multer.memoryStorage();
const multerUpload = multer({ storage });
const multerFiles = multerUpload.any();
await new Promise((resolve, reject) => {
multerFiles(req as any, res as any, (result: any) => {
if (result instanceof Error) {
return reject(result);
}
return resolve(result);
});
});
return {
fields: req.body,
files: req.files
}
}
// IMPORTANT: Prevents next from trying to parse the form
export const config = {
api: {
bodyParser: false,
},
};
const Handler: NextApiHandler = async (req, res) => {
const result = await parseFormData(req, res);
console.log(result);
res.status(200).redirect('/success-page');
}
export default Handler;
I've implemented a working post route that uploads a single image to cloudinary on submit.
What needs to be changed to enable multiple image upload? Any help is appreciated.
My post route:
app.post("/portfolio/new", upload.single('image'), function(req, res) {
cloudinary.v2.uploader.upload(req.file.path, function(err, result) {
if (err) {
console.log(err);
}
req.body.image = result.secure_url;
console.log(req.body.image);
Portfolio.create(req.body.project, function(err, newProject) {]
if (err) {
console.log(err);
}
res.redirect("/portfolio");
});
});
});
My HTML (with EJS):
<form action="/portfolio/new" enctype="multipart/form-data" method="POST">
<div>
Select images:
<input type="file" id="image" name="image" accept="image/*" required>
</div>
</form>
const cloudinaryImageUploadMethod = async file => {
return new Promise(resolve => {
cloudinary.uploader.upload( file , (err, res) => {
if (err) return res.status(500).send("upload image error")
resolve({
res: res.secure_url
})
}
)
})
}
router.post("/", upload.array("img", 3 ), async (req, res) => {
const urls = [];
const files = req.files;
for (const file of files) {
const { path } = file;
const newPath = await cloudinaryImageUploadMethod(path);
urls.push(newPath);
}
const product = new Product({
name: req.body.name,
productImages: urls.map( url => url.res ),
});
}
To add to the above, you can use this pen for a simple javascript multiple image uploader: codepen.io/team/Cloudinary/pen/QgpyOK
Here's a very fancy uploader widget with a ton of options built-in: codepen.io/dzeitman/pen/EwgjJV
Hope this helps others trying to do something similar and just want to see something working.
in input tag, you should write ... multiple.
and in post route, one should write
upload.array("image")
instead of
upload.single()
and in the schema file, you should define the image as an array of objects
i.e. image:[
{
URL: String,
filename: String
}
]
I'm sending some data from my ReactJS front-end application to my node/express backend, however, whenever I send the data, I get the error message mentioned in the title.
This is my ReactJS code:
class App extends React.Component {
constructor(props) {
super(props);
//states and binding functions
}
fetchData () {
fetch('/users')
.then(res => res.json())
.then(users => this.setState({users}));
}
addUser (event) {
let fileData = new FormData();
fileData.append("file", this.state.filesToBeSent);
axios.post('adduser',
querystring.stringify({
entry: this.state.currname,
passwrd: this.state.currpasswrd,
fileData : fileData
})
)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<div className="App">
<form onSubmit={this.addUser} encType="multipart/form-data">
<label>New username:</label>
<input value={this.state.currname} onChange={this.handleChange}></input>
<input value={this.state.currpasswrd} onChange={this.handlePassword} />
<input type="file" name="file" onChange={this.handleFile} />
<button type="submit" >Add</button>
</form>
<h1>Current Users: </h1>
{this.state.users.map((name, n) =>
//map through user-array and display names
)}
</ul>
</div>
);
}
}
(Sorry if it's a lot of code, I shortened it as much as possible but I wasn't sure which parts would be relevant to the question).
Here is how I receive the data in node and how I save parts of it to my db:
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "./uploads/");
},
filename: (req, file, cb) => {
const newFilename = `${uuidv4()}${path.extname(file.originalname)}`;
cb(null, newFilename);
},
})
const upload = multer({ storage });
router.post("/", upload.single("file"), (req, res) => {
res.send(req.file + " and exit.");
var newUser = new Todo();
newUser.username = req.body.entry;
newUser.passwrd = req.body.passwrd;
newUser.save(function (err) {
if(err)
res.send(err);
res.send('User added successfully!');
});
});
This is where it gets weird. The application works perfectly, until I insert upload.single("file"), however, I couldn't seem to figure out why. I didn't have any problems when I just had text inputs, even when I created the FormData() etc. it still worked fine until I implemented that.
I tried looking it up and to implement answers posted on here, however, nothing seems to help.
On the screen I get the following error message: Unhandled Rejection (SyntaxError): Unexpected token P in JSON at position 0
When I check the terminal I receive the error message mentioned in the title. I tried removing the content-headers (not sure what that would do, but the tutorial I was following to implement the file upload did not use content-headers, that's why I tried to remove them.
Does anyone know how to fix this error?
Edit: The error message in the terminal also contains ECONNRESET. I followed the link in the terminal https://nodejs.org/api/errors.html#errors_common_system_errors but I'm still not sure how I can fix that.
I suggest you to append all fields to FormData object, and do not convert the submitted data to json string:
let fileData = new FormData();
fileData.append("entry", this.state.currname);
fileData.append("passwrd", this.state.currpasswrd);
fileData.append("file", this.state.filesToBeSent);
axios.post('adduser', fileData)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
I get undefined when I try to read the req.files and if I read req.body I get file: [object Object] which, when read on the front end before being sent is
Icon: File(4076381)
lastModified: 1513292207750
lastModifiedDate: Thu Dec 14 2017 17:56:47 GMT-0500 (EST) {}
name: "image.jpeg"
size: 4076381
type: "image/jpeg"
webkitRelativePath: ""
but after
let upload = new FormData();
upload.append('file', user);
console.log(upload);
upload logs FormData = {} so I have no idea why .append() did not work. user is the Icon object shown above.
but when sent back it is the same [object Object] as on the server of course.
I am trying to understand why req.file is not readable or any other alternative for how to read the uploaded file so that I can then do whatever I want with it (end game, store in a db as a field in mongodb, then pull out and render on the client side after logging in).
Relevant code
Register.js
// standard React stuff
handleUpload = (e) => {
console.log(e.target.files[0]);
this.setState({ Icon: e.target.files[0] });
};
handleSubmit = async (e) => {
e.preventDefault();
const dispatchObject = {
Icon: this.state.Icon
}
await this.dispatchSubmit(dispatchObject);
}
render(){
return (
<form noValidate autoComplete="off" onSubmit={this.handleSubmit}>
/>
<Input
id='Icon'
type='file'
name='Icon'
accept='image/png, image/jpeg'
onChange={this.handleUpload}
/>
<input type='submit' value='Submit'/>
</form>
)
}
action/index.js
export const register = (user, history) => {
console.log(user);
let upload = new FormData();
upload.append('file', user);
return dispatch => {
axios.post('http://localhost:9000/register', upload)
.then((res) => {
console.log(res.data);
console.log('User successfully created');
})
.catch(err => console.log(err) );
}
}
server.js
import multer from 'multer';
const storage = multer.diskStorage({
destination: './files',
filename(req, file, cb) {
console.log(file);
cb(null, `${new Date()}-${file.originalname}`);
},
});
const upload = multer({ storage });
app.post('/register', upload.single('file'), async (req, res) => {
console.log(req.body.file);
res.json(req.body.file);
});
Multer will create the relevant folder called ./file but there is never anything in it and req.file is not accessible. I cannnot send it through the body in a standard manner because it comes out in the [object Object] format.
you are logging req.body.file but it is req.file.
The solution was too change the below to user.Icon.
export const register = (user, history) => {
console.log(user);
let upload = new FormData();
upload.append('file', user);
I also separated any other files that I wanted to send in another api call.