I'm trying to build a RESTful API for a node app.
I built the routes and everything is running fine. But when I try to test it, it cant get the DELETE method to work, despite of it working normally not under tests.
Here are the codes for the server and test.
Server:
// set up
var express = require('express');
var app = express(); // create our app w/ express
var path = __dirname; //root path
// configuration
app.configure(function() {
app.use(express.static(path));
//app.use(express.logger('dev')); // log every request to the console
app.use(express.json());
app.use(express.urlencoded()); // pull information from html in POST
app.use(express.methodOverride()); // simulate DELETE and PUT
});
function start() {
// routes
require('./app/routes.js')(app);
// listen (start app with node server.js)
app.listen(process.env.PORT || 5000);
console.log("Server listening for incoming conections..");
}
//************************
exports.start = start;
exports.server = app;
Test:
var should = require('should');
var assert = require('assert');
var request = require('supertest');
var mongoose = require('mongoose');
var express = require('express');
var server = require(__dirname + './../index.js');
describe('Routing', function() {
var url = 'http://localhost:5000';
it('should return status 200 after DELETING a bus', function(done) {
request(url)
.delete('/api/buses/' + bus.id)
.end(function(err, res) {
if (err) {
throw err;
}
res.should.have.status(200);
done();
});
});
});
And this is the error message it throws:
Routing
1) should return status 200 after DELETING a bus
✖ 1 of 1 test failed:
1) Routing should return status 200 after DELETING a bus:
TypeError: Object #<Object> has no method 'delete'
at Context.<anonymous> (/home/roger/Documents/Buse/test/test.js:63:16)
at Test.Runnable.run (/home/roger/Documents/Buse/node_modules/mocha/lib/runnable.js:196:15)
at Runner.runTest (/home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:351:10)
at /home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:397:12
at next (/home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:277:14)
at /home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:286:7
at next (/home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:234:23)
at Object._onImmediate (/home/roger/Documents/Buse/node_modules/mocha/lib/runner.js:254:5)
at processImmediate [as _immediateCallback] (timers.js:330:15)
make: *** [test] Error 1
Just to be clear there is no method delete with supertest but the correct method is del.
so a delete request should be tested like this:
var app=require('./app')
var request=require('supertest')
//with mocha for instance.
describe('test delete',function(){
it('should respond 200',function(done){
request(app).del('/path').expect(200).end(done);
})
});
And one needs to pass the app (express app) , not a url or a string.
Take a look at the supertest GitHub-page.
You may pass an http.Server, or a Function to request()
You are passing a string to the function request. Try passing your express-server object as the function parameter.
EDIT: as seen in comments and #mpm:s answer, the issue was the usage of reserved function delete() instead of package-specific function del().
Related
I'm having trouble with the error message in the title when trying to retrieve all users in my express .get('/users') method. I am using Node.js, Express, and node-postgres. I have my
getUsers(); function defined in my queries.js file, and I call the function in my app.get() function in my index.js file.
queries.js
const client = require('./object models/db_client_pool')
const Pool = require('pg').Pool
const pool = new Pool(client.client)
async function getUsers(request, response) {
await pool.connect()
pool.query('select * from discord_users', (error, results) => {
if (error) {
throw error
}
response.sendStatus(200).json(results.rows)
pool.release();
})
}
module.exports = {
getUsers
}
index.js
const express = require('express');
require('dotenv').config();
//const bodyParser = require('body-parser'); deprecated
const app = express();
const port = 3000;
const db = require('./queries');
app.use(express.json())
app.use(express.urlencoded({
extended: true
}))
app.get('/', (request, response) => {
response.json({ info: 'Node.js, Express, and Postgres API' })
})
app.get('/users', (req, res) => {
db.getUsers(req, res)
})
app.listen(port, () => {
console.log(`App is listening on port ${port}`);
});
As I said, I keep getting the "cannot set headers after they are sent to the client" error and I'm at a loss of what to do. Thanks in advance for your help!
Change from this:
response.sendStatus(200).json(results.rows)
to this:
response.status(200).json(results.rows);
or even just to this:
response.json(result.rows); // 200 is the default status already
The last one is fine because 200 is already the default status so you don't need to set that yourself.
The problem is that response.sendStatus(200) sends a complete response with an empty body and then you try to call response.json(result.rows) which tries to send ANOTHER response to the same request. Trying to send that second response to the same request is what triggers the error message you are getting.
response.status(200) just sets the status to 200 as a property on the waiting response object and waits for some other method to actually send the response itself which you can then do with .json(...).
So my guess is, you're running express 4.x and that doesn't support response.sendStatus(200) anymore. You have to use response.status(200) instead.
Now, another issue I see in your code is, I don't recognize pool.release() method from pg library. You can release a client back to a pool but you can't release a pool of clients. Maybe you meant pool.end()?
I have a route defined in my server.js file like:
app.use('/file', require('./server/routes/file.js'));
On the top of the file.js
var express = require('express');
var router = express.Router();
var outsideHelper = require('./outsideHelper');
...
router.post('/file', async function(req, res, next){
//Code
var connection = await dbFunc.dbConnect(sqlPool);
var result = await outsideHelper.DoStuff(res, connection);
})
module.exports = router;
outsideHelper works fine, and is exported in that file via
module.exports = {
DoStuff: async function(res, connection){
try {
var localSiteRes = await localFileFunction(res, connection);
return localSiteRes;
}catch(err){
return false;
}
}
The network only shows one request to the file.js however when outsideHelper.DoStuff function is used inside this request router.post('/file... the request gets ran twice, server side. When I remove the outsideHelper function from this however, it only runs once.
Everything works just fine, except on the first attempt after starting my local server.
I do not have a route defined for outsideHelper.js in my server.js because it is not an externally called route, it gets required within the file.js file.
localFileFunction uses JSFTP
Still kinda new to node.js and express.
Thanks in advance
An app (Loopback) is deployed on Heroku, and I need to to see the req body (the JSON) send to this app for debugging purpose.
So the access to the log is :
heroku logs --tail
The server.js is like the following :
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
app.start = function() {
// start the web server
return app.listen(function() {
app.emit('started');
console.log('Web server listening at: %s', app.get('url'));
});
};
// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module)
app.start();
});
As loopback extends express, we can use body parser module. So first install "body-parser"
Then add this code in serveur.js
var loopback = require('loopback');
var boot = require('loopback-boot');
var app = module.exports = loopback();
var bodyParser = require('body-parser');
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
var logger = function(req, res, next) {
console.log(JSON.stringify(req.body, null, 2));
next(); // Passing the request to the next handler in the stack.
}
app.use(logger);
...
The remote log will display every request body received by the server.
If you don't want to modify your server.js file (and why would you), you can register a middleware logging all requests, refer to:
https://docs.strongloop.com/display/public/LB/Defining+middleware#Definingmiddleware-Overview
for more detail on how to register and write a middleware. routes:before would be a good phase to log requests.
I am building a cross system admin app, which will be used as an admin tool for multiple backend systems. The app is built on top of Mean.js.
I have setup a /proxy route using "express-http-proxy" to send all sub-routes to their respective backend system endpoints. However, I need to have each request authenticated within my admin app and then decorated with the targeted backendSystem credentials before the "express-http-proxy" can continue. Here's an example of my /proxy route...
app.use('/proxy', users.requiresLogin, expressHttpProxy(config.backendSystem.host, {
forwardPath: function (req) {
return '/1.0' + require('url').parse(req.url).path;
},
decorateRequest: function (req) {
req.headers['content-type'] = 'application/json';
req.headers['backend-system-id'] = config.backendSystem.id;
req.headers['backend-system-key'] = config.backendSystem.key;
return req;
}
}));
NOTE:
Currently the backendSystem credentials are stored based on the environment my admin app is ran in. However, in the future the backendSystem credentials will be specified by the user, and this /proxy route will differently than what is currently shown.
THE ISSUE:
Proxy routes that require data within the request body don't work.
e.g. POST /comments {"user": user_id, "text": "rabble rabble rabble"}
WHAT I'VE FOUND:
bodyParser.json() and "express-https-proxy" don't play nice. I've confirmed this by removing bodyParser.json() from express.js.
However, this isn't a full solution since almost all of my other routes need bodyParser.json, e.g. /auth/signin.
Does anyone have a clean way that I can make a route exception for my /proxy route so that bodyParser.json won't be called for it?
As far as I understand, the root of problem is so:
if you were reading a POST request by pure node, you should be using a code like this
if (req.method == 'POST') {
console.log("POST");
var body = '';
req.on('data', function (data) {
body += data;
console.log("Partial body: " + body);
});
req.on('end', function () {
console.log("Body: " + body);
});
res.writeHead(200, {'Content-Type': 'text/html'});
res.end('post received');
}
in other words, you need to use the req.on('data') & req.on('end') events.
but the problem is,that you can use this code only once. after the 'end' is called, the request is consumed.
so then you use bodyParser , it consumes the request, and the proxy have nothing to do with it.
actually, in my opinion, the proxy wait for the 'data' event to appear , but it will newer happen, so the code halts.
The solution:
you need to 're-enable' the events. I used this code and it works for me.
var express = require('express');
var bodyParser = require('body-parser');
var http = require('http');
//call for proxy package
var devRest = require('dev-rest-proxy');
//init express (as default)
var users = require('./routes/users');
var app = express();
app.use(bodyParser.json());
//set the proxy listening port
app.set('port', 8080);
//process the POST request
app.post('/users/*', function(req, res) {
//just print the body. do some logic with it
console.log("req.body: ",req.body);
//remove listeners set by bodyParser
req.removeAllListeners('data');
req.removeAllListeners('end');
//add new listeners for the proxy to use
process.nextTick(function () {
if(req.body) {
req.emit('data', JSON.stringify(req.body));
}
req.emit('end');
});
//forward the request to another server
devRest.proxy(req,res, 'localhost', 3000);
});
//start the proxy server
http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});
module.exports = app;
the solution found on schumacher-m post (github of nodejitsu)
I was able to resolve my issue by adding a regex that excluded my /proxy route to where bodyParser.json was being added within express.js. I found that from this answer
While this approach doesn't scale well, it solved my immediate issue.
I get it works by converting the data into query string using 3rd party query-string as follows:
proxyReqBodyDecorator: function(bodyContent, srcReq) {
return (queryString.stringify(bodyContent));
}
Have tried JSON.stringify but not working, need the data in the following format
array_field=val1&array_field=val2&array_field=val3......
To modify the request body, do this with the latest express-http-proxy v1.6.2:
const express = require('express');
const proxy = require('express-http-proxy');
const bodyParser = require('body-parser');
const conf = {
proxyHost: 'some.example.net:9200',
proxyOptions: {
proxyReqBodyDecorator: modifyRequestBody,
preserveHostHdr: true,
parseReqBody: true
},
port: 8073
};
var app = express();
app.use('/proxy', proxy(conf.proxyHost, conf.proxyOptions));
function modifyRequestBody(body, srcReq) {
if(srcReq.method.match(/^(GET|POST)$/i)) {
try {
// convert buffer to string, then to object
var str = Buffer.from(body).toString('utf-8');
var reqBody = JSON.parse(str);
if(someCondition)) {
reqBody.addStuff = 'whatever';
body = reqBody; // return modified body as object
}
} catch(error) {
console.log('- error: ' + JSON.stringify(error));
}
}
return body; // return original buffer, or modified object
}
app.listen(conf.port, function () {
log('app listening on port ' + conf.port);
});
You can fill the proxyReq.bodyContent inside the decorateRequest method with the JSON-ed data from originalReq.body to be correctly POST'ed:
app.use('/proxy', users.requiresLogin, expressHttpProxy(config.backendSystem.host, {
...
...
decorateRequest: function (proxyReq, originalReq) {
...
...
if (originalReq.body) {
proxyReq.bodyContent = JSON.stringify(originalReq.body);
}
return proxyReq;
}
...
...
}));
I am trying to get my test suite to run within my express.js app, but this is my first express.js app as well as my first time working with mocha and I am unsure how to set everything up.
Here is my test
should = require 'should'
assert = require 'assert'
expect = require 'expect'
request = require 'superagent'
mongoose = require 'mongoose'
config = require '../../config/config'
describe "POST", ->
app = require('../../app')
beforeEach (done)->
mongoose.connect config.db, (err) ->
if err
console.log "Error! #{err}"
done()
describe '/users/:id/activities', ->
it 'should return created document', (done) ->
request(app).post('http://localhost:3000/users/1/activities').send(
description: "hello world"
tags: "foo, bar"
).set('Content-Type','application/json')
.end (e, res) ->
console.log(res.body.description)
expect(res.body.description).should.equal('hello world')
done()
A couple of issues I am having are... 1) In the test suite it can't connect to my test db (in my config file); 2) I am getting this error message when trying to post
1) POST /users/:id/activities should return created document:
TypeError: Object #<Request> has no method 'post'
Can someone point me in the right direction on how to get everything running properly?
The error I am getting back from trying to connect to the MongoDB is Error! Error: Trying to open unclosed connection.
I am running the mocha suite by running this command
NODE_ENV=test mocha test/controllers/activities.test.coffee --compilers coffee:coffee-script/register -R spec
Edit
activities.coffee (routes)
express = require 'express'
router = express.Router()
mongoose = require 'mongoose'
Activity = mongoose.model 'Activity'
module.exports = (app) ->
app.use '/', router
router.post '/users/:id/activities', (req, res, next) ->
activity = Activity(
user_id: req.params.id
description: req.body.description,
tags: req.body.tags
)
activity.save (err)->
if not err
res.json(activity)
else
res.json({ error: err })
router.get '/users/:id/activities/:activity_id', (req, res, next) ->
Activity.findById req.params.activity_id, (err, activity) ->
if not err
res.json(activity)
else
res.json({ error: err })
app.js
require('coffee-script/register');
var express = require('express'),
config = require('./config/config'),
fs = require('fs'),
mongoose = require('mongoose');
mongoose.connect(config.db);
var db = mongoose.connection;
db.on('error', function () {
throw new Error('unable to connect to database at ' + config.db);
});
var modelsPath = __dirname + '/app/models';
fs.readdirSync(modelsPath).forEach(function (file) {
if (/\.coffee$/.test(file)) {
require(modelsPath + '/' + file);
}
});
var app = express();
require('./config/express')(app, config);
app.listen(config.port);
exports.app = app;
First, once you have your superagent pointing to you app variable that holds you routes, middlewares and so on, there is no need to specify the url in the methods provided by superagent. You just need to provide the route, just like so:
request(app)
.post('/users/123/activities')
.end(function (err, response) {
});
Second, use the before statement provided by mocha instead of beforeEach, the second one will try to connect to mongo on every unit test you have.
For the first error
request has no method 'post'
Make sure you have installed superagent and you can find it on your node_modules folder.
Hope that helps!