Correct way to utilize middleware functions in Express - node.js

Hi im creating a proxy endpoint using express with node js, i have it working correctly for one get request, but im wanting to setup multiple get/post request endpoints and wondering if what i have done below is the correct way to carry on to implement the other endpoints, im pretty new with NodeJS and express.
what ive tried is reusing
app.use(validateFirebaseIdToken);
and then app.use(new functionname) before i call the new get Endpoint, i need to reuse the decodedIDToken variable in each of my bigQuery methods, bear in mind there are going to be alot of different bigQuery query methods i will be creating to get data for the end user
var express = require('express')`
var app = express()
const validateFirebaseIdToken = async (req, res, next) => {`
console.log('Check if request is authorized with Firebase ID token');
if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&!(req.cookies && req.cookies.__session)) {
console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
'Make sure you authorize your request by providing the following HTTP header:',
'Authorization: Bearer <Firebase ID Token>',
'or by passing a "__session" cookie.');
res.status(403).send('Unauthorized');
return;
}
let idToken;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
console.log('Found "Authorization" header');
// Read the ID Token from the Authorization header.
idToken = req.headers.authorization.split('Bearer ')[1];
} else {
// No cookie
res.status(403).send('Unauthorized');
return;
}
try {
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
console.log('ID Token correctly decoded', decodedIdToken);
req.user = decodedIdToken;
next();
return;
} catch (error) {
console.error('Error while verifying Firebase ID token:', error);
res.status(403).send('Unauthorized');
return;
}
};
const runDailyCategorybigQuery = async (req, res, next) => {
const query = `select count(string_field_3) as Categories, Month(date_field_2) as Month from test.testing_api group by Month`;
const options = {
query: query,
// Location must match that of the dataset(s) referenced in the query.
location: 'US',
useLegacySql: true,
};
// Run the query as a job
const [job] = await bigquery.createQueryJob(options);
console.log(`Job ${job.id} started.`);
console.log(`ID ${req.user.user_id}`);
// Wait for the query to finish
const [rows] = await job.getQueryResults();
res.query = rows;
console.log('Rows:');
rows.forEach(row => console.log(row));
next();
};
const runCategoryMonthsbigQuery = async (req, res, next) => {
const query = `select count(string_field_3) as Categories, Month(date_field_2) as Month from test.testing_api group by Month`;
const options = {
query: query,
// Location must match that of the dataset(s) referenced in the query.
location: 'US',
useLegacySql: true,
};
// Run the query as a job
const [job] = await bigquery.createQueryJob(options);
console.log(`Job ${job.id} started.`);
console.log(`ID ${req.user.user_id}`);
// Wait for the query to finish
const [rows] = await job.getQueryResults();
res.query = rows;
console.log('Rows:');
rows.forEach(row => console.log(row));
next();
};
app.use(validateFirebaseIdToken);
app.use(runDailyCategorybigQuery);
app.get('/getCategories', (req, res) => {
res.json(res.query);
});
//what im thinking of doing
app.use(validateFirebaseIdToken);
app.use(runCategoryMonthsbigQuery);
app.get('/getCategoriesMonth', (req, res) => {
res.json(res.query);
});

What you listed as "what I am thinking of doing" will add the same middleware twice (for validateFirebaseIdToken). This is not necessary as middleware will be called on every request, so you should only add it once.
The second issue is that you are treating your request handlers like middleware. Since each request will be seen by all middleware (as long as they call next) then you will be running both queries and the second one will overwrite the first.
Move your two query middleware functions into request handlers instead. For example:
const runDailyCategorybigQuery = async (req, res) => {
...
res.json(res.query);
}
app.get('/getCategories', runDailyCategorybigQuery);

Related

How to mock req.session in express while testing API with jest and supertest?

What I'm trying to do is to test a REST API endpoint that validates requests with req.session (set by express-session).
Here are snippets of my code:
middleware.ts
export const validateSecurityCode = (req, res, next) => {
// How do I mock/spy on this "req.session.securityCode"?
if (req.session.securityCode !== req.body.securityCode) {
throw new BadRequestError('Wrong security code was provided');
}
return next();
};
api/item.ts
router.post('/item', [validateSecurityCode], async (req, res) => {
await createItem(req.body);
res.send({ message: 'DONE' });
});
As you can see, if req.session.securityCode !== req.body.securityCode, this endpoint has to throw an error.
Is it possible to mock/spy on req.session while testing the endpoint with jest and supurtest?
Below is what I'd like to achieve:
it('should pass security code check', async () => {
// " MOCK_REQ_SESSION('valid-code')" will ensure that "req.session.securityCode" will get 'valid-code'
validateSecurityCode = MOCK_REQ_SESSION('valid-code');
const response = await request(app).post('/api/item').send({
securityCode: 'valid-code',
// other request body values
});
expect(response.statusCode).toEqual(200);
});

