How do I prevent Service running ... msg from getting logged first? I would like the messages inside testDBConnection fn. to be logged first instead. When DB is not running I would like the Looks like DB is not running msg to be kept getting logged and once the DB kicks in the DB connection has been established and Service running ... msgs should follow. I tried multiple things, but I was not able to come up with proper code. Thanks for your help.
index.js
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, () => {
initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool;
const testDBConnection = (client) => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
logger.info('DB connection has been established');
clearInterval(intervalId);
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
testDBConnection(pool);
}
};
export const getDb = () => pool;
You could use async/await for that.
import app from './config/express';
import config from './config/config';
import logger from './config/winston';
import { initDbConnection } from './server/db';
app.listen(config.port, async () => {
await initDbConnection();
logger.info(`Service running and listening on port ${config.port}`);
});
db.js:
import knex from 'knex';
import config from '../config/config';
import logger from '../config/winston';
const { db } = config;
let pool, connected;
const testDBConnection = (client) => {
return new Promise(resolve => {
const intervalId = setInterval(async () => {
try {
await client.select(1);
if (connected) {
return;
}
connected = true;
logger.info('DB connection has been established');
clearInterval(intervalId);
resolve('success');
} catch (error) {
logger.error('Looks like DB is not running');
}
}, 2000);
});
};
export const initDbConnection = (mock) => {
if (mock) {
pool = knex({});
} else {
pool = knex({
client: 'pg',
version: '7.4.2',
connection: db,
debug: true
});
return testDBConnection(pool);
}
};
export const getDb = () => pool;
This way, the logger inside the app.listen cb won't be called until the initDbConnection is resolved. Another way would be to just use the promise then.
Related
I am working with a RabbitMQ connection and exporting the channel after creating the same to import it in the routes file in my nodejs code.
But for some reason, it is undefined when imported in the routes.js file.
Export
let connection;
let channel;
const connect = async () => {
try {
const amqpServer = 'amqp://localhost:5672';
connection = await amqp.connect(amqpServer);
channel = await connection.createChannel();
await channel.assertQueue('product');
console.log('Product service connected to RABBITMQ');
} catch (error) {
console.log(error);
}
};
connect();
module.exports.connection = connection;
module.exports.channel = channel;
routes.js
const { channel } = require('./index');
console.log(channel) // 👈 undefined
Idk what is it that I am missing here.
CommonJS require is synchronous so you should write something like this:
Export
const connect = async () => {
try {
const amqpServer = 'amqp://localhost:5672';
const connection = await amqp.connect(amqpServer);
const channel = await connection.createChannel();
await channel.assertQueue('product');
console.log('Product service connected to RABBITMQ');
return [connection, channel];
} catch (error) {
console.log(error);
}
};
module.exports = connect();
routes.js
const [connection, channel] = await require('./index');
console.log(channel) // 👈 defined 🎉🎉🎉
Try it this way
let connection;
let channel;
const connect = async () => {
try {
const amqpServer = 'amqp://localhost:5672';
connection = await amqp.connect(amqpServer);
channel = await connection.createChannel();
await channel.assertQueue('product');
console.log('Product service connected to RABBITMQ');
} catch (error) {
console.log(error);
}
};
connect();
exports.connection = connection;
exports.channel = channel;
The following code constructs a redis client and exports. I am fetching the redis password from vault secret management service and that call is a promise/async. The code doesnt wait for that call and it exports the redis client before async call completes. I am not sure what I am doing wrong here. Any idea?
import redis from 'redis';
import bluebird from 'bluebird';
import logger from '../logger';
import srvconf from '../srvconf';
import { getVaultSecret } from '../services/vault.service';
const vaultConfig = srvconf.get('vault');
bluebird.promisifyAll(redis);
let redisUrl = '';
const maskRedisUrl = (url) => url.replace(/password=.*/, 'password=*****');
const setRedisUrl = (host, port, pw) => {
const pwstring = pw ? `?password=${pw}` : '';
const url = `redis://${host}:${port}${pwstring}`;
console.log(`Setting redis_url to '${maskRedisUrl(url)}'`);
return url;
}
if (vaultConfig.use_vault) {
(async () => {
const secret = await getVaultSecret(`${vaultConfig.redis.secrets_path + vaultConfig.redis.key}`)
redisUrl = setRedisUrl(srvconf.get('redis_host'), srvconf.get('redis_port'), secret.PASSWORD);
})().catch(err => console.log(err));
} else {
if (!srvconf.get('redis_url')) {
redisUrl = setRedisUrl(srvconf.get('redis_host'), srvconf.get('redis_port'), srvconf.get('redis_password'));;
} else {
redisUrl = srvconf.get('redis_url');
console.log(`Found redis_url ${maskRedisUrl(redisUrl)}`);
}
}
const options = redisUrl
? { url: redisUrl }
: {};
const redisClient = redis.createClient(options);
redisClient.on('error', err => {
logger.error(err);
});
export default redisClient;
The problem is that (async () => {...})() returns a Promise and you are not awaiting it at the top-level, so the script continues to run past that line, sets options = {} and returns the redisClient.
What you need is a top-level await which is enabled by default in Node versions >= 14.8.0. However, if your project uses a version older than that, there is a workaround as shown below.
Please note that the below code is NOT tested since I do not have the same project setup locally.
Module
import redis from "redis";
import bluebird from "bluebird";
import logger from "../logger";
import srvconf from "../srvconf";
import { getVaultSecret } from "../services/vault.service";
const vaultConfig = srvconf.get("vault");
bluebird.promisifyAll(redis);
let redisUrl = "";
let redisClient = null;
const initRedisClient = () => {
const options = redisUrl ? { url: redisUrl } : {};
redisClient = redis.createClient(options);
redisClient.on("error", (err) => {
logger.error(err);
});
};
const maskRedisUrl = (url) => url.replace(/password=.*/, "password=*****");
const setRedisUrl = (host, port, pw) => {
const pwstring = pw ? `?password=${pw}` : "";
const url = `redis://${host}:${port}${pwstring}`;
console.log(`Setting redis_url to '${maskRedisUrl(url)}'`);
return url;
};
(async () => {
if (vaultConfig.use_vault) {
try {
const secret = await getVaultSecret(
`${vaultConfig.redis.secrets_path + vaultConfig.redis.key}`
);
redisUrl = setRedisUrl(
srvconf.get("redis_host"),
srvconf.get("redis_port"),
secret.PASSWORD
);
} catch (err) {
console.log(err);
}
} else {
if (!srvconf.get("redis_url")) {
redisUrl = setRedisUrl(
srvconf.get("redis_host"),
srvconf.get("redis_port"),
srvconf.get("redis_password")
);
} else {
redisUrl = srvconf.get("redis_url");
console.log(`Found redis_url ${maskRedisUrl(redisUrl)}`);
}
}
// Initialize Redis client after vault secrets are loaded
initRedisClient();
})();
export default redisClient;
Usage
At all places where you import and use the client, you always need to check if it is actually initialized successfully, and throw (and catch) a well defined error if it is not.
const redisClient = require("path/to/module");
...
if (redisClient) {
// Use it
} else {
throw new RedisClientNotInitializedError();
}
...
I am trying to initialize my mongodb in a separate file. I am writing the initialization inside a function and exporting the function. Now, I also want to export the db which is written inside that function.How can I do that?
I want to do something like this.
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
const db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
export default {
connectDB,
db,
};
If it were me, I'd avoid the default export and do the following:
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
export let db;
export const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
or if you wanted a nicer error
import { MongoClient } from 'mongodb';
import config from '../../config';
import logger from '../../logger';
let db;
export const getDB = () => {
if (!db) {
throw new Error("DB Not yet connected");
}
return db;
}
export const connectDB = async () => {
const client = new MongoClient(config.dbUri, {
userNewUrlParser: true,
useUnifiedTopology: true,
});
try {
await client.connect();
db = client.db('sample_mflix');
logger.info('Connected to Database');
} catch (e) {
logger.error(e);
} finally {
await client.close();
}
};
I cannot find clear information on how to manage SQL server database connections from an Azure function written in Javascript.
I am using a connection pool code -
const pool = new sql.ConnectionPool(config);
const poolConnect = pool.connect();
pool.on('error', err => {
// ... error handler
})
and I am using the poolConnect object from the function which is executing the query
export const selectQuery = function() {
const connectionPool = await mssqlDBPoolConnect;
const request = connectionPool.request();
await request.query('select query');
}
So how can I use the same connection pool across all azure functions.
Create two folder named config and toolkit under your root path. Put your db.js in config folder, and create a sql helper class to export a function named sqltools.js in toolkit folder.
So you could use the same connection pool by calling sqltools in your function's code. This step help you to reduce using the same code in every function.
Try use the db.js code below:
const sql = require('mssql')
const config = {
user: 'yourusername',
password: 'yourpassword',
server: 'yoursqlserver.database.windows.net', // You can use 'localhost\\instance' to connect to named instance. Do not use TCP.
database: 'yourdb',
"options": {
"encrypt": true,
"enableArithAbort": true
}
}
const poolPromise = new sql.ConnectionPool(config)
.connect()
.then(pool => {
console.log('Connected to MSSQL')
return pool
})
.catch(err => console.log('Database Connection Failed! Bad Config: ', err))
module.exports = {
sql, poolPromise
}
The sqltools.js class:
const { poolPromise } = require('../config/db')
module.exports.sqltools = {
ExecSqlQuery : async function(arg){
const pool = await poolPromise
//SELECT *FROM SYSOBJECTS WHERE xtype = \'U\'
var result=null;
try {
result = await pool.request()
.query(arg)
} catch (error) {
console.log(error.message);
}
return result;
},
ExecProce : function (arg2, arg3, arg4){
console.log(arg2,arg3,arg4);
}
}
Here is my HttpTrigger1 index.js code, call ExecSqlQuery to exec sqlstrings:
const { sqltools } = require('../toolkit/sqltools');
module.exports = async function (context, req) {
context.log('JavaScript HTTP trigger function processed a request.');
var result=null;
try {
// call ExecSqlQuery func
result = await sqltools.ExecSqlQuery('SELECT *FROM SYSOBJECTS WHERE xtype = \'U\'');
} catch (error) {
console.log(error.message);
}
const responseMessage ="Func 1 Result : TableName= " + result.recordset[0].name;
context.res = {
// status: 200, /* Defaults to 200 */
body: responseMessage
};
}
I have following function called on specific route and I am trying to test if the mongoose method inside is called with specific parameter.
My code:
import boom from 'boom'
import User from '../models/model.user'
export const getSingle = async (req, res, next) => {
try {
const user = await User.findById(req.payload.id, '-auth')
if (user) {
return res.json({user})
}
return next(boom.notFound('User not found'))
} catch (err) {
return next(boom.badImplementation('Something went wrong', err))
}
}
My test case:
process.env.NODE_ENV = 'test'
import 'babel-polyfill'
import mongoose from 'mongoose'
import sinon from 'sinon'
require('sinon-mongoose')
import { getSingle } from '../src/controllers/controller.user'
const User = mongoose.model('User')
describe('User Controller ----> getSingle', () => {
it('Should call findById on User model with user id', async () => {
const req = {
payload: {
id: '123465798'
}
}
const res = { json: function(){} }
const next = function() {}
const UserMock = sinon.mock(User)
UserMock.expects("findById").once().withExactArgs('123465798', '-auth')
await getSingle(req, res, next)
UserMock.verify()
})
})
It fails the test as the method wasn't called even though it was.
Import the same model in the test as is used in the code under test i.e ../models/model.user and it should work as expected.