One Page Notes | Best Practices
Must Read
Write Better Code with these 5 JavaScript Features
ES6 Modules and Node.js
Tips
- Use
type='module'inpackage.json .jsfile extension works fine. You dont need.mjswhen 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
.jsextension. For some reason it is mandatory, unlike react. Example:import { cmap, ctx } from '../ercli/commands.js'
- Note the use of
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
- Must read : https://javascript.info/promise-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
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