How to share config folder between main app and library module? - node.js

This is my plan to setup myNodeApp, which has these folders:
config, src, node_modules/#myCompany/logging
in the library #myCompany/logging, i have code like this,
myWinston.js
const { createLogger } = require('winston');
const logger = createLogger({
level: config.get('logging').level, // this config is the one from myNodeApp
prettyPrint: true』);
module.exports = logger;
myLogger.js
const logger = require(./myWinston');
const warning = (myWarningMsg) => {
logger.warn(myWarningMsg);
}
module.exports = { warning };
Now in the main app code,
myApp.js
const { logger } = require('#myCompany/logging');
...
logger.warning('my warning msg here');
...
The problem is myWinston.js within the library #myCompany/logging, it needs a logging level from the main app.
What's the best way to pass this info from myNodeApp/config pls ?
Another idea see if it can work,
#myCompany/logging this lib has a config folder, can be used for testing within this lib.
When install #myCompany/logging for the myNodeApp, i can exclude config folder by using .npmignore. So logging will use the config folder from myNodeApp.
Comments pls ?

I think it is possible this way:
logger from npm modules can export constructor function:
// file in node_modules/#myCompany/logging/index.js
const { createLogger } = require('winston');
module.exports = exports = function(loggerConfig){
return createLogger({
level: loggerConfig.level,
prettyPrint: loggerConfig.prettyPring
});
};
and it can be used this way in index.js file, so config is provided properly
const config = require('./lib/config'); // all top level app config is loaded from some file in your project
config.logger.appName = 'web'; // you can customize global config here, for example, you have 2 components - web server and background process, and appName can depict it
const logger = require('#myCompany/logging')(config.logger); //here you instantiate logger with global config loaded
logger.info('Web application is preparing to start!');
//lot of code here

Related

How to use Winston Logger in NestJS separate module that doesn't use Classes

I've tried a few different ways of doing this.
I can't set Winston as the default logger for NestJS at the moment because it complains about "getTimestamp" function not being in the instance.
So - for controllers in NestJS - I have used dependency injection - which works fine for the api ( REST endpoints ).
The problem is that I have moved away from OOP - so all of my libraries are written in typescript as functions. Not pure functions but better than an OOP approach ( many less bugs! )
My question is - how do I get access to the main winston logger within my libraries that don't have classes.
I am using the library nest-winston.
Have you tried this?
create the logger outside of the application lifecycle, using the createLogger function, and pass it to NestFactory.create (nest-winston docs)
You can have a separate file that creates the logging instance, then import that into your modules/libraries as well as import it into your main.ts
// src/logger/index.ts
import { WinstonModule } from 'nest-winston';
export const myLogger = WinstonModule.createLogger({
// options (same as WinstonModule.forRoot() options)
})
// src/myLib/index.ts
import { myLogger } from '#/logger' // I like using aliases
export const myLib = () => {
// ...
myLogger.log('Yay')
}
// src/main.ts
import { myLogger } from '#/logger'
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
logger: myLogger
});
}
bootstrap();

How to use simple node logger module

This below function created mylogfile but can't error the logs of api response with timestamp and error in app
const SimpleNodeLogger = require('simple-node-logger'),
opts = {
logFilePath:'mylogfile.log',
timestampFormat:'YYYY-MM-DD HH:mm:ss.SSS'
},
log = SimpleNodeLogger.createSimpleLogger( opts );
seems you're missing something ... here's an example
// utilities/logger.js
const SimpleNodeLogger = require('simple-node-logger');
const opts = {
logFilePath:'mylogfile.log',
timestampFormat:'YYYY-MM-DD HH:mm:ss.SSS'
};
const log = SimpleNodeLogger.createSimpleLogger(opts);
module.exports = log;
and then, just use it
// index.js
const logger = require('./utilities/logger');
logger.info(`I'm an information line`);
logger.debug(`I'm a debug line`);
logger.error(`I'm an error line`);
that will output in a new created file called mylogfile.log:
2020-12-25 13:37:17.139 INFO I'm an information line
2020-12-25 13:37:17.140 ERROR I'm an error line
set the log level if you want to output more info, like debug. All options are in the package page titled "How to use"

Make required json available in other modules

