I want to add data to the request object for each request. Hence I subscribed to the onRequest event and am doing:
server.ext('onRequest', (request, reply) => {
console.log('I AM CALLED');
const marketCode : Market = request.params["market-code"];
const operatorCode : Operator = request.params["operator-code"];
request.operatorContext = new OperatorContext(
operatorEndpointEnv,
operatorCode,
marketCode
);
console.log(request.operatorContext); // will be defined
return reply.continue();
});
Yet when my controller:
function apiStatus(req, reply) {
console.log(req.operatorContext); // will be undefined
reply.status(204).end();
}
recieves the call, the request is just plain has request.operatorContext as undefined, yet it set within the context of the adding the extension it is defined, as I can see it logged:
I AM CALLED
OperatorContext { environment: 'test', operator: undefined, market: undefined }
undefined
How to load my custom data to a request object?
Related
My team and I are trying to mutate the response.end method in our Express middleware in order to have extra functionality be called just before the server responds back to the client.
Here is our attempt:
return (req: Request, res: Response, next: NextFunction): NextFunction => {
// reassign res.end in order to allow logger functionality before
// a response is sent back the client
const temp = res.end;
res.end = () => {
// instantiates PostQuery object with passed in query data from limiter middleware
const postQuery = new PostQuery(gateURI, projectID, res.locals.graphqlGate);
// our logger middleware functionality
try {
await postQuery.post();
} catch (err) {
if (err) console.log(err);
}
// our temp variable holding node's res.end definition
return temp.call(this);
};
return next();
};
Our test server throws this error when we include this function in our middleware chain:
TypeError: Cannot read properties of undefined (reading 'finished')
at end (node:_http_outgoing:856:19)
at /Users/jon/Documents/Solo Projects/OSP/graphQL-gate-logger/src/index.ts:65:25
index.ts:65 points to return temp.call(this)
We have also tried return temp() , as well as binding temp to the res object, and receive the same error in every instance.
Is there some other way we can reach this goal or do we have to start back at the drawing board?
If you don't have to execute your code BEFORE the response has been sent, but can instead do it right afterwards, then you can use the finish event on the res stream.
app.use((req, res, next) => {
res.on('finish', () => {
console.log(`got finish event for ${req.url}`);
// do your business here after a response has been sent
});
next();
});
There are also a couple problems with your existing override middleware. First off, you aren't preserving arguments that can be optionally send to res.end(). Second, res.end() is supposed to return res which makes it chainable. You aren't doing that. You have assigned it an async function which returns a promise, not res.
Though I think it would be much better to use the finish event as illustrated above and not have to override any methods, this would fix some of the problems with your override:
return (req: Request, res: Response, next: NextFunction): NextFunction => {
// reassign res.end in order to allow logger functionality before
// a response is sent back the client
const origEnd = res.end;
res.end = function(...args) {
// instantiates PostQuery object with passed in query data from limiter middleware
const postQuery = new PostQuery(gateURI, projectID, res.locals.graphqlGate);
// our logger middleware functionality
postQuery.post().catch(err => {
console.log(err);
}).finally(() => {
return origEnd.call(this, ...args);
});
return res;
};
return next();
};
I fetch data at server side and push the result to global variable and then send that global variable to client with app.post method using Express.js. My problem is that client fetches the global variable too soon without the data received from the API first. How can I evaluate the response so that client would wait the global variable to reveive data first before displaying anything.
Server side, code looks something like this:
let sharpe = ''
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Sharpe is the global variable storing the response from multiple API calls and is the one that should be sent back to client. Client side code is this:
const sendData = async (event) => {
event.preventDefault();
var stock1 = document.getElementById('weight1').value
var stock2 = document.getElementById('weight2').value
const data = {stock1, stock2};
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {'Content-Type': 'application/json' }
}
fetch('/api', options).then(res => res.json()).then(res => {
console.log(res.stocks);
})
}
As a result SendData() function fetches the sharpe variable that is empty at the moment. How can I adjust client side code or server side code that the client waits for a correct response? Thanks.
One solution would be to store the API results to database and client would fetch ready made datastream but is there more straightforward solution?
To wait for your API Server to set the sharpe Parameter, it needs to be awaited, which you already did. It depends on the setup function (for example setup()) which fills the sharpe parameter. It has to return a promise, which is resolved once sharpe is filled with the data.
let sharpe = ''
async setup() {
return new Promise((resolve, reject) => {
sharpe = 'test';
// set value for sharpe
resolve()
})
}
app.post('/api', async(req, res, next) => {
console.log('I got a request!')
thisOne = req.body.stock1
thisOne2 = req.body.stock2
var result = await setup();
res.json({
status: 'success',
stocks: sharpe
});
})
Eventually it starded working when I pushed all the API calls in the app.post middleware at the server side using promise chaining. The initial problem remains a mystery.
twiml.dial(dialNode => {
dialNode.conference('Test conference', {
startConferenceOnEnter: true,
endConferenceOnExit: false,
from: context.CALLER_ID,
to: event.TO
})
I have tried this on Twilio Functions but this returns an error on the client side.
There are quite a few Twilio Function examples, one of which is making an outbound call. You can view the examples here (under Function Examples) on the left side of the screen.
Make a Call
// Description
// Make a call
exports.handler = function (context, event, callback) {
// Make sure under Functions Settings tab:
// "Add my Twilio Credentials (ACCOUNT_SID) and (AUTH_TOKEN) to ENV" is CHECKED
const twilioClient = context.getTwilioClient();
// Pass in From, To, and Url as query parameters
// Example: https://x.x.x.x/<path>?From=%2b15108675310&To=%2b15108675310&Url=http%3A%2F%2Fdemo.twilio.com%2Fdocs%2Fvoice.xml
// Note URL encoding above
let from = event.From || '+15095550100';
// If passing in To, make sure to validate, to avoid placing calls to unexpected locations
let to = event.To || '+15105550100';
let url = event.Url || 'http://demo.twilio.com/docs/voice.xml';
twilioClient.calls
.create({
url: url,
from: from,
to: to,
})
.then((result) => {
console.log('Call successfully placed');
console.log(result.sid);
return callback(null, 'success');
})
.catch((error) => {
console.log(error);
return callback(error);
});
};
I am working on unit testing an Express application. I am trying to mock up my external dependencies (Express, database, etc) and am close to a break through.
However, I am having issues with stubs not being called from within a .then() inside my business logic.
A couple of the methods I am attempting to test are the following:
/**
* Ping
* A simple endpoint to poke for testing.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
ping: (request, response, next) => {
let elapsed = Date.now() - request.start;
response.status(200).json({
request_started: new Date(request.start).toISOString(),
request_duration: `${elapsed} milliseconds`
});
},
/**
* Registration
* Allows for registration of a user name and password.
* #arg {*} request - Incoming request
* #arg {*} response - Outgoing response
* #arg {*} next - Next route in series
*/
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
this.model.registerUser(username, password).then(ret => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}).catch(err => {
response.status(400).send("Error processing registration request. Please try again.");
});
}
The model in register returns a Promise that wraps a call to a database and replies based on the outcome. I have a mock of this setup as follows:
mockModel: {
registerUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined') {
return Promise.resolve(pass === 'pass');
}
}),
getUser: sinon.stub().callsFake(function(user, pass) {
if (typeof user !== 'undefined' && pass === 'pass') {
return Promise.resolve({id: 9999});
} else {
return Promise.resolve(false);
}
})
}
I also have the response object mocked so I can pass it in and determine if it is called correctly:
mockRes: () => {
return {
status: sinon.stub().returnsThis(),
send: sinon.stub(),
json: sinon.stub()
};
}
The problem arises when I get to the tests:
describe('Register() method', function() {
this.beforeEach(function() {
req = mockReq(0, {}, {username: 'user', password: 'pass'});
res = mockRes();
base.BaseRoutes(router, null, base.methods, mockModel);
});
it('Returns a 201 Created message upon success', function() {
base.methods.register(req, res);
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
});
});
The test here fails with the following error:
1) Base Methods
Register() method
Returns a 201 Created message upon success:
AssertionError: expected stub to have been called with arguments 201
at Context.<anonymous> (test\spec.js:50:50)
Stepping through with a debugger shows that the method is getting called, yet I'm getting this failure.
Other tests in the same suite that leverage the same mocked request/response work correctly but they don't use Promise calls (example: ping() method)
I suspect that it has something to so with scoping, but I'm not sure where things are going wrong.
After running through this a few more times, I found that it was not a scope issue, but an asynchronous issue. The test case was completing before the Promise resolved/rejected.
The piece that I was missing to close the loop was that my Express route handlers needed to return the Promise that they created to handle the database call:
register: (request, response, next) => {
let username = request.body.username;
let password = request.body.password;
return this.model.registerUser(username, password).then((ret) => {
if (ret) {
response.status(201).send("Created");
} else {
response.status(400).send("Error processing registration request. Please try again.");
}
}, (err) => {
response.status(400).send("Error processing registration request. Please try again.");
});
},
And then the test, perform my assertions in the then() on the returned Promise:
it('Returns a 201 Created message upon success', function(done) {
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(201);
chai.expect(res.send).to.have.been.calledWith('Created');
}).then(done, done);
});
it('Returns a 400 message upon failure', function(done) {
req = mockReq(0, {}, {username: 'user', password: 'fail'});
base.methods.register(req, res).then(x => {
chai.expect(res.status).to.have.been.calledWith(400);
chai.expect(res.send).to.have.been.calledWith(sinon.match.string);
}).then(done, done);
While there were examples of passing the Promise to the test and handling it there and examples on testing Express route handlers in isolation, I couldn't find examples that combined the two and tested it. Hopefully this can be of service to someone else.
I am trying to create a simple REST API with NodeJS and Express without any database. I have stored all of my data in JSON files.
The data is in the form of an array of objects.
I have paths like fund-name/:portId
so I am doing this:
const fundName = require('./json/fund-name.json');
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
fundDetails.forEach(fund => {
if (fund.portId === portId) {
return res.json(fund);
}
return res.json([]);
});
});
when I hit the url http:localhost:3000/fund-details/1234, I get the following error:
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent
to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (/home/username/Desktop/data-server/node_modules/express/l
ib/response.js:767:10)
It works fine when I don't pass any path param to get all the funds.
Where am I going wrong??
This error is because you are using res.send() multiple time in single api call.
Correct way
if(a){
res.send()
}else{
res.send()
}
Wrong way
if(a){
res.send()
res.send()
}else{
res.send()
}
In your code.
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
fundDetails.forEach(fund => {
if (fund.portId === portId) {
return res.json(fund); // many or single times here
}
return res.json([]); // and here when fund !==portId here
});
});
You can try
app.get('/fund-details:portId', (req, res) => {
const portId = req.params.portId;
var flag
var data = []
fundDetails.forEach(fund => {
if (fund.portId === portId) {
flag=true
data.push(fund)
}
});
if(flag){
res.send(data);
}else{
res.send()
}
});
The method res.json(fund) is called per each item in fundDetails and then a further res.json([]) method is called. This leads to your response being send back multiple times (which it shouldn't happen, only 1 response per a single api call should be used).
I suggest that you use an array and push back objects with the matching port id and then send the array back to the user when the operation is completed. To be honest, you don't even need the flag variable to check if funds exists or not since if they don't, you empty data array is sent back.
var data = [];
fundDetails.forEach(fund => {
if (fund.portId === portId)
data.push(fund);
});
res.json(data);