How to end process on specific error in fastify using setErrorHandler? - fastify

In my app I'm connecting to a database and then initialize it. I have two custom plugins for that. First plugin connects to DB, and then it registers the second plugin, which initializes the DB (creates tables, domains etc.)
I want to stop process if an error happens inside those two plugins, since without DB connection and initialization the app won't work.
In app.ts I have
const fastify = Fastify({ logger: true });
fastify.register<ConnectAndInitDBConfig>(ConnectAndInitDB, {
config,
initSqlPath: 'assets/init.sql',
});
Here is the first plugin ConnectAndInitDB
const ConnectAndInitDB: FastifyPluginCallback<ConnectAndInitDBConfig> = (
fastify,
{ config, initSqlPath },
done
) => {
fastify.register(fastifyPostgres, config); // this connects to the DB (fastify-postgres NPM package)
fastify.after(err => {
if (err) {
fastify.log.error(err);
return done(err);
}
console.log('DB Connected.');
});
fastify.register(InitDB, { initSqlPath }); // this initializes the DB
fastify.after(err => {
if (err) {
fastify.log.error(err);
return done(err);
}
console.log('DB Initialized.');
});
fastify.setErrorHandler(function errorHandler(error) {
console.log(`got error`); // this is never called, even if an error happens during initialization of the DB
});
done();
};
Here is the second plugin InitDB
const InitDB: FastifyPluginCallback<{ initSqlPath: string }> = async (
fastify,
{ initSqlPath }: { initSqlPath: string },
done
) => {
try {
const initSql = await (
await promisify(readFile)(join(resolve(), initSqlPath))
).toString();
await fastify.pg.pool.query(initSql);
console.log({ initSql });
} catch (err: Error | unknown) {
return done(err as Error);
}
done();
};
When the error happens inside the InitDB plugin, I see it's logged by fastify logger, but I'm not able to catch it inside the setErrorHandler.
How do I catch any error that happens exactly inside my custom plugins, and let fastify handle all other errors?

You don't need the errorHandler because it is triggered for HTTP errors, not for the fastify server startup errors.
It is quite simple to archive your needs:
// when there is an async func, the `done` arg must be removed
const ConnectAndInitDB = async function (fastify, { config, initSqlPath }) {
await fastify.register(fastifyPostgres, config)
console.log('DB Connected.')
try {
await fastify.register(InitDB, { initSqlPath })
console.log('DB Initialized.')
} catch (error) {
console.log(`got error`)
throw error // bubble up the error so fastify server will not start
}
}

Related

Question about the syntax of a function in Node.js

I am following a tutorial to make a blog, and for the MongoDB connection in the server.js file, the instructor made a boiler connection function withDB. Operations and res are props of withDB function. In line 6, is operations a function passed a prop of the withDB functions?
Below is the withDB function.
const withDB = async (operations, res) => {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('my-blog');
await operations(db); // is operations a function that takes db as its props?
client.close();
} catch (error) {
res.status(500).json({ message: 'Error connecting to db', error });
}
}
Using withDB in a function
app.get('/api/articles/:name', async (req, res) => {
withDB(async (db) => {
const articleName = req.params.name;
const articleInfo = await db.collection('articles').findOne({ name: articleName })
res.status(200).json(articleInfo);
}, res);
})
yes actually operations is your callback function, you call it with db as param once you initialize your database connection.
Maybe you're not comfortable with ES6 arrow function syntax. you can find in Mdn doc a simple example with old regular function, and in your case it could be :
function findArticlesByName(articleName) {
return function(db) {
return db.collection('articles').findOne({ name:
articleName });
}
}
async function withDB(callback) {
try {
const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
const db = client.db('my-blog');
return callback(db);
} catch (error) {
throw new Error({ message: 'Error connecting to db', error });
} finally {
client?.close();
}
}
app.get('/api/articles/:name', async (req, res) => {
try {
const articleInfo = await withDB(findArticlesByName(req.params.name));
res.status(200).json(articleInfo);
} catch(error) {
res.status(500).json(error);
}
})
Conclusion, you could easily inline your callback function like in your example, but maybe it's more understandable that way.
Moreover, you should avoid to use a wrapper in order to create and close your db connection after each request, because it could occur some weird errors. Databases connections or any resource-intensive tasks should be shared as much as possible.
So a better solution is to create a specific class with the default implementation of your singleton, construct a single instance of your connection at the top of your app, pass the single instance into each module that needs it then close your connection just before exiting your app.
Hope it'll help.