I am writing an application that has configuration parameters in a json file. Something like this:
// config.json
{
"httpServer": {
"port": 3000
},
"module1": {
"setting1": "value1",
"setting2": "value2"
},
"module2": {
"setting1": "value1",
"setting2": "value2"
}
}
// index.js
const config = require("./config")
const func1 = require("./module1")
const func2 = require("./module2")
// code here
// module1.js
const config = require("./config")
// use config and define functions
module.exports = {
function: function
}
// module2.js
const config = require("./config")
// use config and define functions
module.exports = {
function: function
}
The problem is that I am requiring this file in every module which makes my code unmaintainable since I need to update every require statement if the filename changes. I am pretty sure that this is not the "correct" way of doing this. Can I require the configuration file once when the program starts and then reference to it in other modules? Or should I pass the configuration file as a command line argument and then use process.argv array when requiring the file? What is the best way of handling situations like these?
use dotenv package npm install dotenv --save,
create a config file
//config.env
NODE_ENV=development
IP=127.0.0.1
PORT=3000
load the config file
//index.js
const dotenv = require('dotenv');
dotenv.config({ path: './config.env' })
use it where ever you want
//module1
console.log('IP: ',process.env.IP)
To be honest I don't really see anything wrong in requiring the config in multiple files. Since you need it, you are requiring it.
If you really don't want to require multiple times, you could consider this
Convert the function style to class style and then inject the config as a dependency to that class
Main File
const config = require("./config");
const file1 = new File1(config);
const file2 = new File2(config);
File 1
class File1 {
constructor(config) {
this.config_ = config;
}
someFunction() {
// use this.config_ here
}
}
File 2
class File2 {
constructor(config) {
this.config_ = config;
}
someFunction() {
// use this.config_ here
}
}
Few advantages of using this approach are:
better testable as you can mock the config if you want.
you could also change at one place and you wouldn't need to change other places as you are injecting.

Test process.env with Jest

