I am developing REST API using nodeJS, express, mongoose etc with mongodb. I am uploading file and saving it to a folder using multer. Now I want to save the path of the file to a mongodb document.
However, I am saving data to mongodb using mongoose schema. First I created the model. When a post request is made, I read it using bodyParser (req.body) and save this object by creating new instance or more shortcut.
Product.create(req.body).then(function(product){
res.send(product);
}).catch(next);
But when I am using multer to upload a file and want to save the path to the model I cant do it using create() function. So what is the way ??
In the MongoDB we can store single or multiple images. For storing multiple images I am using productPictures array.
1: First, create the Product model
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
productPictures: [{ img: { type: String } }],
});
module.exports = mongoose.model('Product', productSchema);
2: Create the product controller
const Product = require('../models/product');
exports.createProduct = (req, res) => {
const { name} = req.body;
let productPictures = [];
if (req.files.length > 0) {
productPictures = req.files.map((file) => {
return { img: file.filename };
});
}
const product = new Product({
name,
productPictures,
});
product.save((error, product) => {
if (error) return res.status(400).json({ error });
if (product) {
res.status(201).json({ product });
}
});
};
3: Create products route file
I am using nanoid to generate a unique name for images
Create uploads folder inside src folder
const express = require('express');
const path = require('path');
const multer = require('multer');
const { nanoid } = require('nanoid');
const { createProduct } = require('../controllers/product');
const router = express.Router();
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, path.join(path.dirname(__dirname), 'uploads'));
},
filename: function (req, file, cb) {
cb(null, nanoid() + '-' + file.originalname);
},
});
const upload = multer({ storage: storage });
router.post(
'/products/create',
upload.array('productPicture'), // for storing single image : upload.single('productPicture')
createProduct
);
module.exports = router;
4: Create server.js file
const env = require('dotenv');
const express = require('express');
const mongoose = require('mongoose');
const app = express();
// routes
const productRoutes = require('./routes/product');
env.config();
mongoose
.connect(`${process.env.MONGO_URI}`, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
.then(() => {
console.log('Database connected');
});
// Body parser (You can you **body-parser**)
app.use(express.json());
app.use('/api', productRoutes);
app.listen(process.env.PORT, () => {
console.log(`Server is running on port ${process.env.PORT}`);
});
5: Finally you can create product using postman
Here you can upload the image into some destination you want, this is for the reference for more details information including how to access stored images in Mongodb and you can see the documentation here about multer.
express = require('express')
, router = express.Router()
, MongoClient = require('mongodb').MongoClient
, ObjectId = require('mongodb').ObjectId
, fs = require('fs-extra')
// Your mongodb or mLabs connection string
, url = 'mongodb://username:password#yourinstanced.mlab.com:29459/yourdb'
, multer = require('multer')
, util = require('util')
, upload = multer({limits: {fileSize: 2000000 },dest:'/uploads/'})
app.post('/profile', upload.single('avatar'), function (req, res, next) {
// req.file is the `avatar` file
// req.body will hold the text fields, if there were any
if (req.file == null) {
// If Submit was accidentally clicked with no file selected...
)} else {
MongoClient.connect(url, function(err, db){
// this landing will give you any option of file information that you can collect
console.log('landing here', req.file)
// read the img file from tmp in-memory location
var newImg = fs.readFileSync(req.file.path);
// encode the file as a base64 string.
var encImg = newImg.toString('base64');
// define your new document
var newItem = {
contentType: req.file.mimetype,
size: req.file.size,
name: req.file.originalname,
path: req.file.path
};
db.collection('yourcollectionname')
.insert(newItem, function(err, result){
if (err) { console.log(err); };
var newoid = new ObjectId(result.ops[0]._id);
fs.remove(req.file.path, function(err) {
if (err) { console.log(err) };
res.send(newItem);
});
});
});
}});
If using multer you will get uploaded files path in req.file.path and you just need to save that in your database.
Related
EDIT
I removed app.use(fileUpload());. So it finally worked.
But unfortunately in the folder images of the backend I only get these files c43jnfeh734hdfudf.
For this reason, nothing is displayed in the frontend.
const imagePath = req.file.path
const description = req.file.originalname
console.log(imagePath)
console.log(description)
images\c43jnfeh734hdfudf
empty
I have a problem. I would like to save images with a fixed URL on my server.
I found the following code snippet, but unfortunately it doesn't work.
I get the following error in the backend: 'TypeError: Cannot read property 'path' of undefined'.
The following values are 'undefined'. const imagePath = req.file.path const description = req.body.description
How can I save an image as a URL on the server?
Here is the tutorial, where I found the code snippet https://github.com/meech-ward/sammeechward.com_mdx/blob/master/content/articles/uploading-images-express-and-react/index.mdx
React
import { useState } from 'react'
import axios from 'axios'
export default function App() {
const [file, setFile] = useState()
const [description, setDescription] = useState("")
const [image, setImage] = useState()
const submit = async event => {
event.preventDefault()
const formData = new FormData()
formData.append("image", file)
formData.append("description", description)
const result = await axios.post('/api/images', formData, { headers: {'Content-Type': 'multipart/form-data'}})
setImage(result.data.imagePath)
}
return (
<div className="App">
<form onSubmit={submit}>
<input
filename={file}
onChange={e => setFile(e.target.files[0])}
type="file"
accept="image/*"
></input>
<input
onChange={e => setDescription(e.target.value)}
type="text"
></input>
<button type="submit">Submit</button>
</form>
{ image && <img src={image}/>}
</div>
)
}
Backend
const express = require('express')
const fs = require('fs')
const multer = require('multer')
const upload = multer({ dest: 'images/' })
const app = express()
// app.use('/images', express.static('images'))
app.get('/images/:imageName', (req, res) => {
// do a bunch of if statements to make sure the user is
// authorized to view this image, then
const imageName = req.params.imageName
const readStream = fs.createReadStream(`images/${imageName}`)
readStream.pipe(res)
})
app.post('/api/images', upload.single('image'), (req, res) => {
const imagePath = req.file.path
const description = req.body.description
// Save this data to a database probably
console.log(description, imagePath)
res.send({description, imagePath})
})
app.listen(8080, () => console.log("listening on port 8080"))
routes/Test.js
const express = require("express");
const router = express.Router();
module.exports = router;
const auth_util = require("../utilities/auth_util");
const pgclient = require("../app");
const multer = require('multer')
const upload = multer({ dest: 'images/' })
// app.use('/images', express.static('images'))
router.get('/images/:imageName', (req, res) => {
// do a bunch of if statements to make sure the user is
// authorized to view this image, then
const imageName = req.params.imageName
const readStream = fs.createReadStream(`images/${imageName}`)
readStream.pipe(res)
})
router.post('/api/images', upload.single('image'), (req, res) => {
console.log(req.file)
console.log(req.files)
const imagePath = req.file.path
const description = req.body.description
// Save this data to a database probably
console.log(description, imagePath)
res.send({ description, imagePath })
})
// added the lines below
const path = require("path");
router.use(express.static(path.join(__dirname, 'build')));
router.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.js
const express = require("express");
const cors = require("cors");
//const fileUpload = require("express-fileupload");
const session = require("express-session");
const { Pool } = require("pg");
const app = express();
app.use(express.json());
//app.use(fileUpload());
//------------------------------CORS settings------------------------------
var whitelist = [
"http://localhost:3000",
"http://localhost:3001",
];
var corsOptions = {
credentials: true,
exposedHeaders: ["set-cookie"],
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
// callback(null, true)
callback(new Error("Not allowed by CORS!!"));
}
},
};
app.options("*", cors(corsOptions));
const pgclient = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
module.exports = pgclient;
app.set("trust proxy", 1);
const testRoute = require("./routes/test");
app.use("/test", cors(corsOptions), testRoute);
app.get("/", cors(corsOptions), (req, res, next) => {
res.send("Welcome");
});
module.exports = app;
First of all, you need to remove express-fileupload. There is no need to use it alongside multer.
To have the correct file with an extension in specified folder, you need to change this part of your code:
remove this line:
const upload = multer({ dest: 'images/' })
change it to:
// routes/Test.js
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, 'images')
},
filename: function (req, file, cb) {
cb(null, file.originalname)
}
})
const upload = multer({ storage: storage })
For conventional and standard way to prevent overwriting the same file names, you need to change filename to this:
filename: function (req, file, cb) {
cb(null, `${Date.now()}-${file.originalname}`)
}
According to this answer, multer uses a kind of cookie in its file uploads and out of date versions of the cookie cause the file upload to fail. Try clearing your browser's cookies.
multer - req.file always undefined
Edit: here is the script working on my end with some images:
I did have to make one minor edit to get the example to work on chrome. To avoid the CORS policy, the front and back end must both be hosted at the same port. So, I added get route to statically serve the react page from the expressjs server:
const express = require('express')
const fs = require('fs')
const multer = require('multer')
const upload = multer({ dest: 'images/' })
const app = express()
// app.use('/images', express.static('images'))
app.get('/images/:imageName', (req, res) => {
// do a bunch of if statements to make sure the user is
// authorized to view this image, then
const imageName = req.params.imageName
const readStream = fs.createReadStream(`images/${imageName}`)
readStream.pipe(res)
})
app.post('/api/images', upload.single('image'), (req, res) => {
console.log(req.file)
console.log(req.files)
const imagePath = req.file.path
const description = req.body.description
// Save this data to a database probably
console.log(description, imagePath)
res.send({ description, imagePath })
})
// added the lines below
const path = require("path");
app.use(express.static(path.join(__dirname, 'build')));
app.get('/', function (req, res) {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(8080, () => console.log("listening on port 8080"))
Im trying to upload images with multer. Everything has worked before on localhost fine. Images get uploaded and i can view them from the url link the code provides. However once i have uploaded it on a server im starting to get this error : "Cannot GET /marketplace-api/rest-api/image/1650484814178.jpg".
I have not provided link to server/domains to the question in case people say thats the problem.
Here is my code:
const express = require('express');
const app = express();
const multer = require("multer");
const path = require("path");
const db = require('./db');
const cors = require('cors');
const port = 5000;
const bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true , limit: '50mb'}));
app.use(cors());
app.use("/image", express.static("image"));
let imageName = "";
const storage = multer.diskStorage({
destination: path.join("./image"),
filename: function (req, file, cb) {
imageName = Date.now() + path.extname(file.originalname);
cb(null, imageName);
},
});
const upload = multer({
storage: storage,
limits: { fileSize: 3000000 },
}).single("myImage");
app.post("/marketplace-api/rest-api/upload-image", (req, res) => {
upload(req, res, (err) => {
if (err) {
console.log(err);
} else {
return res.status(201)
.json({ url: "http://link to domains/marketplace-api/rest-api/image/" + imageName });
}
});
});
app.listen(port, () => {
console.log("server run in port", port);
});
ok, few things, 1. you dont have any get functions, which means you cant post to this location, i suggest doing this:
app.get('/', (req, res) => {
res.sendFile(__dirname + "/index.html");
});
and create a file called index.html, go on your site (localhost), and it should work, after that add a form in the html file, it should look something like this
<form action = "/marketplace-api/rest-api/upload-image" method = "POST">
<input type = "file" name="image">
<input type = "submit">
</form>
this will add a basic form where you can upload your photo.
there are a few more stuff to add / change, i will fix everything and make it a repo then send it here
I am developing a web page with node.js and express. I developed a form where users can fill their details and upload their image. The image file is expected to be stored inside a directory while the path is stored in the database.
I am using Node.js with mongodb database. The image is successfully stored in the expected directory but the path is not stored inside the database instead of the path, I found the code to store the path inside the database (/posts/${image.name} for every of the file uploaded. please how do I achieve this?
{
//Index.js
const path = require('path');
const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const Post = require('./database/models/Post');
const fileUpload = require("express-fileupload");
const app = new express();
mongoose.connect('mongodb://localhost/node-js-test-blog', { useNewUrlParser: true })
app.use(express.static('public'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload());
//the route responsible for storing the image in posts directory and the path in the database
app.post('/posts/store', (req, res) => {
const { image } = req.files
image.mv(path.resolve(__dirname, 'public/posts', image.name), (error) => {
Post.create({
...req.body,
image: '/posts/${image.name}'//this code is what I get in the database instead of the path
}, (error, post) => {
res.redirect("/");
});
})
});
app.listen(4000, () => {
console.log("application listening on port 4000")
})
}
//model
const mongoose = require('mongoose');
//collections represents entities in the application e.g users, posts, etc
//schema represents how to structure in the collections
const PostSchema = new mongoose.Schema({
description: String,
title: String,
content: String,
username: String,
image: String,
createdAt: {
type: Date,
default: new Date()
}
});
//the model itself is what will communicate with the database
const Post = mongoose.model('Post', PostSchema);
module.exports = Post;
}
//the code to display the (image) view from the database using node.js directive
<style="background-image: url('{{post.image}}')">
}
You need to use backticks when using ${}, otherwise the string is interpreted literally:
Change this:
image: '/posts/${image.name}'
To this:
image: `/posts/${image.name}`
I'm building a pretty simple API to do a basic CRUD operations on a local mongo database. The code looks fine for me but somehow the CRUD operations results on a pending request which never ends.
Here the parts of the code:
spawn.model.js (Model corresponding to database collection)
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var SpawnSchema = Schema({
Name: {
type: String,
unique: false,
required: true
}
}, { timestamps: true });
module.exports = mongoose.model('spawns', SpawnSchema);
spawn.controller.js
var Spawn = require('../models/Spawn/spawn.model');
exports.getSpawns = function(req, res){
Spawn.find({}, function(spawns){
res.send(spawns);
});
}
Here the spawn.routes.js file:
var Spawns = require('../controllers/spawn.controller');
module.exports = function(app){
app.get('/list', Spawns.getSpawns);
}
And then finally the server.js file:
var express = require('express');
var bodyParser = require('body-parser');
var properties = require('./config/properties');
var db = require('./config/database');
var app = express();
//configure bodyparser
var bodyParserJSON = bodyParser.json();
var bodyParserURLEncoded = bodyParser.urlencoded({ extended: true });
// call the database connectivity function
db();
// configure app.use()
app.use(bodyParserJSON);
app.use(bodyParserURLEncoded);
// Routes
app.get('/', function(req, res){
res.json({ message: 'Spawns API' });
});
require('./app/routes/spawn.routes')(app);
// intialise server
app.listen(properties.PORT, () => {
console.log(`Server is running on ${properties.PORT} port.`);
})
The database file on ./config is the following:
var mongoose = require('mongoose');
var dbURL = require('./properties').DB;
mongoose.Promise = global.Promise;
module.exports = function(){
mongoose.connect(dbURL, { useNewUrlParser: true }, function(){
console.log('Successfully connected to database');
});
}
And the properties.js on /config is simply an object with the database URL and the port for the express server.
When I try to to a request through Postman to the URL: http://localhost:4000/list the request gets hanged and never resolves. What am I missing?
PD: SOLVED!
===========
I needed to update mongoose version on npm cause it was 3.x and needed to be 5.x in order to work well with the new methods.
Update your code little bit, Like this and check
spwanRoute.js
const express = require('express');
const router = express.Router();
const spawnCntr = require('./speanControllers');
router.get('/list', spawnCntr.getSpawns);
module.exports = router;
spwanUtils.js
const Spawns = require('../models/Spawn/spawn.dao');
const spawnUtils = {};
spawnUtils.getSpawns = (req, res) => {
try {
Spawns.get({}, (err, spawns) => {
if(err){
return res.status(400).json({ error: err });
}
return res.status(200).json({ spawns });
});
} catch (err) {
console.log(err);
return res.status(500).json({ error: 'INTERNAL_EROR' });
}
}
module.exports = spawnUtils;
I have followed the node-postgres.org instruction at https://node-postgres.com/guides/async-express to connect via async/await to my postgres table users.
Navigating to localhost:3000/users/1 will return the JSON string for user 1 in the browser. I have extended this a bit to return all users at localhost:3000/users. My routes/user.js script is:
const Router = require('express-promise-router')
const db = require('../db')
// create a new express-promise-router
// this has the same API as the normal express router except
// it allows you to use async functions as route handlers
const router = new Router()
// export our router to be mounted by the parent application
module.exports = router
router.get('/:id', async (req, res) => {
console.log('Where id = ');
const { id } = req.params
const { rows } = await db.query('SELECT * FROM users WHERE id = $1', [id])
res.send(rows[0])
})
router.get('/', async (req, res) => {
console.log('*');
const { rows } = await db.all('SELECT * FROM users')
res.send(rows)
})
the index for this route at routes/index.js is simply:
const users = require('./user')
module.exports = (app) => {
app.use('/users', users)
}
and the db.query() and db.all() functions that I am awaiting are in db/index.js:
const { Pool } = require('pg')
const pool = new Pool()
module.exports = {
query: (text, params) => pool.query(text, params),
all: (text) => pool.query(text)
}
The routes are required in my main app.js file:
// ./app.js
const express = require('express')
const mountRoutes = require('./routes')
const cons = require('consolidate')
const path = require('path')
const bodyParser = require('body-parser')
const app = express()
mountRoutes(app)
// Assign Dust Engine to .dust files
app.engine('dust', cons.dust);
// Set .dust as the default extension
app.set('view engine', 'dust');
app.set('views', __dirname + '/views');
// Set Public Folder
app.use(express.static(path.join(__dirname, 'public')));
//Body parser and Middleware
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.get('/', function(reg, res) {
console.log('Root');
res.render('index', {hallo:'test'})
});
//Server
app.listen(3000, function() {
console.log('Server Started on Port 3000');
});
So far this works beautifully! I get the JSON strings that I want and I can build upon this sort of API by extending my routes and queries.
Question:
How can I return my JSON object rows back to app.js to res.render() it there?
or
Can I do something like this anywhere in my app:
jsonVar = GetMyJson('/users/1');
console.log(jsonVar);
returns:
[
{
"id": 1,
"usr_name": "Michael"
},
{
"id": 2,
"usr_name": "Thomas"
},
{
"id": 3,
"usr_name": "Paul"
}
]
I could then pass whatever route and parameters I want into GetMyJson() and deal with the resulting JSON.
This may be a trivial question for javascript devs ...
Thanks!
EDIT 21/12/2017
I have created a frontend script called graphs.js that simply logs my result when i call the fuction api('/user/1').
var el = document.getElementById("clickMe");
if (el.addEventListener)
el.addEventListener("click", api, false);
else if (el.attachEvent)
el.attachEvent('onclick', api);
var api = function(what){
// Action
sendRequest(what, function(result){
if(result){
log(result);
}
})
}
var apiEndpoint = 'http://localhost:3000/'
function sendRequest(_path, cb) {
var oReq = new XMLHttpRequest();
oReq.open('GET', apiEndpoint+_path);
oReq.onreadystatechange = function() {
if (this.readyState === 4) {
cb(JSON.parse(this.response));
}
else{
cb(null);
}
}
oReq.send();
}
function log(msg){
console.log(msg);
}
BUT
Is that a proper way of doing it in javascript?
The way to go about it would be something like this:
router.get('/', async (req, res) => {
console.log('*');
const { rows } = await db.all('SELECT * FROM users')
res.render('user', rows)
});
Instead of res.send you do res.render
You have to get it from database. Making self call will not be an ideal solution. So you can do following
const db = require('../db') // fix the location of db.js
app.get('/', function(req, res) {
db.all('SELECT * FROM users').then(function({ rows }) {
res.render('index', rows)
})
})
Best would be to make an userRepository.js and add code related to db over there. Then use that repository at both places.