I want to read the CSV file from Next.js API and it's sent from POSTMAN like the below image
But how do I read that CSV file?
Like this below code, I don't read the file anyhow.
export default async function handler(req, res) {
console.log(req.body);
// console.log(req.file); // Undefined
// console.log(req.files['file']); // Undefined
}
My goal is to read files and convert them to an array of data.
Thanks
I recommend you to use the formidable package to read the files server side. You also need to disable bodyParser. A minimum example would be this code:
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === "POST") {
const data: {fields, files} = await new Promise((resolve, reject) => {
const form = new IncomingForm();
form.parse(req, (err, fields, files) => {
if (err) return reject(err);
resolve({ fields, files });
});
});
const file = data.files.file;
let fileData = await fs.promises.readFile(file.filepath);
// now you need to parse the csv file (contents is in the fileData buffer) or do what ever you want
res.status(200).end("Uploaded");
return;
}
res.status(400).end("Method not allowed");
}
export const config = {
api: {
bodyParser: false,
}
};
A package for parsing csv files can be found here
Related
Hi I am trying to use Next.js api routes to store an image file on NFT.storage.
I am taking an image as an input from the user in the browser and passing that to the api file using Axios and FormData
const formData = new FormData(); formData.append('image', imageUrl); const cid = await axios.post('api/uploadNFTData', formData);
here the imageUrl is event.target.files[0]
and in the api folder I have uploadNFTData.js containing this code
I have done this to keep my API key more secure by taking the value from the .env
import { NFTStorage, File, Blob } from 'nft.storage';
import formidable from 'formidable';
const client = new NFTStorage({ token: process.env.NFT_STORE_TOKEN});
// first we need to disable the default body parser
export const config = {
api: {
bodyParser: false,
},
}
export default async function uploadNFTData(req,res) {
const form = new formidable.IncomingForm();
form.uploadDir = "./";
form.keepExtensions = true;
var formfields = await new Promise(function (resolve, reject) {
form.parse(req, (err, fields, files) => {
if (err) {
reject(err);
return;
}
resolve(files);
console.log("within form.parse method, subject field of fields object is: " + files);
}); // form.parse
});
try{
const cid = await client.storeDirectory([formfields]);
res.status(200).json({ message: cid });
}catch(err){
res.status(500).json({ message: "could not upload data" });
}
}
Now I am getting the following error on server side
error - TypeError: blob.stream is not a function
Can someone please help regarding what I am doing wrong??
Or This kind of call cannot be done??
How do I get uploaded image in next.js API route and save it on public folder? I have front end ready. I'm uploading images to an endpoint using plain JavaScript.
here is the onSubmit function for uploading images. Suggest me if I'm doing it wrong here. The main question is how do I retrieve it?
const onSubmit=async(e)=>{
e.preventDefault();
const fd=new FormData()
fd.append('myfile',image.name)
let res=await fetch(`http://localhost:3000/api/upload`,{
method: 'POST',
headers: {
"Content-Type": "image/jpeg",
},
body: fd,
})
let response=await res.json();
one more bonus question, it's surely not a good idea to save the uploaded images on public folder. I have save it somewhere on the cloud.
This is the endpoint code I used for uploading image in nextjs, it requires some additional packages I will list them bellow also.
next-connect
multer
uuid
import nextConnect from "next-connect";
import multer from "multer";
import { v4 as uuidv4 } from "uuid";
let filename = uuidv4() + "-" + new Date().getTime();
const upload = multer({
storage: multer.diskStorage({
destination: "./public/uploads/profiles", // destination folder
filename: (req, file, cb) => cb(null, getFileName(file)),
}),
});
const getFileName = (file) => {
filename +=
"." +
file.originalname.substring(
file.originalname.lastIndexOf(".") + 1,
file.originalname.length
);
return filename;
};
const apiRoute = nextConnect({
onError(error, req, res) {
res
.status(501)
.json({ error: `Sorry something Happened! ${error.message}` });
},
onNoMatch(req, res) {
res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
},
});
apiRoute.use(upload.array("file")); // attribute name you are sending the file by
apiRoute.post((req, res) => {
res.status(200).json({ data: `/uploads/profiles/${filename}` }); // response
});
export default apiRoute;
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
no Need to use any packages to handle file uploading you can use base64 to convert file to string and return it back to file by using "fs" module
why This way is beterr then using formData ?
because you duleing with normal post request where you can send any kind of data with it and use body parsere .
converting
const toBase64 = (file: File) => new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
}
send a post request to server
const base64: string = await toBase64(file) as string;
const fileData = { base64, fileName: file.name };
const result = await api.post("/foo", fileData, name: "Salih", massage: "Hello World"})
converting base64 to file in server
function base64ToFile(file: { base64: string, fileName: string }) {
const fileContents = file.base64.replace(/^data:image\/png;base64,/, "");
fs.mkdirSync("./public/uploads", { recursive: true });
const fileName = `./public/uploads/${Date.now().toString() + file.fileName}`
fs.writeFile(fileName, fileContents, 'base64', function (err) { console.log(err) });
}
I suggest the popular and lightweight formidable library:
# install
yarn add formidable#v3 #types/formidable
// pages/api/file-upload.ts
import fs from "fs";
import path from "path";
import { File } from "formidable";
// Important for NextJS!
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<string>
) {
try {
// Parse request with formidable
const { fields, files } = await parseFormAsync(req);
// Files are always arrays (formidable v3+)
const myfile = (files["myfile"] as any as File[])[0];
// Save file in the public folder
saveFile(myfile, "./public/uploads");
// Return success
res.status(200).json("success!");
} catch (e) {
return res.status(500).json(e);
}
}
function saveFile(file: File, publicFolder: string): void {
const fileExt = path.extname(file.originalFilename || "");
fs.renameSync(file.filepath, `${publicFolder}/${file.newFilename}${fileExt}`);
}
// ./helpers/formidable.ts
import type { NextApiRequest } from "next";
import formidable from "formidable";
export type FormidableParseReturn = {
fields: formidable.Fields;
files: formidable.Files;
};
export async function parseFormAsync(
req: NextApiRequest,
formidableOptions?: formidable.Options
): Promise<FormidableParseReturn> {
const form = formidable(formidableOptions);
return await new Promise<FormidableParseReturn>((resolve, reject) => {
form.parse(req, async (err, fields, files) => {
if (err) {
reject(err);
}
resolve({ fields, files });
});
});
}
Bonus question
one more bonus question, it's surely not a good idea to save the uploaded images on public folder. I have save it somewhere on the cloud.
S3 and other cloud services
You can save on cloud services with Formidable.
See the official examples: https://github.com/node-formidable/formidable/blob/master/examples/store-files-on-s3.js
But you don't need to use cloud storage to protect private uploads. You can store them locally.
Working with private uploads locally
Saving:
Store the uploads in a non-public folder;
Ex. /private-uploads/{logged_user_id}/;
Reading:
Create an API page to fetch the file
Ex. https://.../uploads/{filename}
Fail if the file doesn't belong to the authenticated user;
Send the file as the response;
Security:
With the above folder scheme, hackers can use .. and similar on the filename to obtain unauthorized access;
Sanitize the filename having this in mind (ex. only allow alphanumeric characters);
Alternatively, use a database table to control ownership instead of a folder scheme;
I am trying to get my node.js backend to upload a file to AWS S3, which it got in a post request from my front-end. This is what my function looks like:
async function uploadFile(file){
var uploadParams = {Bucket: '<bucket-name>', Key: file.name, Body: file};
s3.upload (uploadParams, function (err, data) {
if (err) {
console.log("Error", err);
} if (data) {
console.log("Upload Success", data.Location);
}
});
}
When I try uploading the file this way, I get an Unsupported Body Payload Error...
I used fileStream.createReadStream() in the past to upload files saves in a directory on the server, but creating a fileStream did not work for me, since there is no path parameter to pass here.
EDIT:
The file object is created in the angular frontend of my web application. This it the relevant html code where the file is uploaded by a user:
<div class="form-group">
<label for="file">Choose File</label>
<input type="file" id="file"(change)="handleFileInput($event.target.files)">
</div>
If the event occurs, the handleFileInput(files: FileList) method in the corresponding component is called:
handleFileInput(files: FileList) {
// should result in array in case multiple files are uploaded
this.fileToUpload = files.item(0);
// actually upload the file
this.uploadFileToActivity();
// used to check whether we really received the file
console.log(this.fileToUpload);
console.log(typeof this.fileToUpload)
}
uploadFileToActivity() {
this.fileUploadService.postFile(this.fileToUpload).subscribe(data => {
// do something, if upload success
}, error => {
console.log(error);
});
}
the postFile(fileToUpload: File) method of the file-upload service is used to make the post request:
postFile(fileToUpload: File): Observable<Boolean> {
console.log(fileToUpload.name);
const endpoint = '/api/fileupload/single';
const formData: FormData = new FormData();
formData.append('fileKey', fileToUpload, fileToUpload.name);
return this.httpClient
.post(endpoint, formData/*, { headers: yourHeadersConfig }*/)
.pipe(
map(() => { return true; }),
catchError((e) => this.handleError(e)),
);
}
Here is the the server-side code that receives the file and then calls the uploadFile(file) function:
app.post('/api/fileupload/single', async (req, res) => {
try {
if(!req.files) {
res.send({
status: false,
message: 'No file uploaded'
});
} else {
let file = req.files.fileKey;
uploadFile(file);
//send response
res.send({
status: true,
message: 'File is uploaded',
data: {
name: file.name,
mimetype: file.mimetype,
size: file.size
}
});
}
} catch (err) {
res.status(500).send(err);
}
});
Thank you very much for your help in solving this!
Best regards, Samuel
Best way is stream the file. Assuming you are. reading it from disk. You could do this
const fs = require("fs");
const aws = require("aws-sdk");
const s3Client = new aws.S3();
const Bucket = 'somebucket';
const stream = fs.createReadStream("file.pdf");
const Key = stream.path;
const response = await s3Client.upload({Bucket, Key, Body: stream}).promise();
console.log(response);
Im using Vue with vue-apollo in the frontend and graphql stand-alone Apollo Server 2 with mongodb through mongoose in the backend. I have a simple blog application in which posts also have an Image. Everything works fine except uploading Images. I want the images to be uploaded to my local filesystem in a folder on my backend and only the path to the image saved in my mongodb document.
the mutation:
async createPost(parent, args, context, info) {
//...
const {stream, filename} = await args.img
const img_path = await upload({stream, filename})
const post = await Post.save({
//img is a string in my mongo model
img: img_path,
author_name: args.user.username,
author_email: args.user.email
});
}
the upload method that should return the path and save the image to local:
const upload = ({ stream, filename }) => {
const id = shortid.generate()
const path = `${UPLOAD_DIR}/${filename}-${id}`
new Promise((resolve, reject) =>
stream
.pipe(fs.createWriteStream(filename))
.on("finish", () => resolve(path))
.on("error", reject(Error))
);
}
The error im getting is that stream and filename are undefined when calling upload() but args.img is an object if i log it. And uploading them to my local folder doesnt work neither. Any help is appreciated and marked as accepted answer
It would be nice to share your graphql Schema so that we can see the types you're returning. However, Here's how i have been handling file uploads in most of my apps.
graphql-schema
type File {
id: ID!
filename: String!
mimetype: String!
path: String!
}
mongoose schema
import { Schema, model } from "mongoose";
const fileSchema = new Schema({
filename: String,
mimetype: String,
path: String,
});
export default model("File", fileSchema);
Function to store uploads:
const storeUpload = async ({ stream, filename, mimetype }) => {
const id = shortid.generate();
const path = `images/${id}-${filename}`;
// (createWriteStream) writes our file to the images directory
return new Promise((resolve, reject) =>
stream
.pipe(createWriteStream(path))
.on("finish", () => resolve({ id, path, filename, mimetype }))
.on("error", reject)
);
};
To process the uploads
const processUpload = async (upload) => {
const { createReadStream, filename, mimetype } = await upload;
const stream = createReadStream();
const file = await storeUpload({ stream, filename, mimetype });
return file;
};
Mutation
export default {
Mutation: {
uploadFile: async (_, { file }) => {
mkdir("images", { recursive: true }, (err) => {
if (err) throw err;
});
const upload = await processUpload(file);
// save our file to the mongodb
await File.create(upload);
return upload;
},
},
};
Here you can find an article i wrote on how to handle file uploads
I have this code that creates a project and uploads the image to Amazon S3.
I am using Bodyparser middleware to handle the data coming from the client but after some research I found out that it doesn't handle formdata. I need to use multer or some library like that.
Here is my attempt to use multer:
In my routes folder that handles the Post request to the Create function, I added it like this:
import multer from 'multer';
const upload = multer();
routes.post(
'/',
upload.any('projectImage'),
ProjectController.create,
);
Here is the Create function:
export async function create(req, res, next) {
const body = req.body;
S3Upload(req, res, async (s3err, s3res) => {
if (s3err) {
res.send('Error occured uploading image')
} else if (s3res && s3res.Location) {
body.projectImage = s3res.Location
try {
return res
.status(HTTPStatus.CREATED)
.json(await Project.createProject(body, req.user._id));
} catch (err) {
err.status = HTTPStatus.BAD_REQUEST;
return next(err);
}
} else {
res.send('Error creating project.')
}
});
}
Now when I send a post request to the /project routes. I get this error:
(node:77236) UnhandledPromiseRejectionWarning: TypeError: (0 , _s2.default) is not a function
(node:77236) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
PS. The Create function works perfectly without adding the S3Upload. & the S3Upload also works perfectly if I am only sending a file. The only time when it doesnt work is when I send mix data. i.e. a form with some text fields and a file.
I didn't post the S3Upload function but if anyone is curious, let me know I will share the code for it.
UPDATE
export async function S3Upload(req, res, callback) {
const chunks = [];
let fileType;
let fileEncodingType;
const busboy = new Busboy({
headers: req.headers,
});
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
filename.replace(/ /g, "_");
fileType = mimetype;
fileEncodingType = encoding;
file.on('data', data => {
chunks.push(data)
});
file.on('end', () => {
console.log(`File [${filename}] Finished`);
});
});
busboy.on('finish', () => {
const userId = UUID();
const params = {
Bucket: BUCKET_NAME,
Key: userId,
Body: Buffer.concat(chunks),
ACL: ACL,
ContentEncoding: fileEncodingType,
ContentType: fileType
}
S3.upload(params, (err, s3res) => {
if (err) {
callback(err);
} else {
callback(null, s3res);
}
});
});
req.pipe(busboy);
}