What is the best way to write critical nodejs backend functions? - javascript

I want to write functions for repeating tasks. So for example I want to have a file lib/dbFunctions to handle anything with my database, like searching the databse for a specific user.
so I just want to create the function searchUser(username) and just import my functions with e.g. const dbFunctions = require('lib/dbFunctions), so I can call dbFunctions.searchUser("ExampleUser");
How would I solve this in NodeJS?
I only found examples using the express.Router() looking like this:
const express = require('express');
const customRoutes = express.Router();
cusrtomRoutes.get('/login',function(req,res){
res.render('account/login');
});
module.exports = {"CustomRoutes" : customRoutes};
However I somehow couldn't figure out how to write a completly custom class with functions.

I usually do such kind of stuff by making a class such as UserRepository.
class UserRepository() {
static searchUser(user){
// your logic here
}
static getUserByEmail(email){
// your logic here
}
}
module.exports = UserRepository;
You can call this function from anywhere like this:
const UserRepository = require('./user-repository.js');
UserRepository.searchUser(user);
The advantage of above approach is that you can process other logic in constructor which would be require before talking to the database. This is OOP based approach basically.
The other way is (not OOP based):
const searchuser = (user) => {};
const getUserByEmail = (email) => {};
module.exports = {
searchuser,
getUserByEmail,
};
You can use in the same way:
const UserRepository = require('./user-repository.js');
UserRepository.search(user);

Sounds like you need to know how to make a module.
Say you have an index.js like this:
const dbFunctions = require('./lib/dbFunctions')
console.log(dbFunctions.searchUser('Dan'))
You can make your module work by creating a file at <project_root>/lib/dbFunctions.js which can look like this:
const searchUser = (name) => `found user ${name}`
module.exports = {searchUser}
Now if you run node index.js you'll see found user Dan print in your console.
It's important to note that when you're importing modules node expects a path like lib/dbFunctions to exist inside node_modules whereas when you're trying to import a local module within your project you need to specify the relative path from where you're importing hence I used ./lib/dbFunctions in my example.
Another common way to create a custom lib in your project is using a pattern called barrel files or barrel exports.
Instead of having a file called dbFunctions.js you'd create a directory at /lib/dbFunctions with an index.js inside the root of that directory. You can then seperate out all of your functions into their own modules like this for example:
// /lib/dbFunctions/searchUser.js
const searchUser = (name) => `found user ${name}`
module.exports = searchUser
// /lib/dbFunctions/searchPost.js
const searchPost = (id) => `found post ${id}`
module.exports = searchPost
And then your index.js file would look like this:
// /lib/dbFunctions/index.js
const searchUser = require('./searchUser');
const searchPost = require('./searchPost');
module.exports = {
searchUser,
searchPost
}
Then your main entry point (index.js)
const dbFunctions = require('./lib/dbFunctions')
console.log(dbFunctions.searchUser('Dan'))
console.log(dbFunctions.searchPost(2))

It seems you want to create a module consisting of a bunch of functions. If that's the case, a typical module of functions looks like this:
// file customFunctions.js
function custom1 () { ... }
function custom2 () { ... }
...
module.exports = { custom1, custom2, ... }
The module exports an object containing functions as properties.
Then, just require that file where you need it:
const customFns = require('./customFunctions');
customFns.custom1();

Related

Share an object instance across modules in typescript

I'm operating a bot on Wikipedia using npm mwbot, and planning to migrate to npm mwn. This is because you need a "token" to edit pages on Wikipedia, and this can expire after a while so you need to prepare your own countermeasures against this if you use mwbot, but it seems like mwn handles this issue on its own.
When you use mwn, you first need to initialize a bot instance as documented on the turotial:
const bot = await mwn.init(myUserInfo);
Then your token is stored in the bot instance and you can for example edit a page using:
const result = await bot.edit('Page title', () => {text: 'text'});
So, basically you want to share the initialized bot instance across modules. I believe it'd be easiest to declare a global variable like so:
// bot.js (main script)
const {mwn} = require('mwn');
const {my} = require('./modules/my');
(async() => {
global.mw = await mwn.init(my.userinfo);
const {t} = require('./modules/test');
t();
})();
// modules/test.js
/* global mw */
exports.t = async () => {
const content = await mw.read('Main page');
console.log(content);
return content;
};
I'm currently using JavaScript, but will hopefully migrate to TypeScript (although I'm new to it) because I feel like it'd be useful in developing some of my modules. But I'm stuck with how I should use the initialized bot instance across modules in TypeScript.
-+-- dist (<= where ts files are compiled into)
+-- src
+-- types
+-- global.d.ts
+-- bot.ts
+-- my.ts
// bot.ts
import {mwn} from 'mwn';
import {my} from './my';
(async() => {
global.mw = await mwn.init(my.userinfo);
})();
// global.d.ts
import {mwn} from 'mwn';
declare global {
// eslint-disable-next-line no-var
var mw: mwn;
}
This doesn't work and returns "Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. (at mw in global.mw)".
This is probably a naive question but any help would be appreciated.
Edit:
Thanks #CertainPerformance, that's a simple and easy approach. Actually, I once tried the same kind of an approach:
export const init = async () => {
if (typeof mw === 'undefined') {
return Promise.resolve(mw);
} else {
return mwn.init(my.userinfo).then((res) => {
mw = res;
return mw;
});
}
}
But I was like "init().then() in every module?"... don't know why I didn't come up with just exporting the initialized mwn instance.
Anyway, is it like the entry point file should be a .js file? I've been trying with a .ts file and this is one thing that's been giving me a headache. I'm using ts-node or nodemon to auto-compile .ts files, but without "type: module", "Cannot use import statement outside a module" error occurs and with that included, "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"" occurs. How do you tell a given file should be a .js or .ts file?
Edit2:
Just making a note: The error I mentioned above was caused by not having "module": "CommonJS" in my tsconfig.json, as I commented to CertainPerformance's answer below.
One of the main benefits of modules is the ability to drop dependencies on global variables. Rather than going back on that and assigning a global anyway, a better approach that happens to solve your problem as well would be to have a module that exports two functions:
One that initializes the asynchronous mwn
One that returns mwn when called
// mw.ts
import {mwn} from 'mwn';
import {my} from './my';
let mw: mwn;
export const init = async () => {
mw = await mwn.init(my.userinfo);
};
export const getMw = () => mw;
Then it can be consumed by other modules quite naturally, barely requiring any typing at all:
// modules/index.ts
// Entry point
import { init } from './mw';
import { test } from './test';
init()
.then(() => {
test();
})
// .catch(handleErrors);
// modules/test.ts
import { getMw } from './bot';
export const test = () => {
const mw = getMw();
// anything else that depends on mw
};
The above could be a reasonable approach if mw is used in many places across your app and you don't think that passing it around everywhere would be maintainable.
If you could pass it around everywhere, that would have even less of a code smell, though.
// initMw.ts
import {mwn} from 'mwn';
import {my} from './my';
export const initMw = () => mwn.init(my.userinfo);
// modules/index.ts
// Entry point
import { initMw } from './initMw';
import { test } from './test';
initMw()
.then((mw) => {
test(mw);
})
// .catch(handleErrors);
// modules/test.ts
import { mwn } from 'mwn';
export const test = (mw: mwn) => {
// anything that depends on mw
};
Initialize it once, then pass it down (synchronously) everywhere it's needed - that's the approach I'd prefer.
You could put the mwn type in a global d.ts file to avoid having to add import { mwn } from 'mwn'; to every module if you wanted. (Yes, it's somewhat of a global variable, but it's a type rather than a value, so it's arguably less of a problem.)

Is there any way to use the concept of "live bindings" in commonjs without esm?

I have a configuration file with some properties that may change during a lazy loading.
Usually I just import props through the destructurization method to reduce code, but in the lazy case I should forget about it, because commonjs uses the transfer to local variables instead of the new ES behavior.
My example code:
configs.js
let someLazy = null;
module.exports = { someLazy };
setLazy:
const configs = require("./configs");
setTimeout(() => {
configs.someLazy = true;
}, 1000);
module.js
require("./setLazy");
const { someLazy } = require("./configs");
const configs = require("./configs");
setInterval(() => {
//I don't want to write like this:
console.log(configs.someLazy);
//Instead of writing as above I need to make the following work:
console.log(someLazy);
}, 1000);
Of course, I can import full configs object into both of the above files, but at the end I get an extra code. It's more obvious when I have long property names.
Even if there is no way, is it possible to hack webpack just for that?

Running Code When a Module is Loaded and Exposing It as Middleware

I am still trying to fully understand how exporting and importing modules works in Nodejs.
I am using the following file to seed a mongodb database. This file runs exactly as it should and returns exactly the result I am expecting, when I execute it as a standalone file. My issue is I want to use this file in two different places in my app. So I am trying to make it an exportable/importable module. Here is what I have tried:
seed.js looks like this:
'use strict';
// library modules
const {ObjectID} = require('mongodb');
const seeder = require('mongoose-seed');
const jwt = require('jsonwebtoken');
const util = require('util');
// local modules
const {Course} = require('./../models/course');
const {Review} = require('./../models/review');
const {User} = require('./../models/user');
const {data} = require('./../data/data.js');
const config = require('./../config/config.js');
/*==================================================
build seeding Courses, Reviews, Users
==================================================*/
// Connect to MongoDB via Mongoose
let seed = seeder.connect(process.env.MONGODB_URI, (e) => {
console.log(`Connected to: ${process.env.MONGODB_URI} and seeding files`);
// Load Mongoose models
seeder.loadModels([
'src/models/user.js',
'src/models/review.js',
'src/models/course.js'
]);
// Clear specified collections
seeder.clearModels(['User', 'Review', 'Course'], function() {
// Callback to populate DB once collections have been cleared
seeder.populateModels(data, function() {
seeder.disconnect();
});
});
});
module.exports = seed;
Within app.js I have these two lines
const seed = require('./middleware/seed');
and
app.use(seed);
I have also tried, to no avail
app.use(seed());
What is missing? I don't want to use the code in-line in two different places (DRY you know).
It is failing with this error:
throw new TypeError('app.use() requires a middleware function')
I am sorry about the formatting I thought I was using markdown, but I am clearly not.
You are executing your seeder function in that module and not returning any middleware function at all. Middleware is always a function and it has the form of:
const seed = function (req, res, next) {
...
next()
}
With what you have now, the best you can do is the following which will certainly seed your models when the module is loaded.
const seed = require('./middleware/seed');
Are you trying to run those two functions as middleware, on every request? In that case you'd do something like this:
const seed = function (req, res, next) {
seeder.connect(..., (e) => {
...
seeder.clearModels(..., ()=>{
...
next()
})
})
})
If you want to run that seeder code at the module level AND to expose it as middleware then wrap the current module level code in function and execute it when the module loads. Notice that the new function optionally handles the next callback if it receives it.
function doSeed(next){
//your code currently running at module level
if(next)
return next()
}
doSeed(); //run the code at the module level
const seed = (req, res, next) => doSeed(next))
module.exports = seed;
One way that I allow myself to have access to a variable in different instances is adding it to the process variable. In node.js no matter where in the project it is, it will always be the same. In some cases, I would do process.DB = seed(); which would allow process.DB to always be the result of that. So inside your main class you can do process.DB = seed(); and all of your other classes running off of that one main class would have access to process.DB keeping your database readily available.