Handing throw using jest and supertest

I am trying to handle exception use case with supertest and jest for the given code. I tried many things but unable to find the solution.
var serverless = require('serverless-http');
import express from 'express';
const app = express();
app.use(express.json());
app.post('/post', async (req, res) => {
try {
// Get user from dynamodb
const id = 'random-user-id';
const user = await DynamoDb.Scan({
TableName: 'user_table',
FilterExpression: 'id = :id',
ExpressionAttributeValues: {
':id': id,
},
}).catch(async (err) => {
throw err;
});
console.log('user', user);
return res.status(200).send();
} catch (exception) {
// Trying to handle this
throw exception;
}
});
module.exports.handler = serverless(app);
Test case
test("should throw error on failing to get user from database", async () => {
mockScan.mockImplementation(() => {
return {
promise() {
return Promise.reject({});
},
};
});
const response = await request
.post(`/post`)
.send();
});
On running the test, it is getting stuck and leading to
Jest did not exit one second after the test run has completed. This
usually means that there are asynchronous operations that weren't
stopped in your tests. Consider running Jest with
--detectOpenHandles to troubleshoot this issue.

Node.js and Mocha unit testing rollback transaction after each test

I'm using node v10.16.0 and npm packages mssql 5.1.0 and mocha 6.2.1.
What I want to do is write a sql unit test helper class (ES6) that handles transactions and rolls back after each unit test.
db.js
const mssql = require("mssql");
const config = require("./config");
class DbAccess{
constructor(){
this.connection = new mssql.ConnectionPool(config.dbConfig);
this.connection.connect(err => {
// ...
})
this.transaction = new mssql.Transaction(this.connection);
}
beginTransaction(){
this.transaction.begin();
}
rollbackTransaction(){
this.transaction.rollback();
}
executeSql (sql, callback) {
this.connection.connect().then(() => {
var req = new mssql.Request(conn);
req.query(sql).then((recordset) => {
callback(recordset);
})
.catch ((err) => {
console.log(err);
logger.error(err);
callback(null, err);
});})
.catch ((err) => {
console.log(err);
logger.error(err);
callback(null, err);
});
}
}
test.js
before(() => {
dbAccess.beginTransaction();
});
after(() => {
dbAccess.rollbackTransaction();
});
describe('Test',function(){
describe('SelectAll()', function(){
it('returns all',function(){
dbAccess.insertrecords();
});
});
});
That setup is not working as I'm getting a UnhandledPromiseRejectionWarning: ConnectionError: Connection not yet open.
I can't figure out how to
Begin the transaction (in the before handler),
Execute the sql queries (in the test, e.g. inserting records),
Rollback the transaction (in the after handler)

Handling node http.Server#listen() error with callback

