Error/Exception handling in GraphqlExpress - node.js

I'm new to GraphQL and using graphqlExpress in my nodejs project.
const buildOptions = async (req, res) => {
const user = await authenticate(req, mongo.Users);
return {
formatError,
schema,
context: {dataloaders: buildDataloaders(mongo),mongo, user}, // This context object is passed to all resolvers.
};
};
app.use('/api', bodyParser.json(), graphqlExpress(buildOptions));
Resolver methods for Query, Mutations are implemented, but when I throw an Error object from any of the resolver methods then the error is not handled by graphqlExpress and node process is crashing. On debugging it stops at 'emitDestroyScript' method in async_hooks.js file of nodejs.
Any help to appreciated to address this issue.
Thanks

Related

Middleware for Firebase Callable Functions

With Firebase HTTP functions, we can install express and use middlewares. Middlewares are useful (among other things) for checking pre-conditions before functions execute. For example, we can check authentication, authorization, etc in middlewares so that they don't need to be repeated in every endpoint definition.
How are developers achieving the same thing with Firebase callable functions? How are you extracting out all functionality that would typically be in chained middlewares when you have a large number of callable functions?
It seems that there's no readily available middleware framework for callable functions, so inspired by this, I rolled my own. There are some general purpose chained middleware frameworks on NPM, but the middleware I need is so simple that it was easier to roll my own than to configure a library to work with callable functions.
Optional: Type declaration for Middleware if you're using TypeScript:
export type Middleware = (
data: any,
context: functions.https.CallableContext,
next: (
data: any,
context: functions.https.CallableContext,
) => Promise<any>,
) => Promise<any>;
Here's the middleware framework:
export const withMiddlewares = (
middlewares: Middleware[],
handler: Handler,
) => (data: any, context: functions.https.CallableContext) => {
const chainMiddlewares = ([
firstMiddleware,
...restOfMiddlewares
]: Middleware[]) => {
if (firstMiddleware)
return (
data: any,
context: functions.https.CallableContext,
): Promise<any> => {
try {
return firstMiddleware(
data,
context,
chainMiddlewares(restOfMiddlewares),
);
} catch (error) {
return Promise.reject(error);
}
};
return handler;
};
return chainMiddlewares(middlewares)(data, context);
};
To use it, you would attach withMiddlewares to any callable function. For example:
export const myCallableFunction = functions.https.onCall(
withMiddlewares([assertAppCheck, assertAuthenticated], async (data, context) => {
// Your callable function handler
}),
);
There are 2 middlewares used in the above example. They are chained so assertAppCheck is called first, then assertAuthenticated, and only after they both pass does your hander get called.
The 2 middleware are:
assertAppCheck:
/**
* Ensures request passes App Check
*/
const assertAppCheck: Middleware = (data, context, next) => {
if (context.app === undefined)
throw new HttpsError('failed-precondition', 'Failed App Check.');
return next(data, context);
};
export default assertAppCheck;
assertAuthenticated:
/**
* Ensures user is authenticated
*/
const assertAuthenticated: Middleware = (data, context, next) => {
if (!context.auth?.uid)
throw new HttpsError('unauthenticated', 'Unauthorized.');
return next(data, context);
};
export default assertAuthenticated;
As a bonus, here's a validation middleware that uses Joi to ensure the data is validated before your handler gets called:
const validateData: (schema: Joi.ObjectSchema<any>) => Middleware = (
schema: Joi.ObjectSchema<any>,
) => {
return (data, context, next) => {
const validation = schema.validate(data);
if (validation.error)
throw new HttpsError(
'invalid-argument',
validation.error.message,
);
return next(data, context);
};
};
export default validateData;
Use the validation middleware like this:
export const myCallableFunction = functions.https.onCall(
withMiddlewares(
[
assertAuthenticated,
validateData(
Joi.object({
name: Joi.string().required(),
email: Joi.string().email().required(),
}),
),
],
async (data, context) => {
// Your handler
},
),
);
Middleware for Firebase callable functions is not possible. Callable functions force your endpoint to use a certain path, a certain type of input (JSON via POST) and a certain type of output (also JSON). Express wouldn't really help you out, given the constraints of how callables work. You can read about all the callable protocol details in the documentation. You can see that callables abstract away all the details of the request and response, which you would normally work with when using Express.
As per this community answer,
HTTP requests to callable functions don't really come "from" a URL. They come from anywhere on the internet. It could be a web site, Android or iOS app, or someone who simply knows the protocol to call the function.
If you're building a web app and you want to pass along the URL of the page making the request, you'll have to add that data into the object that the client passes to the function, which shows up in data.
So unless you workaround that by sending the URL in the data of the callable function, it will not work. And even if you do, it just would go against the principle of callable functions, so I would recommend that you use HTTP Functions for that purpose.

