ApolloGateway: How to make a gql call after receiving a response - node.js

I have a simple ApolloGateway server, which receives a gql mutation. What I am trying to accomplish is to make another gql mutation upon receiving a success call from the mutation call.
I can see that I can use the method didReceiveResponse in order to handle the received body. However, I am not sure whether there is a better way to do it. Specially, I will need to parse the body, check for the request type, and then make a qgl query, and send an http request, etc.
class SomeRemoteGraphQLDataSource extends RemoteGraphQLDataSource {
async didReceiveResponse(response, req, context) {
const body = await super.didReceiveResponse(response, req, context);
// Do something
return body;
}
}

Related

Is it possible to modify the pug.js-parameter object in an express middleware-callback

Currently I'm on a legacy application using pug.js as view engine in a node.js express-app.
I want to implement a generic way to display feedback messages. I want to be able to display messages (successes, errors), even if the handler does reply with a redirect.
This is what I want:
handlePostRequest(req, res){
// do stuff with the post request
doStuff(req.body);
//This should be done of course somewhere else.
req.session.successes=req.session.successes|[];
//save some success-message for the user
req.session.successes.push("Your post has been saved. Thank you!");
//but reply with a 302
res.redirect(req.headers.referer);
}
//a get request. maybe the handler above redirected here
handleGetRequest(req,res){
// we do NOT get the successes here. Just the 'pure' data.
const renderData=getRenderData();
res.render('fancy-pug-template', renderData);
}
fancyMiddlewareForMessages(req, res, next){
//how to implement getRenderDataByBlackMagic()????
const renderData = getRenderDataByBlackMagic();
//set the messages
renderData.successes = req.session.successes;
//empty saved messages
req.session.successes = [];
next();
}
Obviously, I do not want to polute every handler which actually renders a template with some logic which retrieves the messages and adds them to the parameter object. I would like to move this cross-cutting concern in a middleware callback or something like that.
So, the question is: Can this be achieved? How? I'm fairly new to pug.js, maybe I'm overlooking something obvious.
Ok, I found a way. This is what I did:
const requestStorage = new AsyncLocalStorage<Request>();
function patchRenderFunction(req: Request, res: Response, next: NextFunction) {
const render = res.render;
res.render = function (view: string, options?: any, callback?: (err: Error, html: string) => void) {
const messages = new MessageManager(req);
//merge errorMessages
options.errorMessages = mergeMessageArrays(options.errorMessages, messages.errors);
//same for successMessages
options.successMessages = mergeMessageArrays(options.successMessages, messages.successes);
render.bind(this)(view, options, callback);
};
requestStorage.run(req, () => {
next();
});
}
export function applyAutomaticRenderAttributes(app: Express): void {
app.use(patchRenderFunction);
}
export function successMessage(message: string, req?: Request) {
if (!req) {
req = requestStorage.getStore();
}
if (!req) {
console.error('No request found in async storage. This should not happen. Please report this issue. (successMessage)');
return;
}
new MessageManager(req).addSuccessMessage(message);
}
//export function errorMessage(...) omitted
The MessageManager uses the requests session to store messages. It also filters them in some respect. I'm using the session because the application runs clustered (thank you, pm2). Since the session is stored in the db via express-mysql-session, I avoid problems with non-sticky sessions.

Call Express router manually

