Your — Codes

🚀 10 Game-Changing Node.js Features You Can't Afford to Miss 🌟

Created At9/17/2024
Updated At9/17/2024

Node.js has undergone significant transformations over the years, shedding its old image and incorporating many new features. In this blog, we’ll explore ten remarkable capabilities that enhance Node.js and make it a compelling choice for developers today.

Top 10 Node.js Features You Must Know for Fast and Scalable Apps

Introduction 🚀

It’s a common misconception that Node.js is outdated. In reality, it’s continually evolving, and recent updates have introduced numerous features that modernize its functionality. Let’s dive into these features and see how they can transform your development experience.

TypeScript Support 🛠️

Screenshot about typescript support node.js

Node.js now includes built-in support for TypeScript, which is a boon for developers who prefer static typing. Traditionally, running a TypeScript file in Node.js would result in numerous parsing errors, but with the latest experimental features, you can use the --experimental-strip-types flag.

terminal
node --experimental-strip-types index.ts

This flag removes TypeScript types and allows the code to run as normal JavaScript.

terminal
(node:5600) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:5600) [MODULE_TYPELESS_PACKAGE_JSON] Warning: /NodeJS/amazing%20features%20of%20nodeJS/index.ts parsed as an ES module because module syntax was detected; to avoid the perfore syntax was detected; to avoid the performance penalty of syntax detection, add "type": "module" to \NodeJS\amazing features of nodeJS\package.json

Although you may receive warnings indicating that this feature is experimental, the functionality works well for most cases.

By adding a type: "module" in your package.json, you can further enhance the performance of your Node.js application.

package.json
{
  "name": "amazing-features-of-nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "typescript": "^5.6.2"
  }
}

Now after adding a type: "module" in your package.json you can see one of the error

terminal
(node:5600) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
- (node:5600) [MODULE_TYPELESS_PACKAGE_JSON] Warning: /NodeJS/amazing%20features%20of%20nodeJS/index.ts parsed as an ES module because module syntax was detected; to avoid the perfore syntax was detected; to avoid the performance penalty of syntax detection, add "type": "module" to \NodeJS\amazing features of nodeJS\package.json

is now gone.

ESM Support 📦

Node.js now fully supports ECMAScript Modules (ESM), allowing for a more modern import/export syntax. Instead of using the traditional require function, you can now use import and export statements.

To make parsing quicker, set the type field in package.json to module. This small change can lead to performance improvements.

Built In Test Runner 🧪

built in test with node.js screenshot

One of the most exciting new features is the built-in test runner in Node.js. You can now run tests without needing to install additional libraries like Jest. This built-in functionality allows you to create test files easily, using the describe and it functions directly from Node.js.

By creating a test file with a .test.js extension, you can write tests and execute them with a simple command. This feature simplifies the testing process and integrates seamlessly into your workflow.

here’s the sample test code of index.test.js for typescript file.

index.test.js
import assert from "node:assert";
import { describe, it } from "node:test";
import { IsAdult } from "./index.ts";

describe("#isAdult", () => {
  it("Check if user is adult or not", () => {
    assert(IsAdult(29));
  });
});

make sure to import typescript file with .ts extension at the end. just like in the example.

Now run test using this commad in terminal.

terminal
node --experimental-strip-types --test

you will get a response look like this

terminal
$ node --experimental-strip-types --test
(node:17932) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:11204) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
{ IsAdult: false }
▶ #isAdult
  ✔ Check if user is adult or not (5.2612ms)
▶ #isAdult (9.0428ms)
ℹ tests 1
ℹ suites 1
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 449.4257

Now, If you also want a test coverage then you can add this experimental flag also -- experimental-test-coverage also.

terminal
node --experimental-strip-types --experimental-test-coverage --test

Now The response should look like this

terminal
$ node --experimental-strip-types --experimental-test-coverage --test
(node:6024) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
(node:9708) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
{ IsAdult: false }
▶ #isAdult
  ✔ Check if user is adult or not (2.3688ms)
▶ #isAdult (5.3847ms)
ℹ tests 1
ℹ suites 1
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 486.4751
ℹ start of coverage report
ℹ --------------------------------------------------------------
ℹ file          | line % | branch % | funcs % | uncovered lines
ℹ --------------------------------------------------------------
ℹ index.test.js | 100.00 |   100.00 |  100.00 |
ℹ index.ts      | 100.00 |   100.00 |  100.00 |
ℹ --------------------------------------------------------------
ℹ all files     | 100.00 |   100.00 |  100.00 |
ℹ --------------------------------------------------------------
ℹ end of coverage report
🌟 You know you can even change the formatting of your test by simply adding one more flag into your command --test-reporter tap

