Skip to main content

One Page Notes | Best Practices

Must Read

Write Better Code with these 5 JavaScript Features

ES6 Modules and Node.js

Tips

  • Use type='module' in package.json
  • .js file extension works fine. You dont need .mjs when type is module.
  • You don't need babel and is not recommended on the server side.
  • Follow this format for imports, and should not use require
    • Note the use of .js extension. For some reason it is mandatory, unlike react. Example: import { cmap, ctx } from '../ercli/commands.js'

Complete Express Server example

app.js

import express from "express";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import { logger } from "../util/logger.js";

// CAUTION: Remove in PROD if .env file is present
import dotenv from "dotenv";
dotenv.config();

import { ping, comm } from "./handlers.js";

const app = express();

import cors from "cors";

app.use(
cors({
origin: "http://localhost:3000",
})
);

app.use(bodyParser.json());
app.use(cookieParser());

app.get("/ping", ping);
app.post("/comm", comm);

const port = process.env.SERVER_PORT || 3030;
app.listen(port, () => {
logger.info(
`ErCommand SERVER listening on port ${port}! GET : http://localhost:${port}/ping`
);
});

handler.js

import { logger } from "../util/logger.js";
import { cmap, ctx } from "../ercli/commands.js";
import YAML from "yamljs";

// Meant for browser clients, token is set as cookie
const ping = (req, res) => {
res.send({ msg: "Hello from Auth Server" });
};

const comm = async (req, res) => {
logger.info(JSON.stringify(req.body));
const cmdJson =
req.body.format === "yaml" ? YAML.parse(req.body.cmd) : req.body.cmd;
logger.info(JSON.stringify(cmdJson));
const cmdPref = cmdJson.c;
let result = {};

if (cmdPref in cmap) {
// caution: single global singleton context added. Make it session bound
result = await cmap[cmdPref](cmdJson, ctx);
} else result = { msg: "command not found" };
res.send({ result });
};

export { ping, comm };

package.json

{
"name": "cli-server",
"version": "1.0.0",
"description": "server app",
"main": "app.js",
"type": "module",
"scripts": {
"dev": "node ./node_modules/nodemon/bin/nodemon.js server/app.js",
"start": "node server/app",
"test": "jest"
},
"author": "kre",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.20.12",
"@babel/preset-env": "^7.20.2",
"babel-jest": "^29.3.1",
"jest": "^29.3.1",
"nodemon": "^2.0.20"
},
"dependencies": {
"body-parser": "^1.18.3",
"cookie-parser": "^1.4.4",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.16.4",
"jsonwebtoken": "^8.5.1",
"mongodb": "4.13",
"winston": "^3.8.2",
"yamljs": "^0.3.0"
}
}

Promises

Error Handling

An amazing Promise chaining pattern you should know,

fetch("/article/promise-chaining/user.json")
.then((response) => response.json())
.then((user) => fetch(`https://api.github.com/users/${user.name}`))
.then((response) => response.json())
.then(
(githubUser) =>
new Promise((resolve, reject) => {
let img = document.createElement("img");
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);

setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
})
)
.catch((error) => alert(error.message));

Another complete working example,

// ref: https://thomasstep.com/blog/a-guide-to-using-jwt-in-javascript
const { generateKeyPair } = require("jose");

async function genKeyPair() {
try {
const { publicKey, privateKey } = await generateKeyPair("RS256");
// https://nodejs.org/api/crypto.html#crypto_class_keyobject
const publicKeyString = publicKey.export({
type: "pkcs1-",
format: "pem",
});
const privateKeyString = privateKey.export({
type: "pkcs1",
format: "pem",
});
return [publicKeyString, privateKeyString];
} catch (e) {
const err = new Error(e.code || "Key gen error", { cause: e });
err.name = "KeyGenError";
throw err;
}
}

genKeyPair()
.then((result) => {
console.log(result);
})
.catch((err) => console.log(err.message || err));

JS Error Handling - Best Practices

// Quick Custom Named error
} catch(e) {
const err = new Error(e.code || 'keygen error', {cause : e});err.name = 'KeyGenError';
throw err
}
// to catch the above
try {
//
} catch (e) {
switch (e.name) {
case "KeyGenError":
console.log(e.message || err);
// code block
break;
default:
console.log(e.message || err);
// code block
}
}

Error Object - Instance properties

Error.prototype.message

Error message. For user-created Error objects, this is the string provided as the constructor's first argument.

Error.prototype.name

Error name. This is determined by the constructor function.