Нello! I am looking to call a function which has been passed to an expressRouter.post(...) call.
This expressRouter.post(...) call is occurring in a file which I am unable to modify. The code has already been distributed to many clients and there is no procedure for me to modify their versions of the file. While I have no ability to update this file for remote clients, other developers are able to. I therefore face the issue of this POST endpoint's behaviour changing in the future.
I am also dealing with performance concerns. This POST endpoint expects req.body to be a parsed JSON object, and that JSON object can be excessively large.
My goal is to write a GET endpoint which internally activates this POST endpoint. The GET endpoint will need to call the POST endpoint with a very large JSON value, which has had URL query params inserted into it. The GET's functionality should always mirror the POST's functionality, including if the POST's functionality is updated in the future. For this reason I cannot copy/paste the POST's logic. Note also that the JSON format will never change.
I understand that the issue of calling an expressjs endpoint internally has conventionally been solved by either 1) extracting the router function into an accessible scope, or 2) generating an HTTP request to localhost.
Unfortunately in my case neither of these options are viable:
I can't move the function into an accessible scope as I can't modify the source, nor can I copy-paste the function as the original version may change
Avoiding the HTTP request is a high priority due to performance considerations. The HTTP request will require serializing+deserializing an excessively large JSON body, re-visiting a number of authentication middlewares (which require waiting for further HTTP requests + database queries to complete), etc
Here is my (contrived) POST endpoint:
expressRouter.post('/my/post/endpoint', (req, res) => {
if (!req.body.hasOwnProperty('val'))
return res.status(400).send('Missing "val"');
return res.status(200).send(`Your val: ${req.body.val}`);
});
If I make a POST request to localhost:<port>/my/post/endpoint I get the expected error or response based on whether I included "val" in the JSON body.
Now, I want to have exactly the same functionality available, but via GET, and with "val" supplied in the URL instead of in any JSON body. I have attempted the following:
expressRouter.get('/my/get/endpoint/:val', (req, res) => {
// Make it seem as if "val" occurred inside the JSON body
let fakeReq = {
body: {
val: req.params.val
}
};
// Now call the POST endpoint
// Pass the fake request, and the real response
// This should enable the POST endpoint to write data to the
// response, and it will seem like THIS endpoint wrote to the
// response.
manuallyCallExpressEndpoint(expressRouter, 'POST', '/my/post/endpoint', fakeReq, res);
});
Unfortunately I don't know how to implement manuallyCallExpressEndpoint.
Is there a solution to this problem which excludes both extracting the function into an accessible scope, and generating an HTTP request?
This seems possible, but it may make more sense to modify req and pass it, rather than create a whole new fakeReq object. The thing which enables this looks to be the router.handle(req, res, next) function. I'm not sure this is the smartest way to go about this, but it will certainly avoid the large overhead of a separate http request!
app.get('/my/get/endpoint/:val', (req, res) => {
// Modify `req`, don't create a whole new `fakeReq`
req.body = {
val: req.params.val
};
manuallyCallExpressEndpoint(app, 'POST', '/my/post/endpoint', req, res);
});
let manuallyCallExpressEndpoint = (router, method, url, req, res) => {
req.method = method;
req.url = url;
router.handle(req, res, () => {});
};
How about a simple middleware?
function checkVal(req, res, next) {
const val = req.params.val || req.body.val
if (!val) {
return res.status(400).send('Missing "val"');
}
return res.status(200).send(`Your val: ${val}`);
}
app.get('/my/get/endpoint/:val', checkVal)
app.post('/my/post/endpoint', checkVal)
This code isn't tested but gives you rough idea on how you can have the same code run in both places.
The checkVal function serves as a Express handler, with request, response and next. It checks for params first then the body.

Error: Can't set headers after they are sent because of res.?