Re-including a module on-the-fly

I am currently working on technical debt identified by SonarQube for a Node.js application. My application allows on-the-fly switching between a live and mock datasource. To achieve this I destroy the previous "require" from cache and re-require it. When running SonarQube it does not like "require" statements. It does suggest "import" statements. However that may not be suitable in this case.
Simplified version of existing code:
var config = require('../config');
var polService = require(config.polService);
var root = require('../root');
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
delete require.cache[require.resolve(root.path + ‘/config’)];
config = require('../config');
polService = require(config.polService);
}
The setEnvironment function sets process.env.NODE_ENV = liveOrMock, which is used in config.js. We export the config module using module.exports = localOptions[process.env.NODE_ENV]; This code picks a single key-pair from a JSON. The value that comes back is used to choose which module is being used for a restService.
Being able to change what module is being used is for polService is the purpose of the code.
Change your config module to export a function, and then call this function whenever you need to change environment.
In order to make polService a dynamic module, you can use dynamic import(). import() is not supported natively, but you can use this Babel plugin (it works with webpack) to transpile it.
config.js:
export default () => {
// ...
return localOptions[process.env.NODE_ENV];
}
Main module:
import getConfig from '../config';
let config = getConfig();
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
return import(config.polService).then(result => {
polService = result;
});
}
Keep in mind that now the doingStuff function is asynchronous (i.e. returns a promise), so you can't just call it and access polService immediately. You have to wait for it by either using the then() method, or using await in an async function.
If you have a limited number of polService modules, it might be a better option to import all of them beforehand, and in the doingStuff function just switch which one the polService variable refers to.
import getConfig from '../config';
import polService1 from '../polService1';
import polService2 from '../polService2';
import polService3 from '../polService3';
const polServices = { polService1, polService2, polService3 };
let config = getConfig();
let polService = polService1;
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
polService = polServices[config.polService];
}

How do I export 2 methods from a single server side module (js file)?

I'm using Node/Express.
I have a server, index.js. I have a different js module, called validmoves.js.
Typically, I can export the single function I define in a module by saying:
module.exports = shuffleFunction;
And then require it in the express server file, index.js, by saying:
let shuffle = require('./routes/shuffleRoute');
How can I export 2 function from the module? One of them is actually a helper function.
You can define a name of the module you're exporting and specify on the import. That would look something like this:
// shuffleRoute.js
module.exports.shuffleFunction = shuffleFunction;
module.exports.someHelperFunction = someHelperFunction;
and in your other file:
// index.js
let shuffle = require('./routes/shuffleRoute').shuffleFunction;
let helper = require('./routes/shuffleRoute').someHelperFunction;
You can export multiple functions from a module:
module.exports.shuffleFunction = shuffleFunction
module.exports.somethingElse = somethingElse
Use it in index.js:
let shuffle = require('./route/shuffleRoute');
shuffle.shuffleFunction();
shuffle.somethingElse();
See the example here.

Categories