Developed By Ken Key

Node.js in 2026: What Actually Changed and How to Run It Right

By Ken Key Nov 15, 2024 5 min read Updated Oct 16, 2026

Node.js in 2026: What Actually Changed and How to Run It Right
The short answer

Node.js in 2026 ships with a stable built-in test runner, native TypeScript support, and a real watch mode, so you can drop Jest, ts-node, and nodemon from most projects. Run node --test and node --watch instead. Node still isn't the right pick for heavy CPU work.

Part of the complete web development guide.

On this page

Node.js has changed a lot in the last few years, and a lot of the advice floating around is stuck in 2019. If you’re maintaining a Node app or starting a new one, here’s what actually matters now: what’s built into the runtime, what you no longer need a package for, and where Node is still the wrong tool.

I’ve been shipping Node services for years, mostly for small businesses and agencies around Commack and the rest of Long Island. This is the version of the advice I’d give a friend over coffee, not the version that sounds good in a conference talk.

Node finally stopped making you install everything

The biggest shift is that Node absorbed a bunch of jobs that used to require dependencies. If you’re still adding Jest, ts-node, and nodemon to every project out of habit, you can stop.

The built-in test runner is real now

node:test shipped stable and it’s genuinely usable. No config file, no transform step, no 40MB of dependencies.

// math.js
export function totalWithTax(cents, rate) {
 if (!Number.isInteger(cents)) throw new TypeError('cents must be an integer');
 return Math.round(cents * (1 + rate));
}

// math.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { totalWithTax } from './math.js';

test('adds NY sales tax', () => {
 assert.equal(totalWithTax(10000, 0.08625), 10863);
});

test('rejects non-integer input', () => {
 assert.throws(() => totalWithTax(10.5, 0.08), TypeError);
});

Run it with node --test. That’s it. For most small and mid-size projects this replaces Jest entirely.

TypeScript without a build step

Node can strip TypeScript types natively. You write .ts, you run node app.ts, and the types disappear at load time. It does not type-check, so keep tsc --noEmit in CI, but you no longer need a separate build pipeline just to run your code.

// app.ts
type Plan = { name: string; monthlyCents: number };

const plans: Plan[] = [
 { name: 'basic', monthlyCents: 4900 },
 { name: 'pro', monthlyCents: 9900 },
];

function cheapest(plans: Plan[]): Plan {
 return plans.reduce((a, b) => (b.monthlyCents < a.monthlyCents ? b : a));
}

console.log(cheapest(plans).name); // basic

Two caveats. Enums and namespaces need real compilation, so avoid them or keep a build step. And this only works on files you control. Anything in node_modules still needs to ship as JavaScript.

The permission model

Node can now lock down what a process is allowed to do at the OS level. This is the single best cheap security win available today.

# This process cannot read arbitrary files or open sockets.
# It can only read ./public and write ./tmp.
node --permission 
 --allow-fs-read=./public 
 --allow-fs-write=./tmp 
 server.js

If your app gets compromised, the attacker's reach is dramatically smaller. I turn this on for anything that renders user input or touches uploaded files.

Where Node still wins, and where it doesn't

Node is excellent at I/O-bound work: APIs, real-time features, webhooks, anything that spends its life waiting on a database or another service. It's a bad fit for heavy CPU work. Image resizing, video encoding, and big PDF generation will block the event loop and stall every other request on that core.

When you hit that wall you have three honest options:

  • Push the work to a worker thread with node:worker_threads.
  • Move it to a queue and process it in a separate service.
  • Use a language that's actually built for it.

What you should not do is sprinkle async on a CPU-bound function and hope. It won't help.

Pick the right database driver

For Postgres, pg is fine and boring, which is a compliment. For MySQL, mysql2 with promises. Avoid ORMs that generate surprise queries in a loop. If you want an ORM, use one where you can see the SQL it produces. Prisma and Drizzle both let you.

import { Pool } from 'pg';

const pool = new Pool({
 connectionString: process.env.DATABASE_URL,
 max: 10, // keep this below your DB's connection limit
 idleTimeoutMillis: 30000,
});

export async function getOrder(id) {
 const { rows } = await pool.query(
 'SELECT id, total_cents, status FROM orders WHERE id = $1',
 [id]
 );
 return rows[0] ?? null;
}

Note the parameterized query. String-concatenated SQL is still the most common way I see Node apps get owned.

Deployment in 2026: what I actually do

For most small business sites and internal tools, a single VPS running the app behind Caddy or nginx is cheaper and simpler than a container orchestra. I use Docker when there are multiple services or when the client has a real ops team. I don't use it because it's fashionable.

The basics that matter more than your framework choice:

  • Run as a non-root user with the permission model on.
  • Set NODE_ENV=production and a real memory limit.
  • Log structured JSON, not console.log('here').
  • Restart on crash with systemd or a process manager, and alert when it restarts.

If you're building something that mixes a Node API with a marketing site and a CMS, that's usually a good fit for custom web development rather than gluing three SaaS products together.

Common mistakes I still see in code review

  • Trusting req.body. Validate every field with a schema (Zod, Valibot) before it touches your database.
  • Awaiting inside a forEach. It doesn't wait. Use for...of or Promise.all.
  • Unbounded Promise.all. Ten thousand parallel requests will take down your database. Batch them.
  • Leaking secrets into logs. Redact tokens and auth headers at the logger level, not at each call site.
  • Ignoring unhandled rejections. Log them and exit. A process in an unknown state is worse than a restart.

FAQ

Do I still need Express?

No, but it's not wrong. Node's built-in HTTP server handles a lot, and Fastify is faster and has better defaults. Express is still the easiest thing to hire for and read six months later. Pick based on your team, not benchmarks.

Is Node fast enough for my site?

Almost certainly. For a typical business site or API, the bottleneck is your database queries and your hosting, not the runtime. Fix those first.

Should I use TypeScript?

Yes, and with native type stripping in 2026 there's little reason not to. Keep tsc --noEmit in CI so you actually get the type checking you're paying for.

Have a project like this?

I do custom web development for Long Island businesses. If you need this done right, let’s talk.