I'm trying to submit a form from Angular 7 that has text and images to a Node backend using Multer as my middleware and Express.json() as my bodyParser. The form data is there on the frontend submission and the text data is there in the backend but the image fields are empty {}. I've tried using bodyParse.json() and thre results are the same.
Here is my app.js file
const express = require('express');
// const bodyParser = require('body-parser');
const adminController = require('./controllers/admin');
const path = require('path');
const cors = require('cors')
const app = express()
const FORM_URLENCODED = 'multipart/form-data';
app.use(cors())
... my mongodb connection string ...
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*')
res.setHeader('Access-Control-Allow-Headers', 'Origin, Content-Type, X- Auth-Token')
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS')
next();
});
// const bp = bodyParser.json()
// console.log('TCL: bp', bp);
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(express.static(path.join(__dirname, 'images')));
// req is not defined?!?!?!?!?
app.use(() => {
if (req.headers['content-type'] === FORM_URLENCODED) {
let body = '';
req.on('data', chunk => {
body += chunk.toString(); // convert Buffer to string
});
req.on('end', () => {
console.log(body);
res.end('ok');
});
}
})
// -- multer
const multer = require('multer');
const crypto = require("crypto");
const imgDir = 'images';
const imgStorage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'images')
},
filename: function(req, file, callback) {
crypto.pseudoRandomBytes(16, function(err, raw) {
if (err) return callback(err);
callback(null, raw.toString('hex') +
path.extname(file.originalname));
});
}
});
const fileFilter = ((req, file, cb) => {
// accept image only
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/)) {
return cb(new Error('Only image files are allowed!'), false);
}
cb(null, true);
});
const upload = multer({dest: imgDir, storage: imgStorage, fileFilter:
fileFilter, limits: {fileSize: 16000} }).array('image',20);
// app.use(
// upload.fields([
// { name: 'mainImg', maxCount: 1 },
// { name: 'image', maxCount: 5 },
// ])
// );
// -- end multer
app.post('/admin/add-product', function (req, res, next) {
var path = '';
upload({dest: imgDir, storage: imgStorage, fileFilter: fileFilter,
limits: {fileSize: 16000} });
path = req.file.path;
/// path is not defined!?!?!?!?
return res.send("Upload Completed for "+path);
}, adminController.postAddProduct);
const userRoutes = require('./routes/user');
app.use('/user', userRoutes);
module.exports = multer;
module.exports.imgStorage = imgStorage;
module.exports = app;
Angular form
<form [formGroup]="prodForm" (ngSubmit)="onSubmit()" enctype="multipart/form-data">
<div class="col-md-4">
<label for="title"> <span class="required">*</span>Title: </label>
<mat-form-field>
<input class="form-control" matInput type="text" formControlName="title" #prodTitle />
<mat-error *ngIf="prodForm.get('title').invalid">Please enter a title</mat-error>
</mat-form-field>
</div>
<div class="col-md-4">
<div class="col-md-5">
<button class="btn btn-success" mat-stroked-button type="button" (click)="filePicker.click()">
Pick Image
</button>
<input type="file" #filePicker name="image" (change)="onImagePicked($event)" />
</div>
<div class="image-preview col-md-7" *ngIf="imgSrc !== '' && imgSrc">
<img [src]="imgSrc" alt="{{ prodTitle.value }}" />
</div>
</div>
<div class="col-md-12 sectButtons">
<button class="btn btn-success" (click)="onShowStep2()">Next Step</button>
<div class="clear"></div>
</div>
Angular form output
image: File {name: "some-image.jpg", lastModified: 1552012800142,
lastModifiedDate: Thu Mar 07 2019 21:40:00 GMT-0500 (Eastern Standard Time), webkitRelativePath: "", size: 42381, …}
title: "some title"
Node controller output
TCL: exports.postAddProduct -> req.body { _id: '',
title: 'some title',
image: {}, }
TCL: exports.postAddProduct -> files undefined
What am I missing? I've spent way too much time trying to figure this out.
req is not defined becuase you've not defined the req object. It is not a valid express middleware. change to
// next is optional
app.use((req, res, next) => {
if (req.headers['content-type'] === FORM_URLENCODED) {
let body = '';
req.on('data', chunk => {
body += chunk.toString(); // convert Buffer to string
});
req.on('end', () => {
console.log(body);
res.end('ok');
});
}
})
Related
I am trying to build a website that allows a user to add a collectors item to the website. It has a form that requires certain fields to be filled out (I chose Funko Pop's). I have got everything working for the exception of having the image uploaded to the index.hbs page and into the MongoDB database.
Can anyone help? I think I am very close, but my terminal is throwing a Mongoose validator error (I'll show this at the end of the post)
The image seems to be sent to my "uploads" folder successfully, it just seems that there is some miscommunication happening with MongoDB because mongoose isn't happy.
Would anyone know what the issue is? And know how to fix it?
Thank you all for the help!
Here is my code:
Collection.js Controller/Route:
const express = require('express')
const router = express.Router()
const Collection = require('../models/collection')
const globalAuthFunction = require('./authenticationFunction')
const multer = require('multer')
// storage for multer
const storage = multer.diskStorage({
destination: function(err, file, callback){
callback(null, './public/uploads')
},
filename: function(req, file, callback){
callback(null, Date.now() + file.originalname)
}
})
const upload = multer({
storage: storage
})
router.get('/', (req, res) => {
Collection.find((err, collections) => {
if (err) {
console.log(err);
}
else {
res.render('collections/index', {
title: 'Collections',
collections: collections,
user: req.user
})
}
})
})
router.get('/create', globalAuthFunction.isAuthenticated, (req, res) => {
Collection.find((err, collections) => {
if(err){
console.log(err)
}
else{
res.render('collections/create', {
title: 'Add Employer',
collections: collections,
user: req.user,
})
}
})
})
router.post('/create', upload.single('image'), globalAuthFunction.isAuthenticated, (req, res) => {
Collection.create(req.body, (err, newCollection) => {
if (err) {
console.log(err)
}
else {
res.redirect('/collections')
}
})
})
router.get('/edit/:_id', globalAuthFunction.isAuthenticated, (req, res) => {
Collection.findById(req.params._id, (err, collection) => {
if(err){
console.log(err)
}
else{
res.render('collections/edit', {
title: 'Funko Pop Details',
collection: collection,
user: req.user,
image: req.file.filename
})
}
})
})
router.post('/edit/:_id', globalAuthFunction.isAuthenticated, (req, res) => {
Collection.findByIdAndUpdate({ _id: req.params._id}, req.body, null, (err, collection) =>{
if(err){
console.log(err)
}else{
res.redirect('/collections')
}
})
})
router.get('/delete/:_id', globalAuthFunction.isAuthenticated, (req, res) => {
Collection.deleteOne({ _id: req.params._id}, (err) => {
if(err){
console.log(err)
}
else{
res.redirect('/collections')
}
})
})
module.exports = router
My create.hbs:
<h1>Add a Funko Pop to Your Collection</h1>
<form method="post" enctype="multipart/form-data">
<fieldset>
<label for="character" class="col-2">Character: *</label>
<input name="character" id="character" required />
</fieldset>
<fieldset>
<label for="mediaTitle" class="col-2">Move or Television Name: *</label>
<input name="mediaTitle" id="mediaTitle" required />
</fieldset>
<fieldset>
<label for="category" class="col-2">Category: *</label>
<input name="category" id="category" required />
</fieldset>
<fieldset>
<label for="popNumber" class="col-2">Funko Pop Number: *</label>
<input name="popNumber" id="popNumber" required />
</fieldset>
<fieldset>
<label for="specialtySticker" class="col-2">Specialty Sticker: </label>
<input name="specialtySticker" id="specialtySticker" placeholder="N/A if there is no sticker"/>
</fieldset>
<fieldset>
<label for="price" class="col-2">Price: *</label>
<input name="price" id="price" required />
</fieldset>
<fieldset>
<label for="releaseYear" class="col-2">Funko Pop Release Year: *</label>
<input name="releaseYear" id="releaseYear" required />
</fieldset>
<fieldset>
<label for="image" class="col-2">Upload Image:</label>
<input type="file" name="image" id="image" value="image"/>
</fieldset>
<button class="btn btn-primary offset-2">Save</button>
</form>
My index.hbs:
<h1>Funko Pop Collection</h1>
{{#if user}}
<a class="btn btn-secondary" href="/collections/create">Add a Funko Pop</a>
{{/if}}
<section class="row">
{{#each collections}}
<div class="card col-2 m-2">
<div class="card-body text-center h-100">
<img src="{{this.image}}"/>
<h5 class="card-title">{{this.character}}</h5>
<p>Funko Pop Title: {{this.mediaTitle}}</p>
<p>Category: {{this.category}}</p>
<p>Pop Number: {{this.popNumber}}</p>
<p>Specialty Sticker: {{this.specialtySticker}}</p>
<p>Value: ${{this.price}}</p>
<p>Year:{{this.releaseYear}}</p>
{{#if ../user}}
<div>
Edit
Delete
</div>
{{/if}}
</div>
</div>
{{/each}}
</section>
My app.js:
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./controllers/index');
var usersRouter = require('./controllers/users');
const collections = require('./controllers/collections')
const auth = require('./controllers/auth')
const passport = require('passport')
const session = require('express-session')
const multer = require('multer')
var app = express();
// database connection to MongoDB
if (process.env.NODE_ENV !== 'production'){
require('dotenv').config()
}
app.use(session ({
secret: process.env.PASSPORT_SECRET,
resave: true,
saveUninitialized: false
}))
app.use(passport.initialize())
app.use(passport.session())
const User = require('./models/user')
passport.use(User.createStrategy())
// let passport read/write user data to/from session vars
passport.serializeUser(User.serializeUser())
passport.deserializeUser(User.deserializeUser())
const google = require('passport-google-oauth20').Strategy
passport.use(new google({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_CALLBACK_URL
},
(accessToken, refreshToken, profile, done) => {
User.findOrCreate({ oauthId: profile.id}, {
username: profile.displayName,
oauthProvider: 'Google',
created: Date.now()
}, (err, user) => {
return done(err, user)
})
}
))
const mongoose = require('mongoose');
const { access } = require('fs');
mongoose.connect(process.env.DATABASE_URL)
.then(
(res) => {
console.log('Connected to MongoDB')
}
).catch(() => {
console.log('Cannot connect to MongoDB')
})
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'hbs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/collections', collections)
app.use('/auth', auth)
app.use('/', indexRouter);
app.use('/users', usersRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
This is what my terminal is showing when I try and create a new Funko Pop Object in MongoDB:
It is saying there is an incorrect path or something. The image is uploading correctly to my "uploads" folder, it just seems to be having an issue with the database connection.
I thought it may be that the image type in my model was wrong, so I switched it to "Buffer" instead of "String", but it still showed me the same exact error. I have watched several tutorials, but they have set it up completely different than mine.
They seem to be using a something like this in there "post" function in there controllers:
let connection = new Connection({
title: request.body.title,
img: request.file.filename
}
I do not know enough about handlebars, MongoDB or Multer to be able to diagnose the issues that is happening. Any help would be greatly appreciated.
Thank you!
since you are using multer, change it from request.file.filename to request.file.path
I am able to upload image using Postman in DB and in the destination folder but when I am trying to upload it via frontend(React Typescript) it shows undefined for the image. Can anyone have a look at it and let me know what I am doing wrong. I am stuck at this for hours.
React Typescript Code
function ImageUpload() {
const [images, setImages] = useState<File>();
const handleInput = (event: ChangeEvent<HTMLInputElement>) => {
// setImages(event.target.files[0])
const fileList = event.target.files;
// approach 1
if (!fileList) return;
else {
console.log(fileList[0].type)
setImages(fileList[0]);
}
}
const handleSubmit = (event: FormEvent) => {
event.preventDefault()
console.log("Before axios")
console.log(images)
const config = {
headers: {
"content-Type": "multipart/form-data"
}
};
//approach 1
Axios.post("http://localhost:3003/imageInsert", images).then(() => {
alert("Image Inserted 1")
}).catch(error => {
console.log(error.response)
})
}
return (
<>
<div>
<form onSubmit={handleSubmit} encType="multipart/form-data">
<div>
<label>Image </label><br /><br />
<input type="file" name='image' accept='image/*' onChange={handleInput} />
</div>
<br />
<button type='submit'>Submit</button>
</form>
</div>
</>
)
}
export default ImageUpload
Express Code
const express = require('express')
const app = express()
const mysql = require('mysql2')
const bodyParser = require('body-parser')
const cors = require('cors')
const path = require('path')
const multer = require('multer')
const db = mysql.createPool({
host: 'localhost',
user: 'xyz',
password: 'xyz',
database: 'demo',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
})
app.use(cors())
app.use(express.json())
const fileStorageEngine = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "images");
},
filename: (req, file, cb) => {
console.log(file)
cb(null, Date.now() + "--" + file.originalname);
},
})
const upload = multer({ storage: fileStorageEngine })
app.post("/imageInsert", upload.single('image'), (req, res) => {
console.log(req.file) // shows undefined here
const filename = req.file.filename
const query = `insert into image (img) values ('${filename}')`;
db.query(query, (error, result) => {
res.send("Image Inserted in DB" + Date.now())
console.log("Image Inserted")
})
})
app.listen(3003, () => {
console.log("running on port 3003")
});
Hello there and Happy New Year to all:)
In the .ejs file there are two forms: one to get text inputs, another for file upload:
<div class="sectionContainer">
<div class="tour-upload">
<form method="post" action="/office/new-tour">
<label for="tourHeading">Tour Heading <em>*</em></label
><input
id="tourHeading"
name="tourHeading"
required=""
type="text"
placeholder="eg May 31-June 11 (Riga, Amsterdam, Riga)"
/>
<label for="tourDescription"
>Tour Info <em>*</em></label
>
<textarea
id="tourDescription"
name="tourDescription"
required=""
placeholder="Add tour details"
rows="4"
></textarea>
<button id="add-tour">Add Tour</button>
</form>
<form action="office/add-score" method="POST" enctype="multipart/form-data">
<div class="custom-file mb-3">
<input type="file" name="file" id="file" class="custom-file-input">
<label for="file" class="custom-file-label">Choose File</label>
</div>
<input type="submit" value="Submit" class="btn btn-primary btn-block">
</form>
</div>
In order to get get the info from the form where action="/office/new-tour", I can use this code in app.js:
Setup:
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const http = require("http");
const url = require("url");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
And the actual code with logic:
app.get("/office", function (req, res) {
res.render("office");
});
//Mongo DB connection for form data saving
mongoose.connect("mongodb://localhost:27017/kbTourAppDB", {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const ToursSchema = {
tour: { tourHeading: String, tourDescription: String },
program: { tourProgram: String, tourProgramDescription: String },
};
const Tour = mongoose.model("Tour", ToursSchema);
app.post("/office/new-tour", (req, res) => {
const addedTour = new Tour({
tour: {
tourHeading: req.body.tourHeading,
tourDescription: req.body.tourDescription,
},
program: {
tourProgram: req.body.tourProgram,
tourProgramDescription: req.body.tourProgramDescription,
},
//add author when the login page is ready
});
addedTour.save(function (err) {
if (err) {
console.log("err: ", err);
} else {
console.log("sucess added new Tour to db");
}
});
res.redirect("/office");
});
app.listen(3000, function () {
console.log("listening on port 3000");
});
The code above works successfully for getting those text inputs to database, however I can't use it to upload the files to the database (mostly jpeg and pdf). For that I found a solution with Multer and GridFS.
Set up:
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require("mongoose");
const path = require("path");
const crypto = require("crypto");
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
const Grid = require("gridfs-stream");
const methodOverride = require("method-override");
const http = require("http");
const url = require("url");
const app = express();
app.use(bodyParser.json());
app.use(methodOverride("_method"));
app.use(bodyParser.urlencoded({ extended: true }));
app.set("view engine", "ejs");
And code here:
app.get("/office", function (req, res) {
res.render("office");
});
const conn = mongoose.createConnection("mongodb://localhost:27017/kbTourAppDB");
let gfs;
conn.once("open", () => {
gfs = Grid(conn.db, mongoose.mongo);
gfs.collection("uploads");
});
//create storage object
const storage = new GridFsStorage({
url: "mongodb://localhost:27017/kbTourAppDB",
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 originalFileName = file.originalname;
const fileInfo = {
filename: filename,
bucketName: "uploads",
};
resolve(fileInfo);
});
});
},
});
const upload = multer({ storage });
app.post("/office/add-score", upload.single("file"), (req, res) => {
res.redirect("/office");
});
app.listen(3000, function () {
console.log("listening on port 3000");
});
This code works for uploading files to database..
How can I write a code that will allow me to do both on the same page: upload the text input data to the MongoDB collection 'tour-data' for example, and the files from the other form to the collection 'uploads'..
Thank you very much for your help!
Actually when using multer, the text-fields from your form should still be populated under req.body.<field-name>, so you should be able to just use your initial code for inserting the tour-data:
app.post("/office/add-score", upload.single("file"), (req, res) => {
const addedTour = new Tour({
tour: {
tourHeading: req.body.tourHeading,
tourDescription: req.body.tourDescription,
},
program: {
tourProgram: req.body.tourProgram,
tourProgramDescription: req.body.tourProgramDescription,
}
});
addedTour.save(...)
});
I am using ReactJS to create a simple sendMail app. And the problem is I do not receive req.file in my server (ExpressJS).
myForm.js
<form className="content-container mailing-form" encType='multipart/form-data'>
<h1>Let's see if it works</h1>
<div>
<label>Mail: </label>
<input type="text" value={this.state.email} onChange={this.handleChangeToMail} style={{ width: '50%' }} />
</div>
<div>
<label>Subject: </label>
<input type="text" value={this.state.subject} onChange={this.handleChangeSubject} style={{ width: '50%' }} />
</div>
<div>
<textarea
id="test"
name="test-mailing"
onChange={this.handleChangeFeedback}
placeholder="Post some lorem ipsum here"
required
value={this.state.feedback}
style={{ width: '100%', height: '150px' }}
/>
</div>
<div> //Here is my input file
<label>File: </label>
<input
type="file"
onChange={this.handleChangeFile}
name="myFile"
/>
<img src="" id="preview" alt="Image preview..."></img>
</div>
<input type="button" value="Submit" onClick={this.handleSubmit} />
</form>
handleChangeFile
handleChangeFile = e => {
e.preventDefault();
let fReader = new FileReader();
let file = '';
fReader.readAsDataURL(e.target.files[0]);
fReader.onloadend = (event) => {
document.getElementById('preview').src = event.target.result;
file = event.target.result;
this.setState(() => ({ file }));
console.log(this.state.file);
}
}
handleSubmit
handleSubmit = e => {
let formData = new FormData();
formData.append('name', this.state.name);
formData.append('email', this.state.email);
formData.append('feedback', this.state.feedback);
formData.append('subject', this.state.subject);
formData.append('file', this.state.file);
const config = {
headers: {
"Content-Type": "multipart/form-data",
"Accept": "application/json",
"type": "formData"
}
}
const url = "http://localhost:8081/send";
axios
.post(
url,
formData,
config
)
.then(res => console.log(res.data))
.catch(err => console.log('Error: ', err));
}
Server.js
const nodemailer = require('nodemailer');
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
var multer = require('multer');
const app = express();
const port = 8081;
app.use(cors());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next(); });
app.get('/', (req, res) => res.send('......!'));
const storage = multer.diskStorage({
destination: './uploads',
filename: function (req, file, cb) {
cb(null, file.fieldname + Date.now());
} })
const uploads = multer({
storage: storage
}).single('myFile');
app.post('/send', uploads, (req, res) => {
if (req.file) {
res.send('Uploaded !');
} else {
res.send('Fail !!');
}
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
And I keep receive "Fail !!" message on my form.
I have tried to add any solution I can find. I know it can be some mess in there, sorry for that :') !
I solved my problem, thanks to this thread : Convert Data URI to File then append to FormData
I add a function to convert file from weird base64 string to Blob type before send it to server. Then my server can receive req.file !!
1.make your uploads folder as static folder
app.use(express.static('uploads'));
2.change your destination like below
destination : function( req , file , cb ){
cb(null,'./uploads');
},
append your file as myFile in frontend
formData.append('myFile', this.state.file);
I am new in node. I just want to pass value in POST request with something like uploading of file. here is my sample code:
var express = require('express');
var app = express();
var fs = require("fs");
var bodyParser = require('body-parser');
var urlencodedParser = bodyParser.urlencoded({ extended: false });
var multer = require('multer');
var upload = multer({ dest: '/tmp/'});
app.use(express.static('public'));
app.post('/process_post', urlencodedParser, function (req, res) {
console.log(req.files.file.name);
var file = __dirname + "/" + req.files.file.name;
fs.readFile( req.files.file.path, function (err, data) {
fs.writeFile(file, data, function (err) {
if( err ){
console.log( err );
}else{
response = {
message:'Save successfully',
first_name:req.body.firstname,
last_name:req.body.lastname,
filename:req.files.file.name
};
}
console.log( response );
res.end( JSON.stringify( response ) );
});
});
})
HTML:
<html>
<body>
<form action="http://127.0.0.1:8081/process_post" method="POST" enctype="multipart/form-data">
First Name: <input type="text" name="firstname">
<br>
Last Name: <input type="text" name="lastname">
<br>
Picture: <input type="file" name="file" size="50" />
<br>
<input type="submit" value="Submit">
</form>
</body></html>
The req.files is always undefine.
Thanks in advance!
You're requireing multer, but never configuring or using it. from the docs:
var multer = require('multer')
var upload = multer({ dest: 'uploads/' })
app.post('/photos/upload', upload.array('photos', 12), function (req, res, next) {
// req.files is array of `photos` files
// req.body will contain the text fields, if there were any
})