Images I upload via Next.js API routes are corrupted. I am using Formidable.
From my React component I'm submitting a form via these functions:
const fileUpload = async (file: File) => {
const url = '/api/image'
const formData = new FormData()
formData.append('file', file)
const config = {
headers: {
'content-type': 'multipart/form-data',
},
}
const response = await axios.post(url, formData, config)
const { data } = response
return data
}
const handleSubmit = async (event: React.SyntheticEvent) => {
const url = '/api/post'
if (files) {
// ignore
fileUpload(files[0]).then((response) =>
console.log('submit response', response)
)
}
event.preventDefault()
}
And the API route in Next looks like this:
import formidable from 'formidable'
const fs = require('fs')
const { BlobServiceClient } = require('#azure/storage-blob')
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config()
}
const AZURE_STORAGE_CONNECTION_STRING =
process.env.AZURE_STORAGE_CONNECTION_STRING
export const config = {
api: {
bodyParser: false,
},
}
const getBlobName = (originalName) => {
const identifier = Math.random().toString().replace(/0\./, '')
return `${identifier}-${originalName}`
}
export default async (req, res) => {
const form = new formidable.IncomingForm()
form.keepExtensions = true
form.uploadDir = './public/static/uploads'
form.parse(req, async (err, fields, files) => {
if (err) {
return
}
return res.json({ fields, files })
})
form.on('file', async (name, file) => {
const blobServiceClient = await BlobServiceClient.fromConnectionString(
AZURE_STORAGE_CONNECTION_STRING
)
const containerClient = await blobServiceClient.getContainerClient(
'images'
)
const buff = fs.readFileSync(file.path)
const data = buff.toString('base64')
const blobName = getBlobName(file.name)
const blockBlobClient = containerClient.getBlockBlobClient(blobName)
blockBlobClient.upload(data, data.length)
})
}
The image which gets stored locally is corrupt and looks like a TV tuned to a dead channel. I'm clearly not encoding it properly — but unsure whether it's my ContentType or the string encoding?
I believe the problem is coming because you're converting the data into a base64 encoded string:
const data = buff.toString('base64')
Considering you're already saving the uploaded file somewhere on the server (this is done by formidable package), please try something like:
const blobName = getBlobName(file.name)
const blockBlobClient = containerClient.getBlockBlobClient(blobName)
blockBlobClient.uploadFile(file.path)
uploadFile() method reference: https://learn.microsoft.com/en-gb/javascript/api/#azure/storage-blob/blockblobclient?view=azure-node-latest#uploadfile-string--blockblobparalleluploadoptions-.
UPDATE
Please try this code. I just tried this code and was able to upload the file successfully. When I downloaded the file, it was not corrupted.
form.on('file', async (name, file) => {
const blobServiceClient = await BlobServiceClient.fromConnectionString(AZURE_STORAGE_CONNECTION_STRING);
const containerClient = await blobServiceClient.getContainerClient('images');
const blobName = file.name;
const contentType = file.type;
const filePath = file.path;
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.uploadFile(file.path);
});
UPDATE #2
Here's the complete code I used:
<form id="form1" method="post" action="/upload" enctype="multipart/form-data">
<input name="file" type="file" id="file1" accept="image/*"/>
<input type="button" id="button1" value="Upload" />
</form>
<script src="https://code.jquery.com/jquery-1.12.4.js" integrity="sha256-Qw82+bXyGq6MydymqBxNPYTaUXXq7c8v3CwiYwLLNXU=" crossorigin="anonymous"></script>
<script>
$(document).on('ready', function() {
});
$('#button1').on('click', function(e) {
data = new FormData();
console.log($('#file1')[0].files[0]);
data.append('file', $('#file1')[0].files[0]);
console.log(data);
$.ajax({
url: '/upload',
data: data,
processData: false,
contentType: false,
type: 'POST',
success: function ( data ) {
alert( data );
}
});
e.preventDefault();
e.stopPropagation();
});
</script>
var express = require('express'),
path = require('path'),
fs = require('fs'),
formidable = require('formidable');
const { BlobServiceClient } = require('#azure/storage-blob');
var app = express();
app.set('port', (process.env.PORT || 5000));
// Tell express to serve static files from the following directories
app.use(express.static('public'));
app.use('/uploads', express.static('uploads'));
app.listen(app.get('port'), function() {
console.log('Express started at port ' + app.get('port'));
});
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.post('/upload', function(req, res) {
const connectionString = 'DefaultEndpointsProtocol=https;AccountName=account-name;AccountKey=account-key;EndpointSuffix=core.windows.net;';
const container = 'container-name';
let form = new formidable.IncomingForm();
form.keepExtensions = true;
form.uploadDir = './public/static/uploads';
form.parse(req, async function (err, fields, files) {
});
form.on('file', async (name, file) => {
const blobServiceClient = await BlobServiceClient.fromConnectionString(connectionString);
const containerClient = await blobServiceClient.getContainerClient(container);
const blobName = file.name;
const contentType = file.type;
const filePath = file.path;
console.log(file);
const blockBlobClient = containerClient.getBlockBlobClient(blobName);
const uploadBlobResponse = await blockBlobClient.uploadFile(file.path);
console.log(uploadBlobResponse);
});
});
Related
I am trying to upload a file to the API, sort the numbers and then return the result in another text file that is available to download. I upload the file, and when I start the calculation I get the Internal Server Error. The API is running on port 3000 and I start the React App.js on port 3001.
Is there something I'm doing wrong?
This is the API's app.js:
const express = require('express');
const multer = require('multer');
const bodyParser = require('body-parser');
const fs = require('fs');
const app = express();
const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.use(bodyParser.text({ type: 'text/plain' }));
app.post('/sort', upload.single('inputFile'), (req, res) => {
console.log(req.file)
const input = req.file.buffer.toString().split('\n').map(Number);
const result = input.sort((a, b) => b - a);
const resultText = result.join('\n');
fs.writeFile('result.txt', resultText, (err) => {
if(err) throw err;
res.send('File succesfully sorted!');
});
res.set('Content-Type', 'text/plain');
res.send(resultText);
});
app.listen(3000, () => {
console.log('API is listening on port 3000');
});
This is the React App.js:
const [inputFile, setInputFile] = useState(null);
const [result, setResult] = useState(null);
const [processingTime, setProcessingTime] = useState(null);
const handleFileUpload = (event) => {
setInputFile(event.target.files[0]);
};
const startCalculation = async (event) => {
event.preventDefault();
const startTime = performance.now();
const formData = new FormData();
formData.append('inputFile', inputFile);
console.log(inputFile)
const response = await fetch("http://localhost:3000/sort", {
method: 'POST',
body: formData,
mode: 'no-cors',
});
const data = await response.text();
console.log(data);
setResult(data);
setProcessingTime(performance.now() - startTime);
};
const handleDownload = (event) => {
event.preventDefault();
const file = new Blob([result], {
type: 'text/plain'
});
const fileURL = URL.createObjectURL(file);
const link = document.createElement('a');
link.href = fileURL;
link.download = 'result.txt';
link.click();
};
The issue is on the client you are setting the input name to inputFile, however, on the backend you are telling Multer that the input name is myFile.
Change from this:
upload.single("myFile")
To this:
upload.single("inputFile")
Please I'm new to Nodejs and I'm trying to create an image uploader that will upload files to my server using Nodejs and multer, but the problem is in getting the image back to be displayed in my angular app.
This is the backend code:
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const app = express();
var corsOptions = {
origin: "*",
optionsSuccessStatus: 200,
}
app.use(cors(corsOptions));
app.use(express.static('uploads'));
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "uploads");
},
filename: function (req, file, cb) {
cb(null, `${Date.now()}_${file.originalname}`);
},
})
const upload = multer({ storage });
app.post('/file', upload.single('file'), (req, res) => {
const file = req.file;
if (file) {
res.json(file);
} else {
throw new Error('File upload unsuccessful')
}
})
const port = 3000;
app.listen(port, () => console.log(`Server running on port ${3000}`));
This is my app.html code:
<input type="file" name="image" (change)="upload($event)">
This is my app.ts code:
upload(event: any) {
const file = event.target.files[0];
const formdata = new FormData();
formdata.append('file', file)
this.httpClient.post('http://localhost:3000/file', formdata)
.subscribe((data) => {
console.log(data);
},
(error) => {
console.log(error)
})
Please help me retrieve the image so that I can use it in my angular app. Thank you.
There are two ways you can achieve this. Both the approaches have their own pros and cons.
Store the image locally and send the URL back to the browser.
if (req.files) {
const fileNames = [];
for (let i = 0; i < req.files.length; i++) {
const file = req.files[i];
const relPath = "your/img/path";
const dirName = path.join(BASE_APP_PATH, relPath);
const relFileName = path.join(
relPath,
`${i + 1}_${file.originalname.replace(",", "")}`
);
const img_location = `${dirName}/${
i + 1
}_${file.originalname}`;
if (!fs.existsSync(dirName)) fs.mkdirSync(dirName, { recursive: true });
fs.writeFileSync(img_location, file.buffer, {});
fileNames.push(relFileName);
}
}
Get the image and send back base64 to the browser.
const encoded = req.files[0].buffer.toString('base64')
I am trying to upload, save and display images in MongoDB using express server and GridFS.
Data is sent with FormData with vue.js on the front-end and that part works well.
Images are getting to the server, and it stores that data, but I can not display the uploaded image, instead I get this
It displays the Image in some encoding I guess.
Here is server code for receiving, storing and displaying images.
const express = require('express')
const mongodb = require('mongodb')
const path = require('path')
const crypto = require('crypto')
const mongoose = require('mongoose')
const multer = require('multer')
const GridFsStorage = require('multer-gridfs-storage')
const Grid = require('gridfs-stream')
const methodOverride = require('method-override')
const config = require('../../config/keys')
const morgan = require('morgan')
const images = express()
// MIDDLEWARE
images.use(methodOverride('_method'))
// images.use(morgan("default"))
// MONGO URI
const mongoURI = config.dbUrl
// CREATE MONGO CONNECTION
const conn = mongoose.createConnection(mongoURI)
// INITALIZE GRID FS
let gfs
conn.once('open', () => {
gfs = Grid(conn.db, mongoose.mongo)
gfs.collection('uploads')
})
// CREATING STORAGE ENGINE
const storage = new GridFsStorage({
url: mongoURI,
file: (req, file) => {
return new Promise((resolve, reject) => {
const filename = file.originalname
const fileInfo = {
filename,
bucketName: 'uploads'
}
resolve(fileInfo)
})
}
})
const upload = multer({ storage })
// POST REQUEST UPLOADS FILE TO DATABASE
images.post('/', upload.single('image'), (req, res) => {
console.log('POST REQUEST TO HTTP://LOCALHOST:3000/API/BACKEND/IMAGES/')
res.json(req.file)
console.log(req.file)
})
// GET REQUEST
images.get('/', async (req, res) => {
console.log('GET REQUEST TO HTTP://LOCALHOST:3000/API/BACKEND/IMAGES/')
const posts = await load();
res.send(await posts.find({}).toArray())
})
images.get('/:filename', (req, res) => {
gfs.files.findOne({ filename: req.params.filename }, (err, file) => {
// Check if file
if (!file || file.length === 0) {
return res.status(404).json({
err: 'No file exists'
});
}
// Check if image
if (file.contentType === 'image/jpeg' || file.contentType === 'image/png') {
// Read output to browser
const readstream = gfs.createReadStream(file.filename);
readstream.pipe(res);
} else {
res.status(404).json({
err: 'Not an image'
});
}
});
});
// LOADING FUNCTION FOR MONGODB
async function load() {
const client = await mongodb.MongoClient.connect
(`${config.dbUrl}`, {
useNewUrlParser: true,
useUnifiedTopology: true
});
return client.db('myCluster').collection('uploads.files');
}
module.exports = images
Every help would be nice and much appreciated! Thanks! :)
I am uploading a file using this form and by the time xhr submits it the server does not recognize req.xhr === true, as such I can not process the file upload. What am I missing?
<form encType="multipart/form-data" method="post">
<Button onClick={(event)=>startUploadFile(event)} type="button">Upload</Button>
<input type="file" name="file" id="file" multiple="multiple" onChange={onChangeHandler} />
</form>
Client side
const [upFile, setUpFile] = useState('')
const onChangeHandler = event => {
setUpFile(event.target.files[0]);
}
const startUploadFile = e => {
setSpin('visible')
setMsg(`Uploading Media ...`)
e.preventDefault()
const data = new FormData()
data.append('file', upFile)
var formData = new FormData();
var xhr = new XMLHttpRequest();
var onProgress = function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded/e.total)*100;
console.log('percentage = ' + percentComplete)
}
};
formData.append('files', upFile); // this is a state object set onChange
xhr.open('post', '/upload', true);
xhr.addEventListener('error', onError, false);
xhr.addEventListener('progress', onProgress, false);
xhr.send(formData);
xhr.addEventListener('readystatechange', onReady, false);
}
Server side
const express = require('express'),
app = express.Router(),
cors = require('cors'),
fs = require('fs-extra');
app.use(cors())
app.post('/uploadFile', (req, res) => {
if (req.xhr || req.headers.accept.indexOf('json') > -1) {
// not accepted (req.xhr is false always) why?
}
});
In expressjs you need to install multer or formidable package to upload file from client side application then you will receive your file in req.files object.
Thanks to #Nikas for the remarks - here is working solution if anyone needs it or something like it.
For the client side:
const startUploadFile = e => {
setSpin('visible')
setMsg(`Uploading Media ...`)
e.preventDefault()
const data = new FormData()
data.append('file', upFile)
var formData = new FormData();
var xhr = new XMLHttpRequest();
var onProgress = function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded/e.total)*100;
console.log('% uploaded:' + percentComplete)
}
};
var onReady = function(e) {
console.log('ready')
};
var onError = function(err) {
console.log('something went wrong with upload');
};
formData.append('files', upFile); // this is a state object set onChange
xhr.open('post', '/media/uploadFile', true);
xhr.addEventListener('error', onError, false);
xhr.addEventListener('progress', onProgress, false);
xhr.send(formData);
xhr.addEventListener('readystatechange', onReady, false);
}
server side:
const express = require('express'),
app = express.Router(),
cors = require('cors'),
fs = require('fs-extra'),
formidable = require('formidable');
app.post('/uploadFile', (req, res) => {
const path = './client/public/upload/';
var form = new formidable.IncomingForm();
form.uploadDir = path;
form.encoding = 'binary';
form.parse(req, function(err, fields, files) {
if (err) {
console.log(err);
res.send('upload failed')
} else {
var oldpath = files.files.path;
var newpath = path + files.files.name;
fs.rename(oldpath, newpath, function (err) {
if (err) throw err;
res.send('complete').end();
});
}
});
})
I have a problem uploading image file to my mongoDB using gridFS, but for some reason this doesn't work
this is my code :
const config = require("config");
const express = require("express");
const router = express.Router();
const dbURI = config.get("mongoURI");
const multer = require("multer");
const crypto = require("crypto");
const path = require("path");
const GridFsStorage = require("multer-gridfs-storage");
var storageImage = new GridFsStorage({
url: dbURI,
file: (req, file) => {
return new Promise((resolve, reject) => {
crypto.randomBytes(16, (err, buf) => {
if (err) {
return reject(err);
}
const filename = buf.toString("hex") + path.extname(file.originalname);
const fileInfo = {
filename: filename,
bucketName: "user_images"
};
resolve(fileInfo);
});
});
}
});
const uploadImage = multer({ storageImage });
router.post("/uploadImage", uploadImage.single("userImg"), (req, res) => {
console.log("uploading");
console.log(req.file);
res.json({ msg: "file uploaded successfully" });
});
module.exports = router;
when I console .log req.file I get undefined, does anyone know what the problem is?
I found the problem:
const uploadImage = multer({ storageImage });
it should be
const uploadImage= multer({storage:storageImage});