Node.js Built-in Features You Did Not Know Exist
Let’s cover couple of features you may not know in current version of NodeJS have, expecting Node v25.0.0
Built-in SQLite Support
- SQLite is an amazing database engine that allows you to create a database without a lot of setup or configuration. It’s in Active development , but can be used. No need to use external package like
better-sqlite3(It’s amazing no offense) anymore. Let’s see in Example how to use it
"use strict";
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("my-db.db");
db.exec(`
CREATE TABLE IF NOT EXISTS data(
key INTEGER PRIMARY KEY,
value TEXT
) STRICT
`);
const insert = db.prepare("INSERT INTO data (value) VALUES (?)");
insert.run("test");
insert.run("test2");
insert.run("test3");
setTimeOut
- Generally for create a delay in a function you might be doing
new Promise((resolve)=>setTimeOut(resolve,3000))but not needed as in node’s built-int timers have this covered and it was way old like it cam in in NOdejs 15 (2020), Let’s see a example of how to use it,
import { setTimeout } from "node:timers/promises";
console.log("HI Before Settimeout");
await setTimeout(3000, "Testing");
console.log("HI after Settimeout");
.env
- Storing creds in
.envif not why not you should. Node supports flag like--env-file=.envthat can do this on the go and it’s quite handy as a flag to pass on the go and in any deployment environment
## .env file
test="A SUPER SECRET"
## my-app.js
console.log(process.env.test); // A SUPER SECRET
## Command
node --env-file=.env my-app.js
AsyncLocalStorage
It can be used to store data in memory throughout the lifetime of a web request or other async operations, you can use this API to store info about an authenticated user like user-id or in different middlewares you can use this.
import { AsyncLocalStorage } from "node:async_hooks";
import http from "node:http";
// Create a store to hold per-request context
const requestContext = new AsyncLocalStorage();
// Middleware-style usage
function withRequestContext(req, res, next) {
const context = {
requestId: crypto.randomUUID(),
userId: req.headers["x-user-id"] ?? null,
startTime: Date.now(),
};
// Everything called inside this callback shares the same context
requestContext.run(context, () => next(req, res));
}
// Access from anywhere in the call chain — no need to pass it down
function logInfo(message) {
const ctx = requestContext.getStore();
console.log(`[${ctx?.requestId}] [user:${ctx?.userId}] ${message}`);
}
async function handleRequest(req, res) {
logInfo("Request started");
// Even deep in async calls, context is available
await fetchUserData();
logInfo("Request finished");
res.end("Done");
}
async function fetchUserData() {
const ctx = requestContext.getStore();
logInfo(`Fetching data for user ${ctx?.userId}`);
// ctx is still available here — no need to pass it as a parameter!
}
const server = http.createServer((req, res) => {
withRequestContext(req, res, handleRequest);
});
server.listen(3000);
File Search with Glob
- Yeah you are right if i am going system level i probably use
Rustbut Let’s stick to Node for know Lot of time we build something that work with files or directories withglobabout to find all the files and folders match pattern defined by you is built in. for example if you need to find all the.yamlfiles you can just do(*.yaml)and you’ll be good to go. Let’s see a example as
import { glob } from "node:fs/promises";
for await (const file of glob("yaml/*.yaml")) {
console.log(file);
}
Websocket client
- While connecting to other websocket servers inside your application as
const socket = new WebSocket(`ws://localhost:8001`);
socket.addEventListener('open', (event) => {
socket.send("Sending to other server");
});
socket.addEventListener('message', (event) => {
console.log("Received message from server:", event.data);
});
});
Built-in Test Runner
- let’s talk about test as this article is about to end and we should write test but not at the end but wherever or whenever possible. Even go with TDD if possible.
import { describe, it, before, after } from "node:test";
import assert from "node:assert/strict";
// Basic test
it("adds two numbers", () => {
assert.equal(1 + 1, 2);
});
// Grouped tests with describe
describe("Array utilities", () => {
it("returns the first element", () => {
const arr = [1, 2, 3];
assert.equal(arr[0], 1);
});
it("returns undefined for empty array", () => {
const arr = [];
assert.equal(arr[0], undefined);
});
});
// Async tests
describe("Async operations", () => {
it("resolves a promise", async () => {
const result = await Promise.resolve(42);
assert.equal(result, 42);
});
});
// Lifecycle hooks
describe("Database tests", () => {
let db;
before(async () => {
db = await connectToDb();
});
after(async () => {
await db.close();
});
it("inserts a record", async () => {
const result = await db.insert({ name: "test" });
assert.ok(result.id);
});
});
// Mocking with mock.fn()
import { mock } from "node:test";
it("calls the callback", () => {
const fn = mock.fn((x) => x * 2);
fn(3);
assert.equal(fn.mock.calls.length, 1);
assert.equal(fn.mock.calls[0].arguments[0], 3);
assert.equal(fn.mock.results[0].value, 6);
});
Run tests with:
node --test # Run all test files
node --test --test-reporter=tap # TAP output
node --test src/**/*.test.js # Specific files