Error.prototype.cause

Error cause indicating the reason why the current error is thrown — usually another caught error. For user-created Error objects, this is the value provided as the cause property of the constructor's second argument.

JS - Null Handling

let k = "";
// Here t can be undefined or null as in k.t = null
console.log(k.t?.t);

NPM and NPX

NPM vs NPX

It is simple -> npx executes a script after doing npm install
Example npm "create-react-app": "node tasks/cra.js"

NPM usage

  • npm test, npm start, npm restart, and npm stop are all aliases for npm run xxx.
  • For all other scripts you define, you need to use the npm run xxx syntax.
  • See the docs at https://docs.npmjs.com/cli/run-script for more information.

Check Outdated Packages

Just do npm outdated, give you a list of versions. See bwlow example.

Package             Current  Wanted  Latest  Location                         Depended by
@next/font 13.1.1 13.1.3 13.1.3 node_modules/@next/font rwapp-complib-astra
axios 1.2.2 1.2.3 1.2.3 node_modules/axios rwapp-complib-astra
eslint 8.31.0 8.32.0 8.32.0 node_modules/eslint rwapp-complib-astra

Arrays

Handy snippet - iteration, transform, copy to new

Single line solution.

const kwd_arr_clean = [];
const kwd_arr = kwds.split(",");
kwd_arr.forEach((item) => kwd_arr_clean.push(item.trim()));

Promises

Full file upload example with promises and Promise.all

const upload = (req, res) => {
const uploadBase = "./drop";
const pathName = "/p1";
const uploadPath = path.join(uploadBase, pathName);

if (req.files) {
logger.info("[File upload] received file");
const files = req.files;
var promises = [];

files.forEach((file) => {
const p = new Promise((resolve, reject) => {
createDirIfNotExists(uploadPath);
let fileName = path.join(uploadPath, file.originalname);
fs.writeFile(fileName, file.buffer, "binary", (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
promises.push(p);
});
const aggPromise_saveAllFiles = Promise.all(promises);

aggPromise_saveAllFiles
.then(() => res.send({ msg: "Success" }))
.catch((err) => {
res.send({ msg: err.message });
});
} else {
res.send({ msg: "Fail" });
}
};

Error handling inside Promises

When you are inside a promise block, catch all await errors. Imagine what happens when an error is thrown inside a promise from an async function call.

Firestore example,

async function lookupCredentials(username, pwd) {
const dataRef = db.collection(USER_PROFILE_COLL);
const recSnapshot = await dataRef.where("username", "==", username).get();

const validatePromise = new Promise((resolve, reject) => {
if (recSnapshot.empty) {
reject("no matching records");
}

recSnapshot.forEach(async (doc) => {
const user_data = doc.data();
// console.log(doc.id, '=>', user_data);
// inject id
user_data.id = doc.id;
let val_result;
try {
val_result = await validateHash(pwd, user_data.pwdh);
} catch (val_err) {
reject(val_err);
}
// password validated against hash, user is valid
if (val_result) {
// remove pass hash for log safety
delete user_data.pwdh;
// resolve on first result as you expect only one record
resolve(user_data);
} else {
// val_result is false, password is wrong (against the stored hash)
reject("password missmatch error");
}
});
});

return validatePromise;
}

In this, the follow try..catch is critical, to avoid this nasty error,

[UnhandledPromiseRejection: This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "[ERHASH11] hash mismatch error".] {
code: 'ERR_UNHANDLED_REJECTION'
}
try {
val_result = await validateHash(pwd, user_data.pwdh);
} catch (val_err) {
reject(val_err);
}

Promise Object

Also check out other Promise object methods any, allSettled etc.
ref

JS Array, Map, JSON or Object iteration

Check Out

Also check out Array, Map and Object specs.

Object.values() and Object.keys

Chain -> Map, Filter and Reduce calls

Very useful pattern. Also check out - use of map vs forEach

let ages = data
.filter((animal) => {
return animal.type === "dog";
})
.map((animal) => {
return animal.age * 7;
})
.reduce((sum, animal) => {
return sum + animal.age;
});
// ages = 84

Use for....of for iteration instead of for...in

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of
Iterating over an Array

const iterable = [10, 20, 30];

for (const value of iterable) {
console.log(value);
}
// 10
// 20
// 30

Iterating over a Map

const iterable = new Map([
["a", 1],
["b", 2],
["c", 3],
]);

for (const entry of iterable) {
console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]

for (const [key, value] of iterable) {
console.log(value);
}
// 1
// 2
// 3