Express - 400 bad request on POST and PUT - javascript

I'd like some help as I'm new to Node.js and express. I have the following code which I'm testing on Postman
const Joi = require('#hapi/joi');
const bodyParser = require('body-parser');
// Load the express framework
const express = require('express');
const app = express();
app.use(express.json());
app.use(bodyParser.json());
app.use(express.urlencoded({ extended: true }));
// temp array
const courses = [
{id: 1, title: 'Learning Node.js'},
{id: 2, title: 'How to become a full stack dev'},
{id: 3, title: 'Master your Javascript skills'}
];
app.post('/api/courses', (request, response) => {
let error = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message); // *
return;
}
let course = {
id: courses.length + 1,
name: request.body.name
};
// TODO save
console.log('TODO save the record');
response.send(course);
});
app.put('/api/courses/:id', (request, response) => {
let course = courses.find(c => c.id === parseInt(request.params.id));
if(!course) response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message);
return;
}
// TODO save
console.log('TODO save the record');
response.send(course);
});
function validateCourse(course) {
let schema = Joi.object({
name: Joi.string().min(4).required()
});
console.log(course);
return schema.validate(course);
}
Either when I make a PUT or a POST request in which I supply a name (i.e the validation should pass) I get a 400 error.
On POST request I see the following in the console which refers to where my asterisk (*) is
TypeError: Cannot read property '0' of undefined
at /app/index.js:50:48
On PUT request I see the following in the console Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
In both cases the console.log I have in the validateCourse function is showing an empty object {}
This is a screenshot from postman
Any ideas what I'm doing wrong?

👨‍🏫 I've been test this code and I mean you can try this code below 👇:
app.post('/api/courses', (request, response) => {
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message); // *
}
let course = {
id: courses.length + 1,
name: request.body.name
};
// TODO save
console.log('TODO save the record');
response.send(course);
});
app.put('/api/courses/:id', (request, response) => {
let course = courses.find(c => c.id === parseInt(request.params.id));
if(!course) response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
response.status(400).send(error.details[0].message);
}
// TODO save
console.log('TODO save the record');
response.send(course);
});
function validateCourse(course) {
let schema = Joi.object({
name: Joi.string().min(4).required()
});
console.log(course);
return schema.validate(course);
}
I hope it's can help you 🙏.

let course = courses.find(c => c.id === parseInt(request.params.id))
if(!course) return response.status(404).send('Oops!');
let { error } = validateCourse(request.body);
if (error) {
return response.status(400).send(error.details[0].message);
}
response.send(course);
This must resolve the error that you are getting on PUT request.

Related

Mongodb moongose log table

I am interested in how to create log table that writes data in own table, every time user makes some request.
And how to get data like this:
{
_id: ObjectId('4f442120eb03305789000000'),
host: "127.0.0.1",
logname: null,
user: 'frank',
time: ISODate("2000-10-10T20:55:36Z"),
path: "/apache_pb.gif",
request: "GET /apache_pb.gif HTTP/1.0",
status: 200,
response_size: 2326,
referrer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
user_agent: "Mozilla/4.08 [en] (Win98; I ;Nav)"
}
Maybe not all of this data, but atleast who made the request, what type of request, path and time.
I am using nodejs, mongodb, mongoose.
You can write a middleware that logs all the requests, being sent to your server, to the MongoDB Database.
You can easily get the information you are looking for, using these npm packages,
1 - useragent
2 - express-useragent
I solved on this way.
My middlewere
const Log = require("../models/log");
const log = async (req, res, next) => {
try {
let user_id = req.user.id
let firstName = req.user.firstName
let method = req.method
let path = req.path
const log = new Log({ user_id, firstName, method, path });
try {
await log.save()
} catch (e) {
res.status(400).send(e)
}
next();
} catch (e) {
res.status(401).send(e);
}
};
module.exports = log;
Model
const mongoose = require('mongoose')
const logSchema = new mongoose.Schema({
user_id: {
type: String,
},
firstName: {
type: String,
},
method: {
type: String,
},
path: {
type: String,
},
}, {
timestamps: true
})
const Log = mongoose.model('Log', logSchema);
module.exports = Log;
and router
const express = require('express')
const Log = require('../models/log')
const auth = require('../middleware/auth')
const router = new express.Router()
//Create log
router.post('/logs', async (req, res) => {
const log = new Log({
...req.body
})
try {
await log.save()
res.status(201).send(log)
} catch (e) {
res.status(400).send(e)
}
})
//Sort and search
router.get('/logs', auth, async (req, res) => {
const match = {}
const sort = {}
if (req.query.completed) {
match.completed = req.query.completed === 'true'
}
if (req.query.sortBy) {
const parts = req.query.sortBy.split(':')
sort[parts[0]] = parts[1] === 'desc' ? -1 : 1
}
try {
await req.user.populate({
path: 'logs',
match,
options: {
limit: parseInt(req.query.limit),
skip: parseInt(req.query.skip),
sort
}
}).execPopulate()
res.send(req.user.logs)
} catch (e) {
res.status(500).send()
}
})
module.exports = router;