Mongoose: return a custom object as response to a POST request

Sorry if the title isn't very clear, I didn't know how to phrase the problem I'm facing, in the title.
I have a signup form. The user submits the form and server checks in mongoose if the email already exists in the database. If it doesn't, it lets you continue with signup. I created some additional objects within the model.find function that I want to send as a response to the post request. How can I do that? Please take a look at the code for better understanding.
app.post('/signup', (req, res) => {
User.find({email: req.body.email})
.then(res => {
if (res.length === 0) {
console.log('didnt find the email')
const id = uuidv4()
const code = '1234'
//I want to send this object as a response to the post request
const obj = {id, code}
Model.create(obj)
return obj
//I tried sending the response from here but that doesn't work either.
//return res.json(obj)
} else {
console.log('Error')
}
})
//This return statement doesn't return the obj I want to send because it's not accessible by it.
return res.json(obj)
}
The solution was to use async function and then store the data of the User.find function in a variable. Then the variable can be used as a response to the post request.
app.post('/signup', async (req, res) => {
const var1 = await User.find({email: req.body.email})
.then(res => {
if (res.length === 0) {
console.log('didnt find the email')
const id = uuidv4()
const code = '1234'
//I want to send this object as a response to the post request
const obj = {id, code}
Model.create(obj)
return obj
} else {
console.log('Error')
}
})
return res.json(var1)
}

Express Cookie Session 'undefined'

After I define a cookie in an express cookie session, I can log it to the console with ease. However, when I attempt to access this cookie in another route of my application, it returns 'undefined'.
Setting the cookie:
router.get('/callback', catchAsync(async (req, res) => {
console.log("in /callback");
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
>>> RETURNS THE TOKEN CORRECTLY <<<
Attempting to access the cookie in another route:
router.get('/loggedin', catchAsync(async (req, res) => {
console.log("/loggedin");
console.log("token: " + req.session.token);
>>> RETURNS 'token: undefined' <<<
In the first router.get('/callback'..) the catchAsync() function is not declared globally. It just handle this specific route, and doesn't really require a name.
You should wrap this function inside a middleware or create a function which is available globally, I don't know what is the goal but here is the 2 option.
Option 1 initiate the functionality as a middleware. The behaviour is depends on where you place it!!!! Maybe in that case doesn't fully makes sense, but you can play around, but I think you will get it.
// if you put before your router initiation it is going to have effect on all of the routes
app.use(async(req, res, next) => {
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
//
// and you can do whatever want to do
// but have to call next
//
next()
})
// and your router looks like
router.get('/callback', (req, res) => {
// do what have to do
})
Option 2 - declare the middleware and use where you want
// defining this middleware somewhere in the code
const catchAsync = async(req, res, next) => {
if (!req.query.code) throw new Error('NoCodeProvided');
const code = req.query.code;
const creds = btoa(`${CLIENT_ID}:${CLIENT_SECRET}`);
var response = await fetch(`https://discordapp.com/api/oauth2/token?grant_type=authorization_code&code=${code}&redirect_uri=${redirect}`,
{
method: 'POST',
headers: {
Authorization: `Basic ${creds}`,
},
});
var json = await response.json();
req.session.token = json.access_token
console.log(req.session.token)
//
// and you can do whatever want to do
// but have to call next
//
next()
}
router.get('/callback', catchAsync, (req, res) => {
// do what have to do
})
router.get('/loggedin', catchAsync, (req, res) => {
// do what have to do
})

Calling an API endpoint from within another route in Node / Express

I have myRoute.js with a route (GET) defined and I want to call an api endpoint from another route (api.js), and I'm not sure what the right way to do this is. The api.js route is working properly (image and code below).
api.js
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
works as expected:
myRoute.js
I would like when a user goes to localhost:3000/USER_ID that the route definition gets information from the api. Psuedo code below (someFunction).
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
let fromApi = someFunction(`localhost:3000/getAllGroups/${uid}`); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Not sure if i understand you correct but anyway i will try to help. So you have an api like
router.get('/getGroups/:uid', function(req, res, next) {
let uid = req.params.uid;
db.getAllGroups(uid).then((data) => {
let response =[];
for (i in data) {
response.push(data[i].groupname);
}
res.status(200).send(response);
})
.catch(function (err) {
return err;
});
});
If you would like to reuse it you can extract a function from the code above like so:
async function getAllGroupsByUserId(uid){
const result = [];
try{
const data = await db.getAllGroups(uid);
for (i in data) {
result.push(data[i].groupname);
};
return result;
}
catch(e) {
return e;
}
}
And then reuse it in your api & anywhere you want:
router.get('/getGroups/:uid', async function(req, res, next) {
const uid = req.params.uid;
const groups = await getAllGroupsByUserId(uid);
res.status(200).send(groups);
})
Same you can do in your another route:
router.get('/:uid', async function(req, res, next) {
const uid = req.params.uid;
const fromApi = await getAllGroupsByUserId(uid); // <--!!!
console.log(fromApi) ; //expecting array
res.render('./personal/index.jade', {fromApi JSON stringified});
});
Seems like pretty clear :)
I would use fetch for this. You can replace someFunction with fetch, and then put the res.render code in a .then(). So, you would get this:
const fetch = require("node-fetch");
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}').then(res => res.json()).then(function(data) {
returned = data.json();
console.log(returned); //expecting array
res.render('./personal/index.jade', {JSON.stringify(returned)});
});
});
A more robust way with error handling would be to write something like this:
const fetch = require("node-fetch");
function handleErrors(response) {
if(!response.ok) {
throw new Error("Request failed " + response.statusText);
}
return response;
}
router.get('/:uid', function(req, res, next) {
let uid = req.params.uid;
fetch('localhost:3000/getAllGroups/${uid}')
.then(handleErrors)
.then(res => res.json())
.then(function(data) {
console.log(data) ; //expecting array
res.render('./personal/index.jade', {JSON.stringify(data)});
})
.catch(function(err) {
// handle the error here
})
});
The ideal way would be to abstract your code into a method so you aren't calling yourself, as The Reason said. However, if you really want to call yourself, this will work.

Node.js: Better way to resolve Promise from another API

Suppose I have two APIs, the first API is called by the client. Then it will call a API on the storage server to store some data. The storage server will return an response immediately with status code 202.
app.post('/create/:requestId', (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
axios.post('/store', { id: requestId });
});
After the data is successfully stored, the storage service will call another API on the server as a webhook to notify us that the data is stored.:
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
});
What I need is that, when the client calls POST /create/:requestId, it won't return until the data is successfully stored on the storage server. One way to do so is that:
const pendingRes = [];
app.post('/create/:requestId', async (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
const result = await axios.post('/store', { id: requestId });
if (result.data.success) {
pendingRes.push({ id: requestId, res });
}
});
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
const { requestId }= req.params;
const resToResolve = pendingRes.find(r => r.id === requestId);
const idx = pendingRes.findIndex(r => r.id === requestId);
if (resToResolve) {
// Response to the create API
resToResolve.res.json({ success: true });
pendingRes.splice(idx, 1);
}
// Response to the resolve API
res.json({ success: true });
});
I know this can solve the problem but I wonder if there is any better way to do that such as using Promise. I don't like this method because actually there are many similar APIs on the storage server and I need to create many arrays for saving pending res.
Of course, I cannot change the API on the storage server :(
You can create and await a Promise which will be resolved at /resolve/:requestId.
const requestId2resolve = new Map();
app.post('/create/:requestId', async (req, res) => {
const { requestId } = req.params;
// Call 3rd party API
const result = await axios.post('/store', { id: requestId });
if (result.data.success) {
await new Promise(resolve => requestId2resolve.set(requestId, resolve));
res.json({ success: true });
}
});
app.post('/resolve/:requestId', (req, res) => {
// Called by the storage server to notify the data is successfully stored
const { requestId }= req.params;
if (requestId2resolve.has(requestId)) {
const resolve = requestId2resolve.get(requestId);
resolve();
requestId2resolve.delete(requestId);
}
// Response to the resolve API
res.json({ success: true });
});

Resources