I am building mock restful API to learn better. I am using MongoDB and node.js, and for testing I use postman.
I have a router that sends update request router.patch. In my DB, I have name (string), price (number) and imageProduct (string - I hold the path of the image).
I can update my name and price objects using raw-format on the postman, but I cannot update it with form-data. As I understand, in raw-form, I update the data using the array format. Is there a way to do it in form-data? The purpose of using form-data, I want to upload a new image because I can update the path of productImage, but I cannot upload a new image public folder. How can I handle it?
Example of updating data in raw form
[ {"propName": "name"}, {"value": "test"}]
router.patch
router.patch('/:productId', checkAuth, (req, res, next) => {
const id = req.params.productId;
const updateOps = {};
for (const ops of req.body) {
updateOps[ops.propName] = ops.value;
}
Product.updateMany({_id: id}, {$set: updateOps})
.exec()
.then(result => {
res.status(200).json({
message: 'Product Updated',
request: {
type: 'GET',
url: 'http://localhost:3000/products/' + id
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
err: err
});
});
});
Using for...of is a great idea, but you can't use it like you are to loop through an object's properties. Thankfully, Javascript has a few new functions that turn 'an object's properties' into an iterable.
Using Object.keys:
const input = {
firstName: 'Evert',
}
for (const key of Object.keys(input)) {
console.log(key, input[key]);
}
You can also use Object.entries to key both the keys and values:
const input = {
firstName: 'Evert',
}
for (const [key, value] of Object.entries(input)) {
console.log(key, value);
}
I know this answer might be too late to help you but it might help someone in 2020 and beyond.
First, comment out this block:
//const updateOps = {};
//for (const ops of req.body) {
//updateOps[ops.propName] = ops.value;
//}
and change this line:
Product.updateMany({_id: id}, {$set: updateOps})
to this:
Product.updateMany({_id: id}, {$set: req.body})
Everything else is fine. I was having similar issues, but this link helped me:
[What is the difference between ( for... in ) and ( for... of ) statements in JavaScript?
To handle multi-part form data, the bodyParser.urlencoded() or app.use(bodyParser.json());body parser will not work.
See the suggested modules here for parsing multipart bodies.
You would be required to use multer in that case
var bodyParser = require('body-parser');
var multer = require('multer');
var upload = multer();
// for parsing application/json
app.use(bodyParser.json());
// for parsing application/xwww-
app.use(bodyParser.urlencoded({ extended: true }));
//form-urlencoded
// for parsing multipart/form-data
app.use(upload.array());
app.use(express.static('public'));
Related
I'm having an issue with file uploading with Multer and unfortunately, I don't have a lot of experience with Multer.
In this project, I'm trying to structure things such that routes call functions (controllers) which run commands, for example creating a product etc.
I'm pretty sure I set up Multer correctly, however when I try to req.file.filename in the controller it returns an undefined.
This is my setup (currently Multer is a helper function, I'm going to move it to middleware after as this is incorrect).
File Storage Helper Func
const multer = require("multer");
//SET Storage
let storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, "public/uploads");
},
filename: function (req, file, cb) {
const fileName = file.originalname.replace(" ", "-");
cb(null, fileName + "-" + Date.now());
},
});
const uploadOptions = multer({ storage });
module.exports = { uploadOptions };
Product Router
const express = require("express");
const router = express.Router();
//#Helpers
const { uploadOptions } = require("./../../Helpers/FileStorage.helper");
//#Categories
const {
getProducts,
postProduct
} = require("./../../Controllers/Products/product.controller");
//GET ALL Products + ADD NEW Product
router
.get("/", getProducts)
.post("/", uploadOptions.single("image"), postProduct);
And finally, the post product controller:
//POST A New Product
const postProduct = expressAsyncHandler(async (req, res) => {
const category = await Categories.findOne(req.body.category);
if (!category) return res.status(400).send("Category is invalid, try again!");
const fileName = req.file.filename;
const basePath = `${req.protocol}://${req.get("host")}/public/upload/`;
const product = new Product({
name: req.body.name,
description: req.body.description,
richDescription: req.body.richDescription,
image: `${basePath}${fileName}`,
brand: req.body.brand,
price: req.body.price,
category,
countInStock: req.body.countInStock,
rating: req.body.rating,
numReviews: req.body.numReviews,
isFeatured: req.body.isFeatured,
});
const productList = await Product.findOne({ name: product.name });
if (productList != null) {
return res.status(404).send("Product already exists! Please try again!");
}
try {
await product.save();
res.status(200).send(product);
} catch (e) {
res.status(500).send("Product was not created! Error: " + e.message);
}
});
I know the traditional way of doing this in routes would be the following (which works!):
router.post("/", uploadOptions.single("image"), async(req,res) => {
//Run function
}
However, as I mentioned above, I'm trying to break the route actions up into controller functions.
When console.log(req.file) it returns an undefined.
I suspect the props aren't being passed to the postProduct function which is what causes the error, but I can't figure out how to resolve this. I've been staring at this too long, perhaps it's an easy thing to resolve and I'm being stupid (highly probable).
If someone could assist me in fixing this and explain where I'm going wrong, I would be eternally grateful.
Edit: This is the ERROR: " TypeError: Cannot read property 'filename' of undefined "
This statement: return console.log(req.file) in postProduct prevents the rest of the code from running and returns void. Console.log always returns void.
If you want to just log the file and continue with the rest of the function, remove the return keyword: console.log(req.file).
What does the console.log output in the terminal when you run the app?
I dont think that you are parsing the form data correctly:
// this is for parsing json data
app.use(express.json());
// this is for parsing form data
app.use(express.urlencoded({ extended: false }));
I'm using a simple post request to my backend for a form data and for some reason the body is alwayes empty.
I'm trying to isolate this so i changed the content type to application json and changed the data to json and only this way i can send data.
Client side:
submitForm(event) {
event.preventDefault();
console.log("gggg");
const data = new FormData(event.target);
axios.post("http://localhost:4000/user-form-post",data).then(function (response) {
//handle success
console.log(response);
})
.catch(function (response) {
//handle error
console.log(response);
});
Server side:
// app.use(bodyParser.json());
// app.use(bodyParser.urlencoded({extended:true}));
app.use(express.urlencoded());
// Parse JSON bodies (as sent by API clients)
app.use(express.json());
app.use(logger('dev'));
app.post('/user-form-post', (req,res) =>{
console.log("dfdf");
console.log(req.body); // alwayes print empty dict {}
res.end();
})
This is not working because it expects jsons(expected behavior):
// app.use(bodyParser.json());
// app.use(bodyParser.urlencoded({extended:true}));
Same behavior with Postman.
You will need to parse your form data from express side. For this you will have to use multer or multiparty. Try something like this. refer the documentation as well
const multiparty = require('multiparty');
app.post('/user-form-post', (req,res) =>{
let form = new multiparty.Form();
form.parse(req, function(err, fields, files) {
Object.keys(fields).forEach(function(name) {
console.log('got field named ' + name);
});
});
})
when it comes to my issue,
i have this front end
const form = new FormData();
form.email = this.email;
form.password = this.password;
console.log("onSubmit -> form", form);
axios.post("http://localhost:3000/register", form )
onSubmit -> form FormData {email: "admin#gmail.com", password: "123"}
but the req.body in backend is empty, and i figured it out that the form in axios.post still need 1 more bracket {} even it's a object. like this
axios.post("http://localhost:3000/register", { form })
After that backend got body like this
req.body = { form: { email: 'admin#gmail.com', password: '123' } }
A problem with request body when you post data is data type .
I have recently a problem with Postman .
You should post data with type x-www-form-urlencoded or raw->JSON to fix the problem.
Goodluck.
You are using:
app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
Please, also use given below line code but first install multer and write the code in top of your application:
var multer = require('multer');
var upload = multer();
app.use(express.json());
Faced the same issue , spent 2 days . Here are the solutions i found :
my request payload had JSON.stringify() , it will make body as {} empty object . when i removed JSON.stringify() and sent request it worked .
Content type should be multipart-form :boundary -----
Now if i externally set it to multipart-form , boundary thing was missing.
for few people it worked when you set content-type as false / undefined , boundary thing got added up,but not for me .
Even though i followed all steps and sending FormData as payload, payload was request payload object in network tab and was not FormData object , my request failed with 500 .
i tried the below code , its react + typescript (make necessary changes to avoid syntax errors)
import QueryString from 'qs';
import { ApiParams } from './xyzfile';
import { ApiHandlerRawType } from './types/xyzfile';
const setDefaultOptions = ({
method = '',
url = '',
params = {},
data = {},
signal = null,
headers = new Headers(),
...options
} = {}) => ({
method,
url,
params,
signal,
headers,
data,
...options
});
const setData = ({ method, data, ...options }: ApiHandlerRawType) => {
const option = options;
if (method !== 'GET' && option.isStreamData) {
option.body = data;
}
return {
method,
...option
};
};
const addRequestHeaders = ({ headers = new Headers(), ...options }) => {
const { existingHeaders }: ApiHandlerRawType = options;
if (existingHeaders) {
Object.entries(existingHeaders).forEach(([key, value]) => {
if (key !== 'Content-Type') headers.set(key, value);
});
}
return {
headers,
...options
};
};
export const ApiHandlerRaw = ({
url,
...originalOptions
}: ApiHandlerRawType): Promise<Response> => {
const options = setData(
addRequestHeaders(setDefaultOptions(originalOptions))
);
return fetch(url || '', options)
.then(response => {
if (!response.ok) throw new Error(response.statusText);
return Promise.resolve(response);
})
.catch(err => Promise.reject(err));
};
export const FileUploadApiHandler = async ({
headers,
...options
}: ApiHandlerRawType): Promise<Response | Blob> => {
const response = await ApiHandlerRaw({
headers,
isStreamData: true,
...options
});
return response;
};
export const fileApiService = ({
url,
method,
qsObject,
headers,
reqObjectAsStreamData
}: ApiParams): Promise<Response> => {
const qs = QueryString.stringify(qsObject, { addQueryPrefix: true });
const urlPath = `${url}${qs}`;
const data = reqObjectAsStreamData;
const existingHeaders = headers;
return FileUploadApiHandler({
url: urlPath,
method,
data,
existingHeaders
}) as Promise<Response>;
};
send the required variables from fileApiService . existingHeaders would be your app headers , eg : token / ids ... etc . data in fileApiService is the body .
I have also faced the same issue in the published code.
But I have fixed this issue by using the below code highlighted in the attached image :-
enter image description here
There is no use of "Content-Type" to fix this issue.
Hope you fix your issue by using the above code snippets.
I am trying to send a post request from a node + express server to my Foxx service on Arangodb.
On the node side :
var route = arangopi + '/edge/' + col.name ;
var body = {data: data, from: fromId, to: toId} ;
console.log('|| body :', route, body) ;
>> || body : http//XXX/_db/my-DB/my-foxx-service/path/to/visitedBy { data: { isBackup: true, text: '', isHint: true, continuance: 3441.5 }, from: 'Drop/27237133', to: 'Bot/41116378' }
return requestify.post (route, body)
On the Foxx side, I receive the request but the logs tell me it has no body :
router.post('/path/to/:param', function (req, res) {
console.log ('|| body :', req.body)
var data = req.body ;
var result = api.DoSomething (req.stateParams.param, data)
res.send(result)
})
.response(joi.object().required(), 'Entry stored in the collection.')
.summary('Summary')
.description('Description')
>> || body : [Object { "binarySlice" : function binarySlice() { [native code] }, "asciiSlice" : function asciiSlice() { [native code] }, "base64Slice" : function base64Slice() { [native code] }, "ucs2Slice" : function ucs2Slice() { [native code] }, "hexSlice" : f...
On the node side I also tried the 'request' module.
return request.post(route, {form:body}, function (error, response, body) {
console.log('error:', error);
console.log('statusCode:', response && response.statusCode);
console.log('body:', body);
return response ;
});
And I get the same logs from Foxx.
What do I do wrong ?
Here is a screenshot of my operation on the Foxx interface. Is it normal that I cannot specify a request body for testing ?
I think the reason is because you haven't specified in the end point in Foxx that there is a body expected as part of the .post.
It took me a while to work out a way of defining Foxx MicroServices, and I read through a number of ArangoDB example code before I settled on a pattern.
To help you get started, I've provided how I would quickly mock up the Foxx MicroService code in a way that is extensible, allowing you to separate your Routes from your Models.
Use these as examples to get your example working.
I've made assumptions that there are two document collections, 'Drop' and 'Bot' with an edge collection that joins them called 'VisitedBy'.
All these files are stored on your Foxx MicroService:
main.js
'use strict';
module.context.use('/v1/visitedBy', require('./routes/visitedBy'), 'visitedBy');
routes/visitedBy.js
'use strict';
const request = require('#arangodb/request');
const joi = require('joi');
const createRouter = require('#arangodb/foxx/router');
const VisitedBy = require('../models/visitedBy');
const visitedDataSchema = joi.object().required().description('Data that tracks a visited event');
const router = createRouter();
module.exports = router;
/*********************************************
* saveVisitedBy
* Path Params:
* none
* Query Params:
* none
* Body Params:
* body (required) The data that is used to record when something is visited
*/
router.post('/', function (req, res) {
const visitedData = req.body;
const savedData = VisitedBy.saveVisitedByData(VisitedBy.fromClient(visitedData));
if (savedData) {
res.status(200).send(VisitedBy.forClient(savedData));
} else {
res.status(500).send('Data not saved, internal error');
}
}, 'saveVisitedBy')
.body(visitedDataSchema, 'visited data')
.response(VisitedBy.savedDataSchema, 'The response after the data is saved')
.summary('Save visited data')
.description('Save visited data');
models/visitedBy.js
'use strict';
const _ = require('lodash');
const joi = require('joi');
const db = require('#arangodb').db;
const visitedByEdgeCollection = 'VisitedBy';
/*
Schema for a response after saving visitedBy data
*/
const savedDataScema = {
id: joi.string(),
data: joi.object(),
_from: joi.string(),
_to: joi.string()
};
module.exports = {
savedDataSchema: savedDataScema,
forClient(obj) {
// Implement outgoing transformations here
// Remove keys on the base object that do not need to go through to the client
if (obj) {
obj = _.omit(obj, ['_id', '_rev', '_oldRev', '_key']);
}
return obj;
},
fromClient(obj) {
// Implement incoming transformations here
return obj;
},
saveVisitedByData(visitedData) {
const q = db._createStatement({
"query": `
INSERT {
_from: #from,
_to: #to,
data: #data,
date: DATE_NOW()
} IN ##col
RETURN MERGE ({ id: NEW._id }, NEW)
`
});
q.bind('#col', visitedByEdgeCollection);
q.bind('from', visitedData.from);
q.bind('to', visitedData.to);
q.bind('data', visitedData.data);
const res = q.execute().toArray();
return res[0];
}
};
Your service should look like this in the Swagger interface:
You can learn more about using joi to define data structures here.
It takes a bit getting used to joi, but once you get some good working examples you can define great data definitions for incoming and outgoing data.
I hope this helps, it was difficult for me getting a basic MicroService code model that made it clear how things operated, I'm sure a lot can be done for this example but it should be a good starting spot.
As David Thomas explained in his answer, I needed to specify a body format in my router code (Foxx side).
In short :
const bodySchema = joi.object().required().description('Data Format');
router.post('/path/to/:param', function (req, res) {
var data = req.body ;
var result = api.DoSomething (req.stateParams.param, data)
res.send(result)
})
.body(bodySchema, 'Body data')
.response(joi.object().required(), 'Entry stored in the collection.')
.summary('Summary')
.description('Description')
I'm trying to consolidate a bunch of route usage throughout my Express API, and I'm hoping there's a way I can do something like this:
const app = express()
const get = {
fetchByHostname({
name
}) {
return `hey ${name}`
}
}
const map = {
'/public/hostname/:hostname': get.fetchByHostname
}
app.use((req, res, next) => {
const url = req.originalUrl
const args = { ...req.body, ...req.query }
const method = map[url] // this won't work
const result = method(args)
return res.json({
data: result
})
})
I'm trying to avoid passing round the req and res objects and just handle the response to the client in one place. Is there an Express/Node/.js module or way to match the URL, like my map object above?
I really don't understand what you are trying to achieve, but from what i can see, your fectchByHostname({name})should be fetchByHostname(name) and you might be able to return hey $name. You should be sure you are using ES6 because with you args. Else you have to define the as in es5 args = {body: req.body, query: req.query};. Hope it helps.
I know how to get the params for queries like this:
app.get('/sample/:id', routes.sample);
In this case, I can use req.params.id to get the parameter (e.g. 2 in /sample/2).
However, for url like /sample/2?color=red, how can I access the variable color?
I tried req.params.color but it didn't work.
So, after checking out the express reference, I found that req.query.color would return me the value I'm looking for.
req.params refers to items with a ':' in the URL and req.query refers to items associated with the '?'
Example:
GET /something?color1=red&color2=blue
Then in express, the handler:
app.get('/something', (req, res) => {
req.query.color1 === 'red' // true
req.query.color2 === 'blue' // true
})
Use req.query, for getting he value in query string parameter in the route.
Refer req.query.
Say if in a route, http://localhost:3000/?name=satyam you want to get value for name parameter, then your 'Get' route handler will go like this :-
app.get('/', function(req, res){
console.log(req.query.name);
res.send('Response send to client::'+req.query.name);
});
Query string and parameters are different.
You need to use both in single routing url
Please check below example may be useful for you.
app.get('/sample/:id', function(req, res) {
var id = req.params.id; //or use req.param('id')
................
});
Get the link to pass your second segment is your id example: http://localhost:port/sample/123
If you facing problem please use Passing variables as query string using '?' operator
app.get('/sample', function(req, res) {
var id = req.query.id;
................
});
Get link your like this example: http://localhost:port/sample?id=123
Both in a single example
app.get('/sample/:id', function(req, res) {
var id = req.params.id; //or use req.param('id')
var id2 = req.query.id;
................
});
Get link example: http://localhost:port/sample/123?id=123
Update: req.param() is now deprecated, so going forward do not use this answer.
Your answer is the preferred way to do it, however I thought I'd point out that you can also access url, post, and route parameters all with req.param(parameterName, defaultValue).
In your case:
var color = req.param('color');
From the express guide:
lookup is performed in the following order:
req.params
req.body
req.query
Note the guide does state the following:
Direct access to req.body, req.params, and req.query should be
favoured for clarity - unless you truly accept input from each object.
However in practice I've actually found req.param() to be clear enough and makes certain types of refactoring easier.
#Zugwait's answer is correct. req.param() is deprecated. You should use req.params, req.query or req.body.
But just to make it clearer:
req.params will be populated with only the route values. That is, if you have a route like /users/:id, you can access the id either in req.params.id or req.params['id'].
req.query and req.body will be populated with all params, regardless of whether or not they are in the route. Of course, parameters in the query string will be available in req.query and parameters in a post body will be available in req.body.
So, answering your questions, as color is not in the route, you should be able to get it using req.query.color or req.query['color'].
The express manual says that you should use req.query to access the QueryString.
// Requesting /display/post?size=small
app.get('/display/post', function(req, res, next) {
var isSmall = req.query.size === 'small'; // > true
// ...
});
const express = require('express')
const bodyParser = require('body-parser')
const { usersNdJobs, userByJob, addUser , addUserToCompany } = require ('./db/db.js')
const app = express()
app.set('view engine', 'pug')
app.use(express.static('public'))
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.get('/', (req, res) => {
usersNdJobs()
.then((users) => {
res.render('users', { users })
})
.catch(console.error)
})
app.get('/api/company/users', (req, res) => {
const companyname = req.query.companyName
console.log(companyname)
userByJob(companyname)
.then((users) => {
res.render('job', { users })
}).catch(console.error)
})
app.post('/api/users/add', (req, res) => {
const userName = req.body.userName
const jobName = req.body.jobName
console.log("user name = "+userName+", job name : "+jobName)
addUser(userName, jobName)
.then((result) => {
res.status(200).json(result)
})
.catch((error) => {
res.status(404).json({ 'message': error.toString() })
})
})
app.post('/users/add', (request, response) => {
const { userName, job } = request.body
addTeam(userName, job)
.then((user) => {
response.status(200).json({
"userName": user.name,
"city": user.job
})
.catch((err) => {
request.status(400).json({"message": err})
})
})
app.post('/api/user/company/add', (req, res) => {
const userName = req.body.userName
const companyName = req.body.companyName
console.log(userName, companyName)
addUserToCompany(userName, companyName)
.then((result) => {
res.json(result)
})
.catch(console.error)
})
app.get('/api/company/user', (req, res) => {
const companyname = req.query.companyName
console.log(companyname)
userByJob(companyname)
.then((users) => {
res.render('jobs', { users })
})
})
app.listen(3000, () =>
console.log('Example app listening on port 3000!')
)
you can simply use req.query for get query parameter:
app.get('/', (req, res) => {
let color1 = req.query.color1
let color2 = req.query.color2
})
The url module provides utilities for URL resolution and parsing. URL parse without using Express:
const url = require('url');
const queryString = require('querystring');
let rawUrl = 'https://stackoverflow.com/?page=2&size=3';
let parsedUrl = url.parse(rawUrl);
let parse = queryString.parse(parsedUrl.query);
// parse = { page: '2', size: '3' }
Another way:
const url = require('url');
app.get('/', (req, res) => {
const queryObject = url.parse(req.url,true).query;
});
url.parse(req.url,true).query returns { color1: 'red', color2: 'green' }.
url.parse(req.url,true).host returns 'localhost:8080'.
url.parse(req.url,true).search returns '?color1=red&color2=green'.
Just use the app.get:
app.get('/some/page/here', (req, res) => {
console.log(req.query.color) // Your color value will be displayed
})
You can see it on expressjs.com documentation api:
http://expressjs.com/en/api.html
A nice technique i've started using with some of my apps on express is to create an object which merges the query, params, and body fields of express's request object.
//./express-data.js
const _ = require("lodash");
class ExpressData {
/*
* #param {Object} req - express request object
*/
constructor (req) {
//Merge all data passed by the client in the request
this.props = _.merge(req.body, req.params, req.query);
}
}
module.exports = ExpressData;
Then in your controller body, or anywhere else in scope of the express request chain, you can use something like below:
//./some-controller.js
const ExpressData = require("./express-data.js");
const router = require("express").Router();
router.get("/:some_id", (req, res) => {
let props = new ExpressData(req).props;
//Given the request "/592363122?foo=bar&hello=world"
//the below would log out
// {
// some_id: 592363122,
// foo: 'bar',
// hello: 'world'
// }
console.log(props);
return res.json(props);
});
This makes it nice and handy to just "delve" into all of the "custom data" a user may have sent up with their request.
Note
Why the 'props' field? Because that was a cut-down snippet, I use this technique in a number of my APIs, I also store authentication / authorisation data onto this object, example below.
/*
* #param {Object} req - Request response object
*/
class ExpressData {
/*
* #param {Object} req - express request object
*/
constructor (req) {
//Merge all data passed by the client in the request
this.props = _.merge(req.body, req.params, req.query);
//Store reference to the user
this.user = req.user || null;
//API connected devices (Mobile app..) will send x-client header with requests, web context is implied.
//This is used to determine how the user is connecting to the API
this.client = (req.headers) ? (req.headers["x-client"] || (req.client || "web")) : "web";
}
}