And If you are feeling that the test command is going little bit longer to remember that you also have a choice to add your command to npm scripts like this

package.json
{
  "name": "amazing-features-of-nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "node --experimental-strip-types --experimental-test-coverage --test-reporter tap --test"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "typescript": "^5.6.2"
  }
}

now you just need to run npm run test in your terminal.

Watch Flag ⏳

terminal
node --watch --experimental-strip-types --experimental-test-coverage --test-reporter tap --test

The --watch flag is another fantastic addition. It enables automatic re-running of your code whenever files change.

This means you no longer need to use nodemon or manually restart your tests or applications. Node.js will handle it for you.

Simply run your tests with the --watch flag, and Node.js will monitor your files for any changes, automatically re-running the tests upon saving.

This is how you can update your package.json script.

package.json
{
  "name": "amazing-features-of-nodejs",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "scripts": {
   "test": "node --watch --experimental-strip-types --experimental-test-coverage --test-reporter tap --test"
   "test": "node --experimental-strip-types --experimental-test-coverage --test-reporter tap --test"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "description": "",
  "devDependencies": {
    "typescript": "^5.6.2"
  }
}

env-file Flag 🌍

Node.js now supports loading environment variables directly from a specified .env file using the --env-file flag. This feature allows you to manage your environment variables more effectively without needing additional libraries.

By simply specifying your environment file, Node.js will automatically read the variables, making it easier to manage configurations across different environments.

What you need to do is simply create a .env file in the root and put some env variables or key inside like this

terminal
SECRET_VALUE="@your-ehsan"

now access your env value in file as usual node.js way

index.ts
interface User {
  name: string;
  age: number;
}

const person: User = {
  age: 12,
  name: "John Doe",
};

export function IsAdult(age: number) {
  return age >= 18;
}

console.log({ IsAdult: IsAdult(person.age) }); // { IsAdult: false }
console.log(process.env.SECRET_VALUE); // @your-ehsan

now run your node app using this command

terminal
node --experimental-strip-types --env-file=.env index.ts

Notice that you just need to add another flag --env-file=.env with the location of .env file and you are now good to go.

response of the code

terminal
$ node --experimental-strip-types --env-file=.env index.ts
(node:13220) ExperimentalWarning: Type Stripping is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
{ IsAdult: false }
@your-ehsan

Promise Based Standard Libraries 📜 & top level Await

index.ts
import { readFile } from "fs/promises";

const file = readFile("./index.ts").then((e: Buffer)=> {
console.log(e); // <Buffer 69 6d 70 6f 72 74 20 7b 20 67 6c 6f 62 2c 20 72 65 61 64 46 69 6c 65 20 7d 20 66 72 6f 6d 20 22 66 73 2f 70 72 6f 6d 69 73 65 73 22 3b 0d 0a 0d 0a 63 ... 444 more bytes>
});

Node.js is moving towards a promise-based approach across its standard libraries, reducing reliance on callbacks. For example, the fs.readFile function now supports a promise-based version, making it easier to work with asynchronous file operations.

This change leads to cleaner, more manageable code, allowing you to use then and catch for handling results and errors, respectively.

Top Level Await ⏩

index.ts
import { readFile } from "fs/promises";

const file = await readFile("./index.ts");
console.log(file) // <Buffer 69 6d 70 6f 72 74 20 7b 20 67 6c 6f 62 2c 20 72 65 61 64 46 69 6c 65 20 7d 20 66 72 6f 6d 20 22 66 73 2f 70 72 6f 6d 69 73 65 73 22 3b 0d 0a 0d 0a 63 ... 444 more bytes>

With the latest updates, Node.js now supports top-level await, allowing you to use the await keyword outside of an async function. This simplifies writing asynchronous code and improves readability.

Now, you can write cleaner and more intuitive asynchronous code without nesting functions unnecessarily.

Glob File Search 🔍

index.ts
import { glob } from "fs/promises";

const testFile = await glob("**/*.test.js").next();

console.log(testFile); // { value: 'index.test.js', done: false }

Node.js has also introduced the ability to search for files using glob patterns. This allows you to easily retrieve files that match specific criteria, which is especially useful for testing and managing multiple files in your projects.

By using glob patterns, you can dynamically select files based on naming conventions or file types, streamlining your development process.


Built In Debugger 🐞

Node.js now includes a built-in debugger that integrates seamlessly with Chrome DevTools. By using the --inspect flag, you can debug your Node.js applications just like you would with client-side JavaScript.

This feature allows you to set breakpoints, inspect variables, and step through your code, making debugging far more accessible and efficient.

To access this feature you have to add a flag --inspect-brk to application’s start command like this

terminal
node --experimental-strip-types --env-file=.env --inspect-brk index.ts

Screenshot of node.js debugger from chrome developer tools

Built In Web Socket Support 🌐

index.ts
new WebSocket("wss://whatever-url")

Node.js has added built-in support for WebSockets, allowing you to connect to WebSocket servers directly. While you cannot yet create a WebSocket server, this feature enhances the capabilities of Node.js for real-time applications.

The addition of WebSocket support means fewer dependencies and a more streamlined development process for applications requiring real-time communication.

Built In SQLite Database 🗄️

Finally, Node.js now offers built-in support for SQLite databases. This allows developers to easily connect to SQLite databases, execute SQL statements, and manage data directly within their applications.

This feature simplifies database management and enables developers to deploy applications with database capabilities without relying on external libraries.

To access this latest feature you have to follow these steps

Step 1: Import the DatabaseSync class

index.ts
import { DatabaseSync } from "node:sqlite";
  • This line imports the DatabaseSync class from the node:sqlite module. This class allows synchronous access to an SQLite database in Node.js.
  • node:sqlite refers to the SQLite API in Node.js (usually for managing SQLite databases).

Step 2: Create an in-memory SQLite database

index.ts
const database = new DatabaseSync(":memory:");
  • This creates a new SQLite database in memory (temporary storage, cleared when the process exits).
  • ":memory:" specifies that the database is in-memory rather than being stored on disk.

Step 3: Create a table called data

index.ts
database.exec(`
    CREATE TABLE data(
        key INTEGER PRIMARY KEY,
        value TEXT
    ) STRICT
`);
  • database.exec() executes an SQL command.
  • This SQL command creates a table named data with two columns:
  • key: an integer that acts as the primary key (each key is unique).
  • value: a text field that will store string data.
  • STRICT: This ensures that the table enforces stricter type checking on the data types. (It is a feature available in recent versions of SQLite to avoid silent type conversions.)

Step 4: Prepare an SQL statement for inserting data

index.ts
const insert = database.prepare("INSERT INTO data (key, value) VALUES (?, ?)");
  • database.prepare() prepares an SQL statement to insert data into the data table.
  • The ? placeholders represent values that will be provided when the statement is executed.

Step 5: Insert data into the table

index.ts
insert.run(1, "hello");
insert.run(2, "world!");
  • insert.run() runs the prepared INSERT statement with actual values.
  • The first call inserts 1 as the key and "hello" as the value into the data table.
  • The second call inserts 2 as the key and "world!" as the value into the data table.

Step 6: Prepare an SQL statement for querying data

index.ts
const query = database.prepare("SELECT * FROM data ORDER BY key");
  • This prepares an SQL SELECT statement to retrieve all rows from the data table.
  • The results will be ordered by the key column in ascending order (the default order for ORDER BY).

Step 7: Execute the query and log the result

index.ts
console.log(query.all()); 
/**
[
 { key: 1, value: 'hello' },
 { key: 2, value: 'world!' }
]
*/
  • query.all() executes the prepared SELECT statement and retrieves all rows from the table.
  • The result is an array of objects, each representing a row from the table.
  • console.log() outputs the result to the console. Given the inserted data, the output will be:

Here’s the complete code for node.js built-in sqlite support 🌟

index.ts
import { DatabaseSync } from "node:sqlite";

const database = new DatabaseSync(":memory:");

database.exec(`
    CREATE TABLE data(
        key INTEGER PRIMARY KEY,
        value TEXT
    ) STRICT
`);

const insert = database.prepare("INSERT INTO data (key, value) VALUES (?, ?)");

insert.run(1, "hello");
insert.run(2, "world!");

const query = database.prepare("SELECT * FROM data ORDER BY key");

console.log(query.all());

To run this code you have to add a new experimental flag--experimental-sqlite . Otherwise this will not work.

terminal
node --experimental-strip-types --env-file=.env --experimental-sqlite index.ts

Conclusion 🎉

Node.js has significantly evolved, incorporating features that enhance developer experience and streamline workflows. From TypeScript support to built-in database capabilities, these updates position Node.js as a modern and powerful tool for web development. Stay updated with these features, and consider incorporating them into your next project!