Node 22 Features: Learning from Peers
Node.js is bringing the best out of Bun and Deno into the Node 22 release!
Node 22 has been listening to its competition (Bun and Deno), and have added features to level the playing field. It is exciting to see Node go in this direction, given that it is one of the most popular runtimes on the web.
Bun and Deno
So, for the uninitiated; what is Bun and Deno.
Both of these are alternatives to the Node/NPM ecosystem. In spite of Node/NPM being popular, its historic context makes it a little bit of a brittle system.
Like any popular system, Node had to undergo a lot of social challenges such as changed of hands with Joyent and back to the community, the io.js
fork drama (just to name a few). Not to mention the fast pace of changes on the Web. Typescript becoming the de-facto tool for enterprises (due to code maintainability in large software projects), people ditching CommonJS (initially part of Node.js) in favour of ESM (browser-based).
So Bun and Deno came up into the Node.js scene as an alternative to help alleviate a lot of developer's frustrations and feature requests.
Bun
Bun is created by Jarred Sumner whose motivation is to make Javascript faster and simpler to write.
Bun is bringing all Node.js ecosystem in 1 place:
It is a :
Javascript Bundler
Javascript Package Manager
Test runner
Can als
It also has SQLite and implements Websocket natively. It can also run Shell scripts with JS/TS
Deno
Created by the original creator of Node.js; Ryan Dahl. Deno aims to resolve a lot of regrets by Ryan Dahl.
You can see this talk for more context on the regrets as well as initial design goals for Deno
Node 22 features
All code can be found here:
Code sandbox link
Typescript
Inspired by Deno's native Typescript support
You can now run .ts
files in Node, without need of tsc
or any compiler.
Right now, the feature is very minimal. It only strips way types and does not support a lot of Typescript features. Nonetheless, it is a step in the right direction.
let typedVariable : string = "Hello typed Node.js";
console.log(typedVariable)
This just works and gives the following output:
> nodejs-sandbox@1.0.0 node22-typescript
> NODE_OPTIONS=--disable-warning=DeprecationWarning npx node-nightly --no-warnings --experimental-strip-types features/node-typescript.ts
New nightly available. To upgrade: `node-nightly --upgrade`
Hello typed Node.js
Code example can be found here:
You need to enable experimental flag under Node's nightly build:
npx node-nightly --experimental-strip-types features/node-typescript.js
ESM and CJS
Inspired by Bun Module resolution
One of main issues in Node.js packages are the module resolution.
Using both Common.js (Node.js based and legacy) and native ES Modules (newer, browser-based) was a pain.
Bun provided a way to do this when it first released.
In current Node, when you run this code
const { esmExample } = require('./esm')
const { cjsExample } = require('./commonJs')
console.log(esmExample('(New) Hello ESM!'))
console.log(cjsExample('(Legacy)Hello CJS!'))
// esm.js
export function esmExample(str) {
return str;
}
// commonJs.js
function cjsExample(str) {
return str;
}
module.exports = { cjsExample }
You would get this error:
export function esmExample(str) {
^^^^^^
SyntaxError: Unexpected token 'export'
With Node 22's --experimental-require-module
, this is a thing of the past.
Running this
Give this output:
> NODE_OPTIONS=--disable-warning=DeprecationWarning npx node-nightly --no-warnings --experimental-require-module features/esm-cjs/node-native-esm.js
New nightly available. To upgrade: `node-nightly --upgrade`
(New) Hello ESM!
(Legacy)Hello CJS!
Voila, all the pain of using ESM-only packages just goes away! (Hopefully)
SQLite
Inspired by Bun's SQLite Driver
Can create a database table by importing node:sqlite
package
import { DatabaseSync } from 'node:sqlite'
const database = new DatabaseSync(':memory:')
database.exec(`
CREATE TABLE node_db (
id integer primary key,
name text
)
`)
Insert Scripts can be added using the prepare
method:
const insertScript = database.prepare(`insert into node_db (id, name) values (:id, :name)`)
console.log('----- Inserting data into Node SQLite; John and Mae')
insertScript.run({id: 1, name: 'John'})
insertScript.run({id: 2, name: 'Mae'})
If you want to be insecure, you can directly use the SELECT
and get the data like this:
const getDataSQLInjection = database.prepare(`select * from node_db where name = 'John' OR 1=1`)
console.log('----- Getting `John` User from Node SQLite')
console.log('----- ๐ฉ SQL Injection!!!!')
console.log(getDataSQLInjection.all())
And get all user details:
----- Getting `John` User from Node SQLite
----- ๐ฉ SQL Injection!!!!
[ { id: 1, name: 'John' }, { id: 2, name: 'Mae' } ]
But if you are good developer, you need to parameterise your queries which can be done like this:
const getDetailWithoutSQLInjection = database.prepare(`SELECT * FROM node_db WHERE name = :nameParam`);
console.log('----- Getting `John` User from Node SQLite')
console.log('----- โ
No naive SQL Injection!!!!')
console.log(getDetailWithoutSQLInjection.all({'nameParam': 'John'}))
// Below will not return empty as prepared statement will sanitize
// console.log(getDetailWithoutSQLInjection.all({'nameParam': 'John OR 1=1'}))
----- Getting `John` User from Node SQLite
----- โ
No naive SQL Injection!!!!
[ { id: 1, name: 'John' } ]
Full code snippet can be found and run here by running npm run node22-esm
:
You need to enable experimental flag under Node's nightly build:
npx node-nightly --experimental-sqlite features/node-sqlite.js
Closing thoughts
Node has been listening to competition and embracing the features. Let's wait and see how these features evolve.