In patch request fields which are not included inside allowedUpdates can also be updated

Suppose a model has fields a,b and c
and the patch for updating the model looks like this
router.patch('/entities/:id', passport.authenticate('jwt', { session: false }), async (req, res) => {
const updates = Object.keys(req.body);
const allowedUpdates = ['a', 'b'];
const isValidOperation = updates.some((update) => allowedUpdates.includes(update));
if (!isValidOperation) {
res.status(400).send({ error: 'Invalid updates!' });
}
try {
const entity = await Entity.findOne({ _id: req.params.id });
const entityUpdated = await Entity.findByIdAndUpdate(entity,
body,
{ new: true, runValidators: true });
res.send(entityUpdated);
}
catch (e) {
res.status(400).send(e);
}
});
but in my api request (using postman) if I pass
{
"c" : "some text",
}
C is not there in allowedUpdates.
Still C field gets updated and changes are shown in the backend data.
How can ensure that only the fields inside allowedUpdates are allowed to be updated and the rest throw the error?

AssertionError: expected { status: 'SUCCESS', data: [] } to equal { Object (status, data) }

I am running unit test for API call that is serving post request. I am passing request body and must get back response as account data. But I am getting only assertion error
Note: The data is fetched from Azure
spec.js
const accounts=require('./accounts');
const should=require('chai').should();
const chai=require('chai');
const chaiAsPromised=require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
....
beforeEach(function()
{
mockResponse=
[
{
"AccountId": "xyz",
"AccountState": "Active"
}
]
it('Should get account from Azure API', function() {
return accounts.getActivatedAccounts(req.body.customerNumber).
should.eventually.equal(mockResponse);
});
**JavascriptFile**
function getActivatedAccounts(accounts) {
let promise = new Promise(function(resolve, reject) {
fetch(Url , { headers: config.headersAPIM})
.then(response => response.json())
.then(accounts => {
if (accounts) {
Accounts[accounts] = [];
for (account in accounts) {
let accountType = accounts[account]['type]'];
Accounts[email].push(accounts[account]);
}
let reply = {
status : "SUCCESS",
data : Accounts[accounts]
}
resolve(reply);
} else {
let reply = {
status : "SUCCESS",
data : accounts
}
resolve(reply);
}
})
.catch(err => {
console.log("Error: Could not find accounts");
console.log('Error:' + err);
let reply = {
status:"FAILURE",
err: "Error: Could not find accounts. " + err
}
resolve(reply);
})
});
return promise;
}
I am not able to give javascript file that is calling the service, I will give it in the answer section
Sounds like you're asking about the Chai assertion.
equal uses strict equality so unless the two objects are literally the same object it will fail.
eql uses a deep equality comparison and will pass if the objects have the same properties and values.
Here is a simple example:
const chai = require('chai');
const chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
chai.should();
const getActivatedAccounts = () =>
Promise.resolve({ status: 'SUCCESS', data: ['some', 'data'] });
it('Should get account from Azure API', function () {
return getActivatedAccounts()
.should.eventually.eql({ status: 'SUCCESS', data: ['some', 'data'] }); // Success!
});

Getting single message from Graph

I'm trying to get a single email from an Office 365 Mailbox.
I'm sending the email id to my app via a POST (req.body.id) and then calling this code in order to get some email properties:
router.post('/id', async function(req, res, next) {
console.log("email with ID -> ", req.body.id)
let parms = { title: 'Inbox', active: { inbox: true } };
const accessToken = await authHelper.getAccessToken(req.cookies, res);
const userName = req.cookies.graph_user_name;
if (accessToken && userName) {
parms.user = userName;
// Initialize Graph client
const client = graph.Client.init({
authProvider: (done) => {
done(null, accessToken);
}
});
try {
const result = await client
.api('/me/messages/', req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();
parms.messages = result.value;
console.log("email -> ", result.value);
res.render('message', parms);
} catch (err) {
parms.message = 'Error retrieving messages';
parms.error = { status: `${err.code}: ${err.message}` };
parms.debug = JSON.stringify(err.body, null, 2);
res.render('error', parms);
}
} else {
// Redirect to home
res.redirect('/');
}
});
At the moment, result.value contains all of the messages in the mailbox instead of just the message with provided id.
Could someone tell me where my error is, please?
The api method has a single path parameter. Calling it like .api('/me/messages/', req.body.id) is effectivly sending it a path ("/me/messages/") along with an additional parameter it ignores.
You need to send it a single string so you'll need to append the req.body.id to the path ({path} + {id}):
const result = await client
.api('/me/messages/' + req.body.id)
.select('id,subject,from,toRecipients,ccRecipients,body,sentDateTime,receivedDateTime')
.get();

How to test simple middleware

I have a 3 middlewares like this:
module.exports = {
validateRequest: function(req, res, next) {
return new Promise((resolve, reject) => {
if(!req.body.title || !req.body.location || !req.body.description || !req.body.author){
Promise.reject('Invalid')
res.status(errCode.invalid_input).json({
message: 'Invalid input'
})
}
})
},
sendEmail: ...,
saveToDatabase: ...
}
I use those in my route like this:
const { validateRequest, sendEmail, saveToDatabase } = require('./create')
...
api.post('/create', validateRequest, sendEmail, saveToDatabase);
It works, but I can't test it. Here's my (failed) attempt:
test('create.validateRequest should throw error if incorrect user inputs', (done) => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
expect(validateRequest(req, res, next)).rejects.toEqual('Invalid')
})
Jest outputs this:
Error
Invalid
Question: How can I test this validateRequest middleware?
So firstly, assuming this is Express, there's no reason (or requirement) to return a Promise from your middleware, return values are ignored. Secondly, your current code will actually cause valid requests to hang because you aren't calling next to propagate the request to the next middleware.
Taking this into account, your middleware should look a bit more like
validateRequest: (req, res, next) => {
if (!req.body.title || !req.body.location || !req.body.description || !req.body.author) {
// end the request
res.status(errCode.invalid_input).json({
message: 'Invalid input'
});
} else {
// process the next middleware
next();
}
},
Based on the above, a valid unit test would look like
test('create.validateRequest should throw error if incorrect user inputs', () => {
const next = jest.fn();
const req = httpMocks.createRequest({
body: {
title: 'A new world!',
location: '...bunch of talks...',
description: '...'
}
});
const res = httpMocks.createResponse();
validateRequest(req, res, next);
// validate HTTP result
expect(res.statusCode).toBe(400);
expect(res._isJSON()).toBeTruthy();
// validate message
const json = JSON.parse(res._getData());
expect(json.message).toBe('Invalid input');
})

Categories

Resources