Node.js is a JavaScript runtime environment.
For the ORM Sequelize, see https://sites.google.com/site/pawneecity/sequelize
Eg: gsm, ulises
How To Run Multiple Versions of Node.js on Linux (nvm)
https://medium.com/better-programming/how-to-run-multiple-versions-of-node-js-on-linux-c3833f9c9234
Warning: node -v reports the selected version while nodejs -v seems to output the OS default one.
Installing and using
https://github.com/nvm-sh/nvm#installation-and-update
Eg:
$ nvm install 14
Downloading and installing node v14.21.3...
Downloading https://nodejs.org/dist/v14.21.3/node-v14.21.3-linux-x64.tar.xz...
##################################################################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v14.21.3 (npm v6.14.18)
$ node -v
v14.21.3
$ nvm use 14
Now using node v14.21.3 (npm v6.14.18)
.PHONY: help install package
# CONFIG
NVM_DIR := $(HOME)/.nvm
NODE_VERSION = 20
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
install: ## npm install
. $(NVM_DIR)/nvm.sh && nvm use $(NODE_VERSION) && npm install
package: ## npm run package
. $(NVM_DIR)/nvm.sh && nvm use $(NODE_VERSION) && npm run package
The .npmrc is a configuration file for NPM and it defines the settings on how NPM should behave when running commands.
The following sampe files enforce Node.js 18 and NPM 9 (eg when running 'yarn install') the he following error appear in the console:
$ yarn install
yarn install v1.22.17
[1/5] Validating package.json...
error myapp_back@22.2.24-SNAPSHOT: The engine "node" is incompatible with this module. Expected version "^18.18.0". Got "10.23.0"
package.json
{
"engines": {
"npm": ">=9.8.1 <11.0.0",
"node": "^18.18.0"
},
"name": "myapp_back",
"license": "UNLICENSED",
"version": "22.3.44-SNAPSHOT.0",
"description": "",
"dependencies": {
"aws-sdk": "^2.1038.0",
"dotenv": "^10.0.0",
"node-fetch": "2",
"serverless-http": "^2.7.0"
},
"devDependencies": {
"prettier": "^2.5.0",
"serverless-dynamodb-local": "^0.2.40",
"serverless-offline": "^8.3.1",
"serverless-offline-ses": "^0.0.7",
"serverless-offline-sqs": "^6.0.0",
"serverless-s3-local": "^0.6.20"
}
}
.npmrc
engine-strict=true
https://dev.to/nipu/js-cjs-and-mjs-defference-5f21
* .mjs (ECMAScript Module). Uses the import and export keywords (for browsers and server-side)
* .cjs (CommonJS). Uses the require and module.exports syntax (primarily for server-side)
* .js (JavaScript). Generic file extension for JavaScript files
* .d.mts (ECMAScript Module). TypeScript declaration file for implementation file .mjs
* .d.ts (CommonJS & JavaScript). TypeScript declaration file for implementation files .csj & .js
const - declares a constant
let - declares a block-scope variable
var - declares a function-scope variable
Error handling with async/await and promises, n² ways to shoot yourself in the foot
https://catchjs.com/Docs/AsyncAwait
console.log('%s failed to login %i times', user, attempts);
console.log(JSON.stringify(myObject, null, 2));
Here are the most common built-in error types:
1. Error: The base class for all errors in Node.js. All other error types inherit from this. Use it for general-purpose exceptions. throw new Error("General error message");
2. TypeError: Thrown when an operation is performed on an incompatible type. throw new TypeError("This is a type error");
3. RangeError: Thrown when a value is not in the set or range of allowed values. throw new RangeError("Out of range value");
4. SyntaxError: Thrown when there is a syntax error in the code, often encountered in eval() or dynamic code generation. throw new SyntaxError("Invalid syntax");
5. ReferenceError: Thrown when trying to reference a variable that does not exist or is not declared. throw new ReferenceError("Undefined variable");
6. EvalError: Thrown when the eval() function is used incorrectly. In modern environments, it's rarely used. throw new EvalError("Eval error");
7. URIError: Thrown when an invalid global URI handling function (encodeURI(), decodeURI(), etc.) is used. throw new URIError("Malformed URI");
8. AggregateError (Node.js 15+): Thrown when multiple errors need to be aggregated into a single error. Usually used in Promise.any(). throw new AggregateError([new Error("Error 1"), new Error("Error 2")], "Multiple errors occurred");
9. AssertionError: A specific type of error used in Node.js’s built-in assert module, typically used for unit tests and assertions. const assert = require('assert'); assert(false, "Assertion failed!"); // Throws AssertionError
10. System Errors: Node.js has special "system errors" which usually occur when a syscall fails, like file I/O errors, network errors, or other underlying system-related issues. These errors have additional properties like code, errno, syscall, etc. For example, you might encounter errors like ENOENT, EACCES, or ECONNREFUSED. fs.readFile('nonexistentFile.txt', (err, data) => {
if (err) {
console.error(err.code); // e.g., "ENOENT" (file not found)
throw err;
}
});
Eg: Define and use a custom "UnkownTableError" exception:
src/services/appException.mjs
/**
* Eg:
* import { UnknownTableError } from '../../services/appException.mjs';
* throw new CustomError("This is a custom error!");
*/
class UnknownTableError extends Error {
constructor(message) {
super(message);
this.name = "UnknownTableError"; // Optional: Customize the error name
}
}
// EXPORTs =====================================================================
export { UnknownTableError }; // Export the function for use in other modules
Building Lambda functions with Node.js
https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html
package.json
{
"name": "myapp_lambda",
"version": "22.2.10",
...
}
src/services/artifactService.mjs
import { promises as fs } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
/**
* Get this artifact version.
* @returns {string} This artifact version
*/
async function getArtifactVersion() {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const packageJsonPath = join(__dirname, '../../package.json');
const packageJsonData = await fs.readFile(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonData);
// console.info("======== Artifact version: %s ========", packageJson.version);
return packageJson.version
}
// EXPORTs =====================================================================
export { getArtifactVersion }; // Export the function for use in other modules
src/functions/myLambda/index.mjs
import { getArtifactVersion } from '../../services/artifactService.mjs'
// Log artifact version
getArtifactVersion().then(version => { console.info(`======== Artifact version: ${version} ========`); });
package.json
{
"name": "myapp_back",
"version": "22.2.10",
...
}
src/functions/myLambda/handler.js
...
const myLambda = async (event, context, callback) => {
var tech_comp_version = require('../../../package.json').version;
console.info("======== Artifact version: %s ========", tech_comp_version);
...
Note: The path to file package.json must be relative to the directory location of the function
const response_success = {
statusCode: 200,
body: JSON.stringify({
message: 'ok'
}),
};
const response_error = {
statusCode: 400,
body: JSON.stringify({
message: 'error'
}),
};
if (error) {
console.log('END gSuiteCallback:\n%s', JSON.stringify(response_error, null, 2));
return callback(response_error)
} else {
console.log('END gSuiteCallback:\n%s', JSON.stringify(response_success, null, 2));
callback(undefined, response_success)
}
Eg: gsm
Install AWS SDK package:
npm install @aws-sdk/client-lambda
src/services/awsLambdaService.mjs
import { InvokeCommand, LambdaClient } from '@aws-sdk/client-lambda';
const lambdaClient = new LambdaClient();
/**
* Inovkes a lambda function.
* @param {String} functionName Lambda name
* @param {Object} event Payload to sent to the lambda
* @returns {Object} JavaScript Object, representing the Payload returned by the invoked lambda.
*/
async function invokeLambda(functionName, event) {
const params = {
FunctionName: functionName,
Payload: JSON.stringify(event),
};
try {
const command = new InvokeCommand(params);
const response = await lambdaClient.send(command);
// If the target Lambda returns a response, you can parse it
const result = JSON.parse(new TextDecoder().decode(response.Payload));
console.log('END invokeLambda. StatusCode', response.StatusCode, '; Payload(parsed)', result);
return result;
} catch (error) {
console.error('EXC invokeLambda:', error);
return {
statusCode: 500,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(
{
detail: 'Failed to invoke Lambda ' + functionName, // 'detail' as in ProblemDetail (SB)
}, null, 2),
};
}
}
src/services/myService.mjs
Sample that invokes the 'my-lambda-function'. It expects input (event) a subset of the JSON structure a regular lambda webservice gets from its api gateway, eg:
{
"routeKey": "GET /v1/subjects/{subjectId}",
"headers": {
"accept-language": "fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, *;q=0.5"
},
"pathParameters": {
"subjectId": "S27"
},
"queryStringParameters": {
"page": "0",
"size": "2147483647"
}
}
Code that initiates the invocation:
import { invokeLambda } from '../services/awsLambdaService.mjs';
async function sampleFunc(subjectId) {
try {
const lambdaFunction = `my-lambda-function`;
const lambdaEvent = {
"routeKey": "GET /v1/subjects/{subjectId}",
"headers": {
"accept-language": "en"
},
"pathParameters": {
"subjectId": subjectId
},
"queryStringParameters": {
"page": "0",
"size": "2147483647"
}
}
const resPayloadParsed = await invokeLambda(lambdaFunction, lambdaEvent);
const statusCode = resPayloadParsed.statusCode;
console.log('DEBUG, statusCode:', statusCode);
const parsedBody = JSON.parse(resPayloadParsed.body);
console.log('DEBUG, parsedBody:', parsedBody);
} catch (error) {
throw error;
}
}
export { sampleFunc }
Unit Testing in Node.js Using Jest: A Comprehensive Guide > https://medium.com/@ayushnandanwar003/unit-testing-in-node-js-using-jest-a-comprehensive-guide-09717f4438dd
package.json
{
"name": "@edu.cou/app_comp",
"version": "24.10.9-alpha.3",
"type": "module",
"scripts": {
"test": "NODE_OPTIONS=--experimental-vm-modules npx jest --coverage"
},
...
"devDependencies": {
"jest": "^29.7.0",
"@types/jest": "^29.5.13"
}
}
jest.config.js
Optional file to customize the Jest configuration.
export default {
testMatch: [
'**/__tests__/**/*.[jt]s?(x)',
'**/?(*.)+(spec|test).[tj]s?(x)',
'**/?(*.)+(test).[m]js' // To recognize `.test.mjs` files
]
};
$ npm install
$ npm test
Note: If the package.json script 'test' doesn't have the --coverage, it can be generated w/: npm test -- --coverage
Jest will output a coverage summary and create a "coverage" directory with detailed reports.
If ES module to be tested a.mjs imports b.mjs then in the Jest test file a.test.mjs:
Mock b.mjs BEFORE importing a.mjs !!! (otherwise the real b.mjs will be used by a.mjs), eg:
await jest.unstable_mockModule('./b.mjs', async () => {
return {
MyBClass: class {
static init = jest.fn();
},
myBFunction: jest.fn(),
};
});
const { MyBClass, myBFunction } = await import('./b.mjs');
const { MyAClass, myAFunction } = await import('./a.mjs');
import { ietfBcp47FromIso639p3, iso639p3FromHeaderAcceptLanguage, langGatToIso639p3 } from './langService.mjs';
it("should return expected value", async () => {
expect(await ietfBcp47FromIso639p3('cat')).toEqual("ca");
expect(await ietfBcp47FromIso639p3('eng')).toEqual("en");
});
test("bcp47 > iso639p3", async () => {
expect(await iso639p3FromHeaderAcceptLanguage(undefined)).toEqual('eng');
expect(await iso639p3FromHeaderAcceptLanguage('ca')).toEqual("cat");
});
test("non-VALID code > iso639p3", async () => {
let code;
try {
code= undefined;
await ietfBcp47FromIso639p3(code);
fail('it should not reach here');
} catch (e) {
expect(e.message).toBe(`does not know about the language code ${code}.`);
}
try {
code= 'SOS'; //non-existing code
await ietfBcp47FromIso639p3(code);
fail('it should not reach here');
} catch (e) {
expect(e.message).toBe(`does not know about the language code ${code}.`);
}
});