π 10 Game-Changing Node.js Features You Can't Afford to Miss π
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.
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 π οΈ
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.
node --experimental-strip-types index.ts
This flag removes TypeScript types and allows the code to run as normal JavaScript.
(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.
{
"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
(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 π§ͺ
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.
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.
node --experimental-strip-types --test
you will get a response look like this
$ 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.
node --experimental-strip-types --experimental-test-coverage --test
Now The response should look like this
$ 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
{
"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 β³
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.
{
"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
SECRET_VALUE="@your-ehsan"
now access your env value in file as usual node.js way
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
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
$ 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
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 β©
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 π
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
node --experimental-strip-types --env-file=.env --inspect-brk index.ts
Built In Web Socket Support π
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
import { DatabaseSync } from "node:sqlite";
- This line imports the
DatabaseSync
class from thenode: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
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
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
const insert = database.prepare("INSERT INTO data (key, value) VALUES (?, ?)");
database.prepare()
prepares an SQL statement to insert data into thedata
table.- The
?
placeholders represent values that will be provided when the statement is executed.
Step 5: Insert data into the table
insert.run(1, "hello");
insert.run(2, "world!");
insert.run()
runs the preparedINSERT
statement with actual values.- The first call inserts
1
as the key and"hello"
as the value into thedata
table. - The second call inserts
2
as the key and"world!"
as the value into thedata
table.
Step 6: Prepare an SQL statement for querying data
const query = database.prepare("SELECT * FROM data ORDER BY key");
- This prepares an SQL
SELECT
statement to retrieve all rows from thedata
table. - The results will be ordered by the
key
column in ascending order (the default order forORDER BY
).
Step 7: Execute the query and log the result
console.log(query.all());
/**
[
{ key: 1, value: 'hello' },
{ key: 2, value: 'world!' }
]
*/
query.all()
executes the preparedSELECT
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 π
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.
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!