Fastify - i18next in custom error handler

In my Node.js application I´m using fastify as framework together with some plugins (i18next).
I´m using i18next for translation (is working properly in preHandler and handler hooks) and want to customize all errors by using my translations via i18next in my custom error handler (via fastify setErrorHandler method).
Here is my coding so far (from top to bottom):
import fastify from "fastify";
import routes from "./routes/routes.js";
import i18next from "./config/i18next.js";
import i18nextMiddleware from "i18next-http-middleware";
const app = fastify({
logger: true,
ajv: {
customOptions: {
allErrors: true,
$data: true
}
},
});
app.register(i18nextMiddleware.plugin, { i18next });
app.register(routes, { prefix: '/api/v1' });
app.setErrorHandler((error, request, reply) => {
console.log('request.t: ', request.t);
if (error.validation) {
// other coding ...
reply.send(error);
}
});
(async () => {
try {
await app.listen(process.env.PORT);
console.log(`Server started listening on port ${process.env.PORT}`);
} catch (err) {
app.log.error(err);
}
})();
Inside the setErrorHandler (which is sync) I want to use the initialized t() method from i18next instance passed to request object (this is working for all my routes in the preHandler and handler hooks) but is not working in the setErrorHandler, as I´ll get undefined when an error occours.
I know the setErrorHandler is sync and all plugin registration will be handled async, but didn´t solved it yet.
What I´ve also tried is to do the setErrorHandler call in the after() hook when registering the i18next plugin, but the result is the same. I know I´m missing a small detail, but any tips are appreciated, as I´m spinning my head around this since hours.
This happens because the i18next-http-middleware plugin adds the t method to the request object on a preHandler hook that is executed after the JSON validation step:
export function plugin (instance, options, next) {
const middleware = handle(options.i18next, options)
instance.addHook('preHandler', (request, reply, next) => middleware(request, reply, next))
return next()
}
source
You should be able to write a workaround like this:
import i18nextMiddleware from "i18next-http-middleware";
// ....
const middleware = i18nextMiddleware.handle({})
app.addHook('preValidation', (request, reply, next) => middleware(request, reply, next))
I think that is a bug of the module tho

Can I make a fetch request from an express request handler or middleware?

Let's say I have an express api with an /api/load-post endpoint. That is handled by the loadPostHandler: RequestHandler
index.ts
const app = express();
app.get("/api/load-post", loadPostHandler);
loadPostHandler.ts
Can I make a fetch request from that handler?
import fetch from "cross-fetch";
export const loadPostHandler: RequestHandler = async (req, res) => {
// HANDLE BLOGPOST LOAD
res.json({ blogPost: blogPostData }) // RES JSON THE BLOGPOST DATA
await fetch("/api/updateViewcount?id=POST_ID"); // MAKE A FETCH REQUEST
};
Is this something people usually do? Or is this an anti-pattern? Not sure if this would even work.
Short answer
Yes, you can make requests in the api call handler in general, and it depends on the requirements of that api.
Longer version
Judging by your example: you want to update view count, and since there is no use of response of it, you don't need to await for the response. You can just fire it without await.
And structurally it would be better practice to move it to a separate function that make an actual call, or fire an event and handle it in a different place.
Moreover, it looks like you are calling the same api server, in that case it will be better just to call a function instead of the api call.
const updatePostViewcount = postId => {
// HANDLE BLOGPOST VIEWCOUNT UPDATE
}
export const loadPostHandler: RequestHandler = async (req, res) => {
// HANDLE BLOGPOST LOAD
// no await here because we don't need the response
// it will still run asynchronously
updatePostViewcount(POST_ID);
res.json({ blogPost: blogPostData }) // RES JSON THE BLOGPOST DATA
};

Detecting Mongoose/MongoDB error in Express route instead of Mongoose.connect callback