I am trying to handle nodejs http.Server listen error with callback. But the following example will throw.
If I wrap the server.listen function into a try ... catch it works and the callback get called with the error.
Can You please enlighten me, why it doesn't work without try ... catch?
Is there any better way to catch the listen error?
const { createServer } = require('http')
function build(handler, callback) {
const server = createServer((req, res) => handler)
// this will throw RangeError
server.listen(99999, function (err) {
if (err) return callback(err, null)
return callback(null, server)
})
})
Edit: node version: 10.12.0
Answered for Node 13:
If you are expecting errors in the callback, I think this is incorrect. I think specifically in this case, the callback is not called if there is an error.
The key is to instead listen for the 'error' event to be triggered:
async function build(handler, callback) {
const server = createServer((req, res) => handler);
try {
let startFinished = false;
await new Promise((resolve, reject) => {
server.listen(3000, () => {
if (!startFinished) {
startFinished = true;
resolve();
}
});
server.once('error', (err) => {
if (!startFinished) {
startFinished = true;
console.log(
'There was an error starting the server in the error listener:',
err
);
reject(err);
}
});
});
return callback(null, server);
} catch (e) {
// Do with the error e as you see fit
console.log('There was an error starting the server:', e);
}
}
in general node will throw a range error if provided function arguments are out of expected range, you can try/catch in your build function like so and handle it there
function build(handler, callback) {
try {
const server = createServer(handler);
// this will throw RangeError
server.listen(99999, function (err) {
callback(err, server);
});
} catch (e) {
if (e instanceof RangeError) {
// handle your special range rrrors
} else {
// handle all your other errors
}
}
};
also you can pass your handle directly to the createServer, and for you error callback you don't need to return the result of your callback
I liked Charley's answer the best. Allyraza's answer caught the error, but directly to the console as well. Charley's answer bugged me though with the extra variable "startFinished". I found I could avoid this and have the best of both worlds by using err appropriately.
async function build(handler, callback) {
// Creating http Server
const server = http.createServer(handler);
// The key is to instead listen for the 'error' event to be triggered:
// listen EADDRINUSE: address already in use :::3000
try {
await new Promise((resolve, reject) => {
// Listening to http Server
server.listen(3000, (err) => {
if (!err)
resolve();
});
server.once('error', ( err ) => {
if (err) {
//console.log( 'There was an error starting the server in the error listener:', err);
reject(err);
}
});
});
// All good
return callback(null, server);
} catch (e) {
// Return to caller any errors
// console.log('There was an error starting the server:', e );
return callback(e, server);
}
}
// Setting up PORT
const PORT = process.env.PORT || 3000;
build(requestListener, (e, server) => {
if (e) {
console.log(`startServer failed: ${ e }` );
} else {
console.log(`Started server. Listening on PORT: ${ PORT }...` );
}
});

Nodejs promises catch "hell" then using sequelize orm

Currently I developing web app for nodejs using popular sequelize orm and typesciprt. Here is a example from my code
this.createNewGame(player.idPlayer).then((game) => {
this.getBestScore(player.idPlayer).then((bestScore) => {
defer.resolve({
idGame: game.idGame,
bestScore: bestScore
});
}).catch((error) => { defer.reject(error); });
}).catch((error) => { defer.reject(error); });
Here is one of the method
private getBestScore(idPlayer: number): Q.Promise<number> {
var defer = this.q.defer<number>();
GameModel.max<number>('score', { where: { 'idPlayer': idPlayer } }).then((result) => {
defer.resolve(result);
}).catch((error) => { defer.reject(error); });
return defer.promise;
}
I use catch in every method implementation and also in every call to method. I would like to have only one catch block in my expressjs router. I tried code like this and it works just fine, here is example:
//code in GameService class ...
getData(): Q.Promise<number> {
var defer = this.q.defer<number>();
this.method1().then((result) => {
defer.resolve(result);
});
return defer.promise;
}
private method1(): Q.Promise<number> {
var defer = this.q.defer<number>();
throw 'some error occurs here';
return defer.promise;
}
//router call GameService
router.get('/error-test', (req: express.Request, res: express.Response) => {
gameService.getData().then((result) => {
res.json(result);
}).catch((error) => { res.send(error); });
//works fine, here I get my thrown error
});
But in my previous example I need to use catch blocks everywhere otherwise If I get Unhandled rejection SequelizeDatabaseError or any other Unhandled rejection, nodejs stops working. Why I can't use only one catch block in my expressjs router when using calls to db with sequalize, like in my first example?
Sequelize operations return promises, so there is no reason to put Q into the mix. This is equivalent
private getBestScore(idPlayer: number): Q.Promise<number> {
return GameModel.max<number>('score', { where: { 'idPlayer': idPlayer } });
}
It doesn't return a Q.Promise (sequelize uses bluebird), but the implementations should be interoperable, since they are both 'thenables'.

Resources