I have an application that depends on environmental variables like:
const APP_PORT = process.env.APP_PORT || 8080;
And I would like to test that for example:
APP_PORT can be set by a Node.js environment variable.
or that an Express.js application is running on the port set with process.env.APP_PORT
How can I achieve this with Jest? Can I set these process.env variables before each test or should I mock it somehow maybe?
The way I did it can be found in this Stack Overflow question.
It is important to use resetModules before each test and then dynamically import the module inside the test:
describe('environmental variables', () => {
const OLD_ENV = process.env;
beforeEach(() => {
jest.resetModules() // Most important - it clears the cache
process.env = { ...OLD_ENV }; // Make a copy
});
afterAll(() => {
process.env = OLD_ENV; // Restore old environment
});
test('will receive process.env variables', () => {
// Set the variables
process.env.NODE_ENV = 'dev';
process.env.PROXY_PREFIX = '/new-prefix/';
process.env.API_URL = 'https://new-api.com/';
process.env.APP_PORT = '7080';
process.env.USE_PROXY = 'false';
const testedModule = require('../../config/env').default
// ... actual testing
});
});
If you look for a way to load environment values before running the Jest look for the answer below. You should use setupFiles for that.
Jest's setupFiles is the proper way to handle this, and you need not install dotenv, nor use an .env file at all, to make it work.
jest.config.js:
module.exports = {
setupFiles: ["<rootDir>/.jest/setEnvVars.js"]
};
.jest/setEnvVars.js:
process.env.MY_CUSTOM_TEST_ENV_VAR = 'foo'
That's it.
Another option is to add it to the jest.config.js file after the module.exports definition:
process.env = Object.assign(process.env, {
VAR_NAME: 'varValue',
VAR_NAME_2: 'varValue2'
});
This way it's not necessary to define the environment variables in each .spec file and they can be adjusted globally.
In ./package.json:
"jest": {
"setupFiles": [
"<rootDir>/jest/setEnvVars.js"
]
}
In ./jest/setEnvVars.js:
process.env.SOME_VAR = 'value';
You can use the setupFiles feature of the Jest configuration. As the documentation said that,
A list of paths to modules that run some code to configure or set up
the testing environment. Each setupFile will be run once per test
file. Since every test runs in its own environment, these scripts will
be executed in the testing environment immediately before executing
the test code itself.
npm install dotenv dotenv that uses to access environment variable.
Create your .env file to the root directory of your application and add this line into it:
#.env
APP_PORT=8080
Create your custom module file as its name being someModuleForTest.js and add this line into it:
// someModuleForTest.js
require("dotenv").config()
Update your jest.config.js file like this:
module.exports = {
setupFiles: ["./someModuleForTest"]
}
You can access an environment variable within all test blocks.
test("Some test name", () => {
expect(process.env.APP_PORT).toBe("8080")
})
Expanding a bit on Serhan C.'s answer...
According to the blog post How to Setup dotenv with Jest Testing - In-depth Explanation, you can include "dotenv/config" directly in setupFiles, without having to create and reference an external script that calls require("dotenv").config().
I.e., simply do
module.exports = {
setupFiles: ["dotenv/config"]
}
In test file:
const APP_PORT = process.env.APP_PORT || 8080;
In the test script of ./package.json:
"scripts": {
"test": "jest --setupFiles dotenv/config",
}
In ./env:
APP_PORT=8080
In my opinion, it's much cleaner and easier to understand if you extract the retrieval of environment variables into a utility (you probably want to include a check to fail fast if an environment variable is not set anyway), and then you can just mock the utility.
// util.js
exports.getEnv = (key) => {
const value = process.env[key];
if (value === undefined) {
throw new Error(`Missing required environment variable ${key}`);
}
return value;
};
// app.test.js
const util = require('./util');
jest.mock('./util');
util.getEnv.mockImplementation(key => `fake-${key}`);
test('test', () => {...});
Depending on how you can organize your code, another option can be to put the environment variable within a function that's executed at runtime.
In this file, the environment variable is set at import time and requires dynamic requires in order to test different environment variables (as described in this answer):
const env = process.env.MY_ENV_VAR;
const envMessage = () => `MY_ENV_VAR is set to ${env}!`;
export default myModule;
In this file, the environment variable is set at envMessage execution time, and you should be able to mutate process.env directly in your tests:
const envMessage = () => {
const env = process.env.MY_VAR;
return `MY_ENV_VAR is set to ${env}!`;
}
export default myModule;
Jest test:
const vals = [
'ONE',
'TWO',
'THREE',
];
vals.forEach((val) => {
it(`Returns the correct string for each ${val} value`, () => {
process.env.MY_VAR = val;
expect(envMessage()).toEqual(...
you can import this in your jest.config.js
require('dotenv').config()
this work for me
All the above methods work if you're using require("dotenv").config within the jest.config.js file, a NodeJS application without TypeScript such as what Jialx or Henry Tipantuna has suggested.
But if you're using ts-jest and within the jest.config.ts file.
import dotenv from "dotenv"
dotenv.config()
/* config options below */
When using Typescript the following works for me:
in root:
jest.config.js
/* eslint-disable #typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require('ts-jest');
const { compilerOptions } = require('./tsconfig.paths.json');
module.exports = {
// [...]
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }),
};
process.env = Object.assign(process.env, {
env_name: 'dev',
another_var: 'abc123',
});
To build upon #HenryTipantuña's suggestion is to import dotenv in your jest.config.js and use a .env.test file in the config path
require('dotenv').config({
path: '.env.test'
})
Building on top of #jahller's answer.
I made it responsive so you don't need to keep the files in sync as things change.
Put this at the bottom of your jest.config.js file.
const arr = require('fs')
.readFileSync('.env', 'utf8')
.split('\n')
.reduce((vars, i) => {
const [variable, value] = i.split('=')
vars[variable] = value
return vars
}, {})
process.env = Object.assign(process.env, arr)
It reads the contents of your .env file, splits every new line and reduces it all back down to an object where you then assign it to process.env
OR
just use dotenv in jest.setup.js 🤷‍♂️
i have most simple for implementation env (specialy test.env)
require("dotenv").config({ path: './test.env' });
const { sum } = require('./sum.js');
describe('sum', () => {
beforeEach(() => {
jest.resetModules(); // remove cache
})
test('should success', () => {
expect(sum(1, 3)).toEqual(4);
})
})
I think you could try this too:
const currentEnv = process.env;
process.env = { ENV_NODE: 'whatever' };
// test code...
process.env = currentEnv;
This works for me and you don't need module things

In which file should I initialize objects in Express.js?

I'm new to Node.js and Express.
I want to use log4js but not certain about in which file I should configure my logger.
Is there a conventional file for the initialization? If not, where should I create a new configuration file?
Thanks :)
Answer (based on #jfriend00 answer)
In logger.js
'use strict';
var log4js = require('log4js');
log4js.configure({
"appenders": [...]
});
var logger = log4js.getLogger("structuredLogger");
module.exports = logger
In client.js
var logger = require('../../../../../config/logger.js');
logger.info('My message');
This module will allow me to:
Easily configure the the log4js
Easily replace the log4js with another package.
One common option for a module that needs to get initialized once is to create your own container module that does the initialization. Then, every other module that wants to use the logging can just load your container module and, if not already initialized, your container module will initialize the logging.
// mylog.js
// initialization code will only be called the first time the module is loaded
// after that, the module is cached by the `require()` infrastructure
var log4js = require('log4js');
log4js.configure({
appenders: [
{ type: 'console' },
{ type: 'file', filename: 'logs/cheese.log', category: 'cheese' }
]
});
module.exports = log4js;
Then, every module that wishes to use the common configuration logging can just do this near the top of the module:
var log4js = require('./mylog.js');

Resources