A number of our systems running Express/Node.js have implemented a /healthcheck endpoint that our load balancers and other monitoring systems can watch to detect when one of our applications is in trouble. Basically the endpoint checks connections to all downstream dependencies the app has, including MongoDB.
This issue is that when the /healthcheck endpoint tries to run its test transaction and it fails, there's no obvious way to catch the error in the endpoint so that I can have it report that the MongoDB dependency has an issue. Instead the server throws a 500 error for the /healthcheck request (while still detectable as an issue, is not as helpful).
This code shows basically what we're doing. It's not complete and runnable, but Express users with Mongoose experience should recognize it easily.
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const HealthcheckSchema = new Schema({
widget: {
type: String,
required: true,
unique: false,
},
createdAt: Date,
})
const HealthCheck = mongoose.model('Healthcheck', HealthcheckSchema, 'healthcheck')
const mongoCheck = () => Healthcheck.remove({}).exec()
.then(() => Healthcheck.create({widget: 'xyzpdq'}))
.then((results) => Healthcheck.findOne({widget: results.widget}).exec())
.then(() => true)
.catch(() => false)
const healthCheckEndpoint = (req, res) => {
let status = {}
Promise.all([mongoCheck()]) // normally there are other checks as well
.then(function (values) {
status.mongoUp = values[0]
return res.status(200).json(status) // always return 200; the payload gives the health status, not the HTTP status code
})
}
app.get('/healthcheck', healthCheckEndpoint)
In the main setup for the application, not specific to the healthcheck, we use mongoose.connect() with a callback. We have event handlers for various states like disconnected and error and such, and those events catch, but that doesn't help get the proper status in the /healthcheck endpoint.
I imagine I could use those event handlers to set state that the /healthcheck could pick up upon and return that, but I'd really prefer to make an actual query and use its success/failure as the result.
This all turned out to be a red herring. The issue here was that there was an Express middleware layer that was making use of Mongo before the /healthcheck endpoint was reached.
In this case it was a Mongo session manager.
The solution was to exclude the /healthcheck URL from the session middleware.
function excludeSessionsForStaticURLs (req, res, next) {
if (!new RegExp(/\/about|\/api\/heartbeat|\/healthcheck|\.css|\.js|\.jpg|\.png'/)
.test(req.originalUrl)) {
return sessionMiddleware(req, res, next)
}
return next()
}
app.use(excludeSessionsForStaticURLs)
So now that this exclusion has been put in place, the normal try/catch (async/await) and .catch() for Promises works and the status for Mongo is reflected as False when appropriate.

Define/Use a promise in Express POST route on node.js

I currently have a POST route defined in an Express Node.js application as so:
var locationService = require("../app/modules/locationservice.js");
app.post('/createstop', isLoggedIn, function(req, res) {
locationService.createStop(res, req.body);
});
(for this question, please assume the routing in & db works.. my record is created on form submission, it's the response I am struggling with)
In the locationservice.js class I then currently have
var models = require('../models');
exports.createStop = function(res, formData) {
models.location.build({ name: formData.name })
.save()
.then(function(locationObj) {
res.json({ dbResult : locationObj });
});
};
So as you can see, my route invokes the exported function CreateStop which uses the Sequelize persistent layer to insert a record asynchronously, after which I can stick the result on the response in the promised then()
So at the moment this only works by passing the response object into the locationservice.js method and then setting res.json in the then() there. This is sub-optimal to me with regards to my service classes, and doesn't feel right either.
What I would like to be able to do is "treat" my createStop method as a promise/with a callback so I can just return the new location object (or an error) and deal with it in the calling method - as future uses of this method might have a response context/parameter to pass in/be populated.
Therefore in the route I would do something more like:
var locationService = require("../app/modules/locationservice.js");
app.post('/createstop', isLoggedIn, function(req, res) {
locationService.createStop(req.body)
.then(dataBack) {
res.json(dataBack);
};
});
Which means, I could call createStop from else where in the future and react to the response in that promise handler. But this is currently beyond me. I have done my due diligence research, but some individual expert input on my specific case would be most appreciated.
Your locationservice.js could look like that
exports.createShop = function(data){
// here I have used create instead of build -> save
return models.location.create(data).then(function(location){
// here you return instance of saved location
return location;
});
}
And then your post() method should be like below
app.post('/createstop', isLoggedIn, function(req, res){
locationService.createShop(req.body).then(function(location){
// here you access the location created and saved in createShop function
res.json(location);
}).catch(function(error){
// handle the error
});
});
Wrap your createStop function with a promise like so:
exports.createStop = function(res, formData) {
return new Promise(function(resolve, reject) {
models.location.build({ name: formData.name })
.save()
.then(function(locationObj) {
resolve({ dbResult : locationObj });
});
//in case of error, call reject();
});
};
This will allow you to use the .then after the createStop within your router.

Resources