I'm trying to set up a method that is called with Shopify's webhook. I get the data and I'm able to store with a fresh server but I get "Error: Can't set headers after they are sent" returned in the console. I believe this is because I'm calling res twice. Any ideas on how to structure this better?
This is my method:
function createProductsWebHook(req,res,next) {
//if(req.headers){
// res.status(200).send('Got it')
// return next()
// }
res.sendStatus(200)
next()
const productResponse = req.body
console.log(productResponse)
const product = Product.build({
body_html: req.body.body_html,
title: req.body.title,
});
product.save()
.then(saveProduct => res.json(saveProduct))
.catch((e)=> {
console.log(e)
});
}
This occurs because the middleware, createProductsWebHook(), is called first when a request is received, which then sends a 200 status code response, res.sendStatus(200). Then in, in the same middleware function, product.save().then(...) is called. save()’s callback function attempts to send a response too – after one has already been sent by the very same middleware – using res.json(saveProduct).
Key Takeaway
Middleware should not send the response; this defeats the purpose of middleware. Middleware's job is to decorate (add or remove information, i.e, headers, renew some auth session asynchronously, perform side effects, and other tasks) from a request or response and pass it along, like a chain of responsibility, not transmit it – that's what your route handler is for (the one you registered your HTTP path and method with, e.g., app.post(my_path, some_middleware, route_handler).

Is there an alternative for recursive approach I use here in node.js?

I implemented a recursive function in a requestHandler I made to serialize API requests and also to make sure the endpoint isn't currently being requested. To make sure that the endpoint isn't currently being requested, I add it to a Set and verify it with conditionals.
Problem is that this recursive approach consumes quite a lot of memory when a lot of requests are made to the same endpoint. Is there any way I could make it less memory intensive as well as performant at the same time? I would love to hear any alternative approach which I could use instead of recursion. Below you can find my code.
async request(endpoint, domain, method, headers, query, body, attachments) {
const requestURL = `${(domain === "discord") ? this.discordBaseURL :
(domain === "trello") ? this.trelloBaseURL : domain}/${endpoint}`;
if (this.queueCollection.has(endpoint) === false) { // queueCollection is the Set in which I store endpoints that are currently being requested by my requestHandler.
this.queueCollection.add(endpoint);
const response = await this.conditionalsHandler(endpoint, requestURL, method, headers, query, body, attachments);
this.queueCollection.delete(endpoint);
return response;
}
else {
const response = new Promise((resolve) => {
setTimeout(() => { // https://stackoverflow.com/a/20999077
resolve(this.request(endpoint, domain, method, headers, query, body, attachments)); // This is where I make the method recursive to call itself back until the endpoint is no longer in the queueCollection Set.
}, 0);
});
return response;
}
}
Yes, you can remove the recursion by making the queueCollection a Map<string, Promise> instead of a Set<string>, and instead of recursing asynchronously and polling the queue until it's empty, chain the request to the tail of the queue if it exists like this:
async request(endpoint, domain, method, headers, query, body, attachments) {
const requestURL = `${(domain === "discord") ? this.discordBaseURL :
(domain === "trello") ? this.trelloBaseURL : domain}/${endpoint}`;
// get existing queue or create a new one
const queue = this.queueCollection.get(endpoint) || Promise.resolve();
// schedule request on the tail of the queue
const request = queue.then(
() => this.conditionalsHandler(endpoint, requestURL, method, headers, query, body, attachments)
);
// prevent errors from propagating along the queue
const tail = request.catch(() => {});
// enqueue the request
this.queueCollection.set(endpoint, tail);
try {
// propagates error handling to consumer
// waits for request to settle before executing finally block
return await request;
} finally {
// only remove promise from Map if this settled request is at the tail of the queue
if (this.queueCollection.get(endpoint) === tail) this.queueCollection.delete(endpoint);
}
}
This approach allows request to throw without breaking the chain so the consumer can handle the error and all the requests will still happen in sequence without depending on previous requests being successful, and it will always clean up the queueCollection on the last pending request regardless of whether the request throws. The await is not redundant here for that reason.

Node typescript global service to return value from a function

I'm trying to implement a service for my backend that allows the session data to be called from anywhere in the code, which means I want to create a service file that exports the values from the functions that get the session data. Otherwise I can only get the session data from inside functions that have both req: Request and res: Response parameters. So I'm basically trying to lock the values to a variable that can be used and called from anywhere in my project. My code now looks like this but if I use the exports anywhere else in the file, it just prints the actual code (function) snippet instead of the return value specified inside the code. I'm pretty new to typescript and node in general which means I might be doing some really silly errors here.
Thanks for all the help in advance!
/Victor
import { Request, Response, NextFunction } from "express";
function getUserSessionData(req: Request, res: Response) {
const userData = res.status(200).send(req.session.userData);
return userData;
}
function getUserSessionLang(req: Request, res: Response) {
const userLang = res.status(200).send(req.session.language);
return userLang;
}
function getUserSessionAll(req: Request, res: Response) {
const userAll = res.status(200).send(req.session);
return userAll;
}
module.exports = {
userData: getUserSessionData,
userLang: getUserSessionLang,
userAll: getUserSessionAll
};
How I would like it to work:
const sessionService = require("./services/sessionService");
function getStuff(req: Request, res: Response, next: NextFunction) {
let redisKey;
if (!req.session.language) {
redisKey = "getStuffEN";
}
else {
redisKey = "getStuff" + sessionService.userLang;
}
console.log(redisKey);
getRedis(redisKey, function success(reply: any) {
res.status(200).json(
JSON.parse(reply)
);
}, function error(err: Error) {
console.log("Something went wrong");
});
}
This is how it is right now (and working)
function getStuff(req: Request, res: Response, next: NextFunction) {
let redisKey;
if (!req.session.language) {
redisKey = "getStuffEN";
}
else {
redisKey = "getStuff" + req.session.language;
}
console.log(redisKey);
getRedis(redisKey, function success(reply: any) {
res.status(200).json(
JSON.parse(reply)
);
}, function error(err: Error) {
console.log("Something went wrong");
});
}
I want it to work like the first example, since there are some instances in my code where I want to access the data without having to pass the req, res parameters, if possible.
First, a short explanation on sessions:
When a user logs in (and you verify his credentials, etz) you start a new session for that user.
The middleware you're using will assign this session a unique ID and you can assign some data to it.
The ID is transferred to the users Browser in form of a cookie
The Data is stored on the Server (in Redis for your case)
The middleware will check if a session-cookie with a valid ID is included in a request and do the following:
Fetch Session-Data for the given ID from Redis
Populate the req.session-object with the Data from Redis
Call the next Route
With this out of the way, a word of advice: Don't store the session-data in your applications memory. Why? The data should only be relevant in the context of a request from a user. You'll need the session data to handle the request, but you don't need it otherwise.
Instead of storing it globally, build your handlers and functions to accept the specific Session-Data as parameters, like this:
// handler, after middleware:
const getUserBio = (req, res) => {
const userId = req.session.userId;
const bioData = fetchBio(userId);
res.render("userBio", bioData);
}
// somewhere else in your code
function fetchBio(userId) {
const fullBio = database.fetchBio(userId);
return {
full: fullBio,
excerpt: fullBio.substring(0, 24);
}
}
This has two important advantages:
You don't have to keep the session-Data in your memory synchronized with the one in Redis
These (almost) pure functions make your code easier to understand
If you write functions that work entirely on their input parameters and don't use any global state, things like "order in which to call functions" or "check if data is available" become irrelevant. The caller is responsible for getting the data, the callee is responsible for working with it.
Extending on that, express routes should never use any global in-memory data, if you ever want to scale your application horizontally (by using multiple instances). It can't be guaranteed that the same client will always connect to the same server-instance, so the globally stored data might be available for one request but not for the next. In this case, you'll have to find a way to share the global data between all your instances, which is what Redis already does in your case.
tl;dr: Only access the session-data in a request-handler, then pass it on as parameters to any functions that need to work on it. Don't keep global in-memory state in your server if you ever want to scale it horizontally.
You can write data to a db or to a file as it say madvic, but we can use also global variables. But global variables is a bad practise, as I know, so It's better to write someone your data.

Resources