How to make synchronous db query before asynchronous node app starts? - node.js

I run node app with CORS.
Currently I load whitelisted domains from .env file but I need to change it to load them from database. The whole CORS functionality is synchronous and I wonder what's the proper way to make db query for it.
app.js
app.use(cors());
async function listenCallback(server) {
try {
// app code
} catch (err) {
server.close();
}
cors.js
const cors = require('cors');
const db = require('../db');
const whitelist = db.raw('select domains from table'); // I need to change this
module.exports = (enabled = true) =>
(req, res, next) => {
const options = {
origin(origin, callback) {
if (!origin) {
callback(null, true);
return;
}
const originIsWhitelisted = enabled ? whitelist.indexOf(origin) !== -1 : true;
if (originIsWhitelisted) {
callback(null, originIsWhitelisted);
return;
}
callback({
statusCode: 401,
error: 'Not allowed',
});
},
};
return cors(options)(req, res, next);
};

If what you're trying to do is to load a set of domains from a database upon module initialization so you can then use those in some middleware, then you can get a promise from loading the whilelist from the database and then just use that promise in your middleware.
const cors = require('cors');
const db = require('../db');
const whitelistPromise = db.someOperationThatReturnsPromise();
module.exports = (enabled = true) =>
(req, res, next) => {
const options = {
origin(origin, callback) {
if (!origin) {
callback(null, true);
return;
}
whitelistPromise.then(whitelist => {
const originIsWhitelisted = enabled ? whitelist.indexOf(origin) !== -1 : true;
if (originIsWhitelisted) {
callback(null, originIsWhitelisted);
return;
}
callback({
statusCode: 401,
error: 'Not allowed',
});
}).catch(err => {
console.log(err);
next(err);
return;
});
},
};
return cors(options)(req, res, next);
};
Another approach is to export a module constructor function from the module that returns a promise that tells your server when your module initialization is done and provides this middleware function as the resolved value. I like this approach better because if the db operation fails, you will know it more centrally and can provide better error handling.
const cors = require('cors');
const db = require('../db');
// export module constructor that initializes this module asynchronously and returns a promise
// That promise resolves to the middleware function (so it can't be used before
// the module is properly initialized)
module.exports = function() {
return db.someOperationThatReturnsPromise().then(whitelist => {
return (enabled = true) => (req, res, next) => {
const options = {
origin(origin, callback) {
if (!origin) {
callback(null, true);
return;
}
const originIsWhitelisted = enabled ? whitelist.indexOf(origin) !== -1 : true;
if (originIsWhitelisted) {
callback(null, originIsWhitelisted);
return;
}
callback({
statusCode: 401,
error: 'Not allowed',
});
},
};
return cors(options)(req, res, next);
};
});
}
In this case, whoever loads this module will do it something like this:
require('./myModule')().then(makeWhitelistMiddleware => {
// put route handlers here that use the function to make CORS middleware
// don't start your server until this completes
app.use(makeWhitelistMiddleware());
// other routes here
app.get(...)
// start server
app.listen(...);
}).catch(err => {
console.log("Could not initialize myModule");
process.exit(1);
});
Lastly, I'll add that there is effort underway to allow top level await inside of module initialization where the whole module loader system can be made aware of promises involved in initializing a module. That type of capability would make this a lot simpler, but until then you have to add your own custom code to initialize the module differently and load the module differently.

Related

Node js Error Handler Doesnt get exact error message from Controller Express/Mongoose

I a trying to implement a rest API for our project then I go for node js and express. I have built all the models and controllers. I faced an issue while trying to handle an error. Errorhandler function doesn't receive all the properties of error that caught in try/catch block. I can not read its name in a handler but I can use its name in the controller. Could you please help me?
const errorHandler = (err, req, res, next) => {
console.log(`Error in method:${req.method}: ${err.stack}`.bgRed);
let error = { ...err };
console.log(`Error handler: ${err.name}`);
res.status(error.statusCode || 500).json({
success: false,
data: error.message || 'Server Error',
});
};
module.exports = errorHandler;
controller
const mongoose = require('mongoose');
const Product = require('../models/Product');
const ErrorResponse = require('../utils/error');
const routeName = 'PRODUCT';
// #desc getting single product via id
// #route GET api/v1/products
// #acces public
exports.getProdcut = async (req, res, next) => {
try {
const product = await Product.findById(req.params.id);
if (!product) {
return next(
new ErrorResponse(`Product not found with id:${req.params.id}`, 404)
);
}
res.status(200).json({
success: true,
data: product,
});
} catch (err) {
console.log(err.name);
console.log('ERRO APPEND');
next(new ErrorResponse(`Product not found with id:${req.params.id}`, 404));
}
};
Assuming that errorHandler is part of your middleware that is somewhere after getProdcut, you can try just throwing the error and Express will automatically detect that for you, because error handling middleware such as yours accepts 4 parameters. So the following would work:
const getProdcut = async (req, res, next) => {
try {
// ...
} catch (err) {
throw err;
}
};
const errorHandler = (err, req, res, next) => {
if (err) {
console.log('hello from the error middleware');
console.log(err.name);
}
else {
// next() or some other logic here
}
}
app.use('/yourRoute', getProdcut, errorHandler);
And inside of your errorHandler you should have access to the error object.
Error-handling middleware always takes four arguments. You must provide four arguments to identify it as an error-handling middleware function. Even if you don’t need to use the next object, you must specify it to maintain the signature. Otherwise, the next object will be interpreted as regular middleware and will fail to handle errors.
https://expressjs.com/en/guide/using-middleware.html#middleware.error-handling

How to use Redis with NodeJS to get all the users from DB

I'm trying to implement Redis inside my NodeJS API but having issues to do so.
What I try is to make Redis for the endpoint which serves all the users as a response.
I'm getting this error from my implementationnode_redis:
The GET command contains a invalid argument type of \"undefined\".\nOnly strings, dates and buffers are accepted. Please update your code to use valid argument types.:
I tried in this way from connection to middleware:
import redis from 'redis';
import { redisConfig } from '../config';
import Logger from './logger';
// Redis default port === 6379
const REDIS_PORT = redisConfig.port || 6379;
// Connect to redis
const client = redis.createClient(REDIS_PORT);
// Check connection
client.on('connect', () => {
Logger.info('Connected to Redis');
});
client.on('error', () => {
Logger.error('Redis not connected');
});
export default client;
Then using this above in my controller:
import Client from '../loaders/redis';
const UserController = {
async getAllUsers(req, res, next) {
try {
const users = await DB.User.find({});
if (!users) {
Logger.error('User was not found');
return res.status(404).send('Nothing found');
}
// Set users to redis
await Client.setex(users, 3600, users);
Logger.info('All the users were found');
return res.status(200).send(users);
} catch (err) {
Logger.error(err);
return next(err);
}
},}
The middleware:
import Client from '../loaders/redis';
// Cache middleware
const cache = (req, res, next) => {
const { users } = res;
Client.get(users, (error, cachedData) => {
if (error) throw error;
if (cachedData != null) {
res.send(users, cachedData);
} else {
next();
}
});
};
export default cache;
The route:
import Controller from '../controllers';
import cache from '../middleware/cacheMiddle';
app.get('/users', cache, Controller.UserCtrl.getAllUsers);
I cannot understand how to use it as I want to adopt Redis to bee able to get the users faster and also I don't know if make sense and also how to do it for the post of a user for example.

Time Out in Api rest Express Nodejs

I want to put my api in time out. I tried to put it in a "global" way when I create the server but the answer only says that no data was returned, not a 504 error. I also have the option to place it for each route, I think the time out should go in the controller of my app .
function initialize() {
return new Promise((resolve, reject) => {
const app = express();
httpServer = http.createServer(app);
app.use(morgan('combined'));
app.use('/proyecto', router_proyecto);
app.use('/tramitacion',router_tramitacion);
httpServer.listen(webServerConfig.port, err => {
if (err) {
reject(err);
return;
}
//httpServer.timeout = 500;
console.log('Servidor web escuchando en puerto:'+webServerConfig.port);
resolve();
});
});
}
exaple of a route:
router_proyecto.route('/getProyecto/:proyid?')
.get(proyecto.getProyecto);
Controller(Time out should go here):
async function getProyecto(req, res, next) {
try {
const context = {};
context.proyid = parseInt(req.params.proyid, 10);
const rows = await proyectos.getProyecto_BD(context);
if (rows.length >0) {
res.status(200).json(rows);
} else {
res.status(404).end();
}
} catch (err) {
next(err);
}
}
module.exports.getProyecto = getProyecto;
What does "time out" mean to you? Because a a timeout is a well defined term in Javascript and your server should definitely not be using that. If you want your server to simply serve some content with a specific HTTP code, depending on some global flag, then you were on the right track except you need to write it as middleware.
let lock = Date.now();
function serveLockContent(req, res, next) {
if (lock <= Date.now()) {
return next();
}
res.status(503).write('Server unavailable');
}
...
app.use(serveLockContent);
...
app.post('/lock/:seconds', verifyAuth, otherMiddlerware, (req, res) => {
lock = Date.now() + (parseInt(req.params.howlong) || 0) * 1000;
res.write('locked');
});

fastify-mongodb : "mongo" is undefined when accessed

I'm attempting to use Fastify and fastify-monogdb.
Currently I have the following...
In my /src/index.js
const routes = require("./routes");
const fastify = require("fastify")({
logger: true
});
routes.forEach((route, index) => {
fastify.route(route);
});
fastify.register(require("fastify-mongodb"), {
url: "mongodb://localhost:27017/parkedcars"
});
const startFastify = async () => {
try {
await fastify.listen(3333);
fastify.log.info(`server listening on ${fastify.server.address().port}`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
startFastify();
In my /routes/index.js I have a route...
const carController = require("../controllers/carController");
{
method: "POST",
url: "/api/create/parkedcar",
handler: carController.createParkedCar
}
And finally in my /controllers/carController...
const fastify = require("fastify")();
exports.createParkedCar = async (req, reply) => {
try {
let car = { ...req.body };
const db = fastify.mongo.db
*// will do insert here*
return car;
} catch (err) {
throw boom.boomify(err);
}
};
When I attempt to call:
const db = fastify.mongo.db
I get an error that says...
"Cannot read property 'db' of undefined"
What am I doing wrong here?
How is mongo undefined at this point?
Doesn't "fastify.register" make this accessible to me?
You need to do the require("fastify")() only in your once per application because it is a factory and not a singleton, so every time you run the require you are creating a brand new HTTP server!
The magic is to use the .register in a proper way and/or using function instead of arrow function in the handler.
For your use case you could change the carController:
exports.createParkedCar = function handler (req, reply) {
let car = { ...req.body };
const db = this.mongo.db
*// will do insert here*
db.insert(...)
.then(() => reply.send(car))
.catch((err) => reply.send(boom.boomify(err)))
}
because all the function handlers, in fastify, are bound to the fastify server instance (like this aFunction.bind(fastify)). The arrow functions can't be binded.
Another options is to use the register:
// /routes/index.js
module.exports = (fastify, opts, next) => {
fastify.route({
method: "POST",
url: "/api/create/parkedcar",
handler: async (req, reply) => {
try {
let car = { ...req.body };
const db = fastify.mongo.db
*// will do insert here*
return car;
} catch (err) {
throw boom.boomify(err);
}
});
next() // dont forget it
}
For more info checkout the docs

How to test express middleware that depends on other vendor middleware?

I wanna test a middleware function that inside calls a vendor middleware function. The middleware is:
const expressJwt = require('express-jwt');
const validateJwt = expressJwt({ secret: 'whatever' });
exports.isAuthenticated = (req, res, next) => {
if (req.query && req.query.hasOwnProperty('access_token')) {
req.headers.authorization = `Bearer ${req.query.access_token}`;
}
validateJwt(req, res, next);
};
I've tried to create a sinon.spy() object and pass it as next parameter, but is not called apparently.
Another approach I've tried is to check if exists req.user, since the purpose of the express-jwt middleware is to validate and attach user to the req object. No luck with this neither.
I've seen the existence of chai-connect, but not sure how to use it.
Any ideas? Highly appreciate it!
I finally managed to do it with proxyquire and chai-connect:
In your mocha config:
chai.use(require('chai-connect-middleware'));
global.connect = chai.connect;
In your test:
describe('isAuthenticated', () => {
// Wrap call to chai.connect into a function to use params and return a Promise
const mockedMiddleware = (changeSecret) => {
let oldToken;
if (changeSecret) {
oldToken = acessToken;
acessToken = 'blabalblalba';
}
return new Promise((resolve, reject) => {
connect.use(middleware.isAuthenticated)
.req(req => {
req.query = { access_token: acessToken };
})
.next((res) => {
acessToken = oldToken;
if (res && res.status === 401) {
reject(res.message);
} else {
resolve();
}
})
.dispatch();
});
};
it('should validate correctly', () =>
mockedMiddleware().should.be.fulfilled
);
it('should not validate', () =>
mockedMiddleware(true).should.be.rejected
);
});

Resources