How to upgrade a Turborepo to NestJS 12 and TypeScript 6 (with benchmarks)
Piotr Żarów
CEO at Dev and Deliver
2026-10-09
#Development
Time to read
10 mins
In this article
Introduction
What is NestJS 12?
Short answer
What we found (measured October 2026)
The setup we upgraded
Part 1: TypeScript 6 only needs tsconfig changes
Part 2: What 'nest upgrade' does in a pnpm monorepo
Part 3: Does a CommonJS app survive ESM-only NestJS?
Part 4: Benchmarks, and the false alarm
Part 5: Can you use TypeScript 7 yet?
Upgrade checklist
Is the upgrade worth it?
FAQ
Closing: Measure the thing you ship
Share this article
Introduction
This is part 7 of Building a Turborepo starter in public, a series in which we build and ship a full-stack TypeScript monorepo with Next.js, NestJS and Turborepo, one step at a time. Previously in the series:
- How to Structure a Full-Stack TypeScript Monorepo: Next.js, NestJS, Turborepo
- Building a Landing Page with Next.js 16, Tailwind v4 and Framer Motion
- Wiring a Next.js Landing Page to Contentful Without the SDK
- Embedding a Self-Hosted Cal.diy Calendar in the Next.js App Router
- Deploying a Turborepo Monorepo: NestJS to Railway, Next.js to Vercel
- How to Deploy a NestJS API to a Free Oracle Cloud VPS (Arm, Caddy, PM2)
NestJS 12 is out, and its upgrade path assumes TypeScript 6. We upgraded this series' pnpm monorepo to both, kept a log of every error, and benchmarked the result on three Node versions.
What is NestJS 12?
NestJS 12 is the major release of the NestJS framework that ships its core packages as ES modules, adds Standard Schema support for validation, and adds a nest upgrade command to the CLI. Existing CommonJS applications keep working through Node's require(esm), so converting your own code to ESM is optional. (NestJS migration guide)
Short answer
Upgrading this Turborepo to TypeScript 6 and NestJS 12 took six manual fixes and changed no application code. TypeScript 6 needed four tsconfig edits. nest upgrade bumped the Nest packages cleanly but tried to run npm install in a pnpm workspace, and it left @nestjs/throttler on a version that doesn't accept Nest 12 (measured: our own upgrade, October 2026).
On the same Node version, NestJS 12 matched or exceeded NestJS 11's requests per second. It started about 100 ms slower and used 16-23 MiB more memory at idle (measured).
Moving the same API from Node 22 to Node 24 more than doubled its throughput, from 14,972 to 33,210 requests per second on a health endpoint (measured). No change to NestJS came close to that.
The upgrade itself is small. Benchmarking it took far longer: our first benchmark said NestJS 12 halved throughput, and that turned out to be wrong.
What we found (measured October 2026)
- TypeScript 6 is a configuration change. Upgrading this monorepo from TypeScript 5.9.3 to 6.0.3 needed four
tsconfigedits, and the compiled JavaScript of the NestJS API was byte-identical before and after. nest upgradechanged one file. In a pnpm workspace, NestJS CLI 12.0.8'snest upgraderewrote onlyapps/api/package.json. It also schedulesnpm install, so run it with--skip-installand runpnpm installyourself.- CommonJS keeps working. A CommonJS NestJS API ran on NestJS 12.1.2 with no code change on Node 22.23.2, 22.23.3, 24.21.0 and 26.11.1.
- Throughput is unchanged, resources are not. Under realistic load, NestJS 12 matched or exceeded NestJS 11's requests per second on each Node version. It started about 100 ms slower and used 16-23 MiB more memory at idle.
- Node matters more than the framework. The same NestJS 11 API served 14,972 requests per second on Node 22.23.3 and 33,210 on Node 24.21.0.
- Jest needs extra work. Jest 30.5.2 could not load NestJS 12 packages from a CommonJS test with its default configuration on Node 22, 24 or 26.
- TypeScript 7 is limited to type-checking. TypeScript 7.0.2 type-checked the API in 93 ms against 589 ms for TypeScript 6.0.3, but
nest buildrefuses to run with it.
All measurements: Apple M3 Pro, medians of five interleaved runs after one warm-up.
The setup we upgraded
This is the series repo: a Turborepo with a NestJS API in apps/api, a Next.js 16 site in apps/web, and shared @repo/types and @repo/ui packages, all managed by pnpm. The API runs on an Oracle Cloud VPS under PM2, as set up in article 6; the site runs on Vercel.
| Before | After | |
|---|---|---|
| NestJS (@nestjs/core) | 11.1.28 | 12.1.2 |
| Nest CLI | 11.0.24 | 12.0.8 |
| TypeScript | 5.9.3 | 6.0.3 |
| @nestjs/throttler | 6.5.0 | 6.7.1 |
| pnpm / Turborepo | 11.17.0 / 2.10.7 | unchanged |
| Node (production) | 22.23.2 | unchanged |
We upgraded in two separate steps, TypeScript first and NestJS second, each on its own branch. That way every error and every benchmark difference has exactly one cause.
Before you start: check your Node version
The NestJS migration guide sets two separate minimums:
- Running a NestJS 12 app: Node 20.19+ or 22.12+.
- CLI commands such as
nest upgrade: Node 22.22.3+, 24.15+ or 26+.
The new packages declare it too. @nestjs/swagger 12.0.2 requires ^20.19.0 || >=22.12.0, and @nestjs/throttler 6.7.1 requires ^20.19.0 || ^22.12.0 || >=24.0.0. Our production server runs 22.23.2, which clears both bars.
Part 1: TypeScript 6 only needs tsconfig changes
TypeScript 6.0 is the major release that deprecates the compiler options TypeScript 7.0 no longer supports, such as moduleResolution: "node" and baseUrl. It also changes defaults, including strict (now true) and types (now empty). (TypeScript 6.0 announcement)
Don't run 'pnpm add typescript@latest'
As of 8 October 2026, typescript@latest on npm points to 7.0.2. TypeScript 7 ships without a programmatic API, which the Nest CLI needs (more on that in Part 5). Pin the major explicitly:
1pnpm -r --include-workspace-root up "typescript@^6.0.3"
-r --include-workspace-root matters in a monorepo: TypeScript was declared in all six package.json files, root included. If your pnpm-workspace.yaml has a catalog: entry for TypeScript, update that instead.
The two errors you will see
TypeScript 6 turns long-standing deprecations into hard errors. Our first build failed in the shared packages:
1error TS5107: Option 'moduleResolution=node10' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
Once those were fixed, the API and the Next.js app failed on the second one:
1error TS5101: Option 'baseUrl' is deprecated and will stop functioning in TypeScript 7.0. Specify compilerOption '"ignoreDeprecations": "6.0"' to silence this error.
The error message offers "ignoreDeprecations": "6.0" as a way out. Don't take it. It only postpones the same two errors to TypeScript 7, where they can't be silenced.
The four fixes
| File | Change | Why |
|---|---|---|
| packages/config/tsconfig/nestjs.json | moduleResolution: Node → Bundler | node10 resolution is deprecated |
| packages/types/tsconfig.json, packages/ui/tsconfig.json | moduleResolution: Node → Bundler | same |
| apps/api/tsconfig.json | remove baseUrl | deprecated; the API had no paths, so it did nothing anyway |
| apps/web/tsconfig.json | remove baseUrl | deprecated; "@/*": ["./src/*"] already starts with ./ |
For the shared Nest config, the whole diff is one line:
1 2 3 4 5 6"compilerOptions": { "module": "CommonJS", - "moduleResolution": "Node", + "moduleResolution": "Bundler", "experimentalDecorators": true, "emitDecoratorMetadata": true,
And for the Next.js app, baseUrl simply goes. The paths entry already resolves relative to the tsconfig:
1 2 3 4 5"compilerOptions": { - "baseUrl": ".", "paths": { "@/*": ["./src/*"] }
If your paths entries don't start with ./, add it when you remove baseUrl.
Why Bundler and not NodeNext for a CommonJS API? TypeScript 6 explicitly allows moduleResolution: "bundler" together with module: "commonjs" as the migration path for exactly this case (TypeScript 6.0 announcement). NodeNext would also change module and make you think about file extensions, which is a different migration.
The proof that this was purely a configuration change: the compiled dist/ of the API and both shared packages was byte-identical before and after (measured: diff -r of the build output). In total the step touched 12 files, and outside the lockfile only 9 lines were added and 11 removed.
The trap that didn't fire (yet)
TypeScript 6 changed the default of types to [], so @types/node is no longer included automatically. Our API still compiled, and that looked suspicious.
tsc --explainFiles showed why: @types/node arrives through a /// <reference types="node" /> inside dotenv's type definitions. If you ever drop dotenv, process.env stops type-checking. Add "types": ["node"] to your Nest tsconfig on purpose rather than relying on that accident.
Part 2: What 'nest upgrade' does in a pnpm monorepo
nest upgrade is a NestJS CLI 12 command that moves every @nestjs/* package in a project to its version 12 release in one step. It applies the mechanical parts of the migration and prints a report of what still needs manual review. (NestJS migration guide)
The NestJS 12 flow is: install the new CLI, then let it upgrade the project.
1 2pnpm --filter @repo/api add -D @nestjs/cli@latest @nestjs/schematics@latest cd apps/api && pnpm exec nest upgrade --dry-run --no-observe
--no-observe skips the prompt for @nestjs/observe, the new observability SDK. It's optional, and it doesn't belong in a version upgrade.
Why you need '--skip-install'
The dry run printed this among its actions:
1 2✔ Updated NestJS packages: @nestjs/common@^12.0.0, @nestjs/core@^12.0.0, @nestjs/testing@^12.0.0, @nestjs/platform-express@^12.0.0, @nestjs/cli@^12.0.0, @nestjs/schematics@^12.0.0, @nestjs/swagger@^12.0.0 ✔ Scheduled "npm install"
nest upgrade runs npm install, even in a pnpm workspace. npm doesn't understand the workspace:* protocol, and it would leave a package-lock.json next to your pnpm-lock.yaml. Run it like this instead:
1 2cd apps/api && pnpm exec nest upgrade --no-observe --skip-install cd ../.. && pnpm install
What it changed, exactly
We saved every package.json, every tsconfig, nest-cli.json and the lockfile before running it, then diffed. It changed one file: the seven @nestjs/* version ranges in apps/api/package.json. It didn't touch TypeScript, engines, vitest, @types/node, any tsconfig or nest-cli.json.
Most NestJS 12 headlines are about new defaults: Rspack instead of webpack, oxlint instead of ESLint, and Vitest for ESM projects. Those apply to projects created with nest new, not to upgrades. Our ESLint, vitest and nest-cli.json setup came through untouched.
One side effect: it rewrote @nestjs/cli from ^12.0.8 to ^12.0.0. The installed version doesn't change, but your package.json now allows an older CLI than the one you just added.
The package it leaves behind
The upgrade report ends with a warning:
1⚠ The following @nestjs packages were left untouched because their v12-compatible release is not known to this schematic: @nestjs/throttler.
pnpm peers check confirmed it: throttler 6.5.0 declares support up to Nest 11. Releases from 6.6.0 onward allow Nest 12, so we installed the latest:
1 2pnpm --filter @repo/api add "@nestjs/throttler@^6.7.1" pnpm peers check # No peer dependency issues found
That bump is the reason for most of Part 4.
What didn't need changing
We walked through the migration guide item by item. For this API, none of the following required a code change:
- ESM packages: the CommonJS API loads ESM Nest 12 through
require(esm). @Optional()inheritance: our guard subclassesThrottlerGuard, whose constructor has no@Optional().ValidationPipe: the 400 responses were byte-for-byte the same.- Express error mapping: 404 bodies were unchanged.
- Structured logging: we only log strings.
- Swagger 12:
/docsstill served all routes in development.
Lifecycle-hook ordering, NATS, Joi, Terminus, GraphQL and webpack didn't apply to this repo.
We checked all of this with a smoke test against the built API: health check, valid and invalid requests, an unknown field (forbidNonWhitelisted), and twelve quick requests to confirm the rate limiter still answers 429 from the eleventh. NestJS 11 and 12 returned identical responses.
Part 3: Does a CommonJS app survive ESM-only NestJS?
require(esm) is Node.js support for loading an ES module with a CommonJS require() call. It is what lets an existing CommonJS NestJS app use the ESM-only NestJS 12 packages without converting its own code. (NestJS migration guide)
It does, on every Node version we tried. The API package has no "type" field, so it's CommonJS, while @nestjs/core 12 is "type": "module". The built API started and served traffic with no code change and no runtime warnings on Node 22.23.2, 22.23.3, 24.21.0 and 26.11.1 (measured). What broke was tooling around the app.
'require("@nestjs/core/package.json")' now throws
The Nest 12 core packages don't export ./package.json. Any script that reads Nest's version that way now gets MODULE_NOT_FOUND. Read the file from disk instead.
Jest can't load NestJS 12 (Vitest can)
This repo tests with vitest, which handled the upgrade without any changes. To check Jest, we ran a minimal probe outside the repo: a CommonJS Jest test that require()s @nestjs/core and @nestjs/common.
| Node | Jest 30.5.2, default config | with --experimental-vm-modules |
|---|---|---|
| 22.23.3 | fails | fails |
| 24.21.0 | fails | passes |
| 26.11.1 | fails | passes |
The migration guide says Jest can load the v12 packages only on Node 24.9+, and that older versions fail with ERR_REQUIRE_ASYNC_MODULE. That's not what we observed:
- We never saw
ERR_REQUIRE_ASYNC_MODULE. The failure is Jest's own error,Must use import to load ES Module. - Jest's message also recommends "Node v24.9+", yet on 24.21.0 it still failed until we added the flag. Jest loaded NestJS 11 on all three versions. If your NestJS project uses Jest, plan this part of the migration separately.
Node 26 doesn't ship Corepack
Not a NestJS issue, but it surfaced here. Node 26.11.1 has no bundled corepack, so pnpm was simply command not found. Our VPS deploy runs corepack enable, which would fail on a plain Node 26. Install corepack from npm first (npm install -g corepack).
Part 4: Benchmarks, and the false alarm
How we measured
All runs used one machine, an Apple M3 Pro with 12 cores and 18 GiB RAM. Every configuration got one warm-up run, which we discarded, and five measured runs. The runs were interleaved, so slow drift hits every variant equally, and we report the median, plus the min-max range where it matters.
Load came from autocannon 8.0.0 with 20 connections, against the built API running with NODE_ENV=production. Every number in this article comes from a raw JSON result file or run log, and a verification script traced each one back to its source before publishing.
The first result said NestJS 12 halved throughput
To keep the rate limiter (10 requests per minute per IP) from turning the load test into a stream of 429s, we raised its limit to one billion. Same one client, same route:
| Node 22.23.2, limit raised to 1e9 | GET /health | POST /waitlist |
|---|---|---|
| NestJS 11 | 15,002 req/s | 12,860 req/s |
| NestJS 12 | 7,659 req/s | 5,298 req/s |
The headers, keep-alive behaviour and Express version were identical, so a CPU profile was next. In NestJS 12, @nestjs/throttler took 21.7% of CPU time, against 3.2% in NestJS 11. Most of it was in one function, pruneExpiredHits (measured).
The cause: a rate-limiter rewrite, measured wrong
Throttler 6.7.1 changed its in-memory storage:
- Before (6.5.0): a counter, plus one timer per request.
- After (6.7.1): a list of timestamps per client, filtered on every request.
The release notes describe this as a fix: it "stops the in-memory storage from retaining request contexts" and removes the per-hit timers.
Our benchmark had one client and a limit of a billion, so that list grew to tens of thousands of entries, and every request scanned it. To confirm, we pinned throttler back to 6.5.0 on NestJS 12:
| Node 22.23.2, limit 1e9 | GET /health | POST /waitlist |
|---|---|---|
| NestJS 12 + throttler 6.7.1 | 7,722 req/s | 5,335 req/s |
| NestJS 12 + throttler 6.5.0 | 15,076 req/s | 12,768 req/s |
NestJS 12 itself was never slower. Then we tested the throttler the way production uses it: the real limit of 10 requests per minute per IP, with between 4,993 and 6,156 simulated clients per test rotating through X-Forwarded-For, each staying within its limit.
| Node 22.23.2, real limit | GET /health | POST /waitlist | Memory after load |
|---|---|---|---|
| NestJS 11 + throttler 6.5.0 | 15,108 req/s | 12,774 req/s | 340 MiB |
| NestJS 12 + throttler 6.7.1 | 15,270 req/s | 12,786 req/s | 260.4 MiB |
| NestJS 12 + throttler 6.5.0 | 15,040 req/s | 12,654 req/s | 365.1 MiB |
Under realistic traffic, the new throttler is just as fast and uses less memory. We had raised the limit only so the load test could run, and that setting ended up being what the test measured.
NestJS 11 vs 12 across Node 22, 24 and 26
Here is the full matrix: both NestJS versions on three Node versions, with the real rate limit and many clients (measured, medians):
| Node 22.23.3 | Node 24.21.0 | Node 26.11.1 | |
|---|---|---|---|
| NestJS 11 - GET /health | 14,972 req/s | 33,210 req/s | 35,638 req/s |
| NestJS 12 - GET /health | 14,624 req/s | 34,970 req/s | 35,304 req/s |
| NestJS 11 - POST /waitlist | 12,326 req/s | 24,932 req/s | 26,424 req/s |
| NestJS 12 - POST /waitlist | 12,490 req/s | 25,668 req/s | 25,508 req/s |
On Node 22 and 26, NestJS 12's medians fall inside NestJS 11's run-to-run range. On Node 24, NestJS 12 came out ahead on both endpoints. p99 latency stayed at 1-3 ms in every case.
What did cost something
| (medians) | Node 22 | Node 24 | Node 26 |
|---|---|---|---|
| Startup - NestJS 11 | 347 ms | 341.4 ms | 313.2 ms |
| Startup - NestJS 12 | 446.5 ms | 435 ms | 415.6 ms |
| Memory 5 s after start - NestJS 11 | 98.8 MiB | 104.6 MiB | 110.9 MiB |
| Memory 5 s after start - NestJS 12 | 122 MiB | 120.7 MiB | 128.3 MiB |
Startup grew by about 100 ms and idle memory by 16-23 MiB on every Node version. We didn't trace which module accounts for the memory. For a long-running API under PM2 neither matters much. On a small VPS, the memory is worth knowing about.
Build time (the full monorepo, Next.js included) and API test time stayed within run-to-run noise between NestJS 11 and 12.
Why Node 24 is twice as fast for this API
We profiled the same NestJS 11 build under the same load on both Node versions:
- Node 22.23.3: 15,136.8 req/s, with
reflect-metadatataking 11.8% of CPU time. - Node 24.21.0: 33,113.6 req/s, with
reflect-metadataat 2.4%. Its functions drop out of the profile's top list entirely.
NestJS reads decorator metadata through reflect-metadata on every request, so that library sits on the hot path. We didn't dig into why Node 24 runs it so much cheaper. A newer V8 is the obvious suspect, but we haven't verified it.
One benchmarking lesson worth stealing
A random re-run of one measurement didn't reproduce: API tests took 574 ms instead of the stored 1,434 ms. It wasn't the laptop's battery, and it wasn't the build that ran beforehand. The original benchmark deleted node_modules just before running the tests, which wiped vitest's cache. Re-running in that same order reproduced the stored number within 2.3%.
The stored number was correct. It measured tests with a cold cache, which is what CI sees, while a laptop mostly sees the warm-cache case at about 550 ms. A benchmark result means little without the state it started from.
Part 5: Can you use TypeScript 7 yet?
TypeScript 7.0 is out, and Microsoft's announcement is direct about the catch: "TypeScript 7.0 is here, it does not ship with an API." It recommends running it alongside TypeScript 6 through npm aliases.
Plain TypeScript 7 breaks the Nest CLI
Installing typescript@^7 in the API worked under pnpm, with one peer warning from @nestjs/swagger. nest build then stopped:
1Error The installed TypeScript version (7.0.2) does not expose the programmatic compiler API that the Nest CLI requires. TypeScript 7.0 ships the "tsc" executable only; the compiler API is expected to return in 7.1.
The alias pattern works for the API, not for Next.js
Microsoft's side-by-side setup:
1 2"@typescript/native": "npm:typescript@^7.0.2", "typescript": "npm:@typescript/typescript6@^6.0.2"
With that in place, tsc runs TypeScript 7 and tsc6 runs TypeScript 6. Anything that imports typescript, such as the Nest CLI or typescript-eslint, gets the TypeScript 6 API. pnpm installed it with no peer warnings.
In the API, everything worked. In the Next.js app, next build failed with "It looks like you're trying to use TypeScript but do not have the required package(s) installed." Next.js 16.3.3 looks for typescript/bin/tsc, and the alias package only ships tsc6. Next then tried to install TypeScript itself, and failed again.
The working setup is the alias pattern in apps/api only, with apps/web left on plain TypeScript 6. The full build, lint and tests pass, and the compiled API is byte-identical.
What it buys
| Cold 'tsc --noEmit' (medians) | TypeScript 6.0.3 | TypeScript 7.0.2 |
|---|---|---|
| apps/api | 589 ms | 93 ms |
| apps/web | 1,038 ms | 172 ms |
Both compilers reported zero type errors. TypeScript 7 is several times faster, but on a codebase this size that's half a second per type-check. In this setup TS 7 only replaces the API's typecheck script; builds, lint and Next.js still run TypeScript 6. For a project this size, it's not worth the extra configuration yet. On a large codebase, or once 7.1 ships its API, the answer changes.
Upgrade checklist
The whole sequence for a pnpm monorepo, in the order that worked for us:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20# 0. Node: 22.12+ to run NestJS 12, 22.22.3+/24.15+/26+ for `nest upgrade` node --version # 1. TypeScript 6 (pin the major: typescript@latest is 7) pnpm -r --include-workspace-root up "typescript@^6.0.3" pnpm build # fix TS5107 (moduleResolution) and TS5101 (baseUrl), then rebuild # 2. NestJS 12, in the API workspace pnpm --filter @repo/api add -D @nestjs/cli@latest @nestjs/schematics@latest cd apps/api && pnpm exec nest upgrade --dry-run --no-observe pnpm exec nest upgrade --no-observe --skip-install cd ../.. && pnpm install # 3. Packages nest upgrade skipped pnpm peers check pnpm --filter @repo/api add "@nestjs/throttler@^6.7.1" # if you use it # 4. Verify pnpm build && pnpm lint && pnpm --filter @repo/api test NODE_ENV=production node apps/api/dist/main # then hit a few endpoints
Is the upgrade worth it?
Yes, with eyes open. NestJS 12 keeps CommonJS apps working, matched NestJS 11's throughput on every Node version we tested, and the migration itself was small. The costs are real but modest: about 100 ms of startup, 16-23 MiB of memory, a pnpm workaround for nest upgrade, and a separate project if you rely on Jest.
TypeScript 6 is mandatory groundwork. Its deprecations become TypeScript 7's removals, so fixing them now is cheaper than later.
And if you came here for performance, look at your Node version before your framework version.
FAQ
How do I upgrade an existing project to NestJS 12?
Add @nestjs/cli@latest and @nestjs/schematics@latest as dev dependencies, then run nest upgrade in the API folder. In a pnpm workspace, pass --skip-install and run pnpm install yourself, because the command schedules npm install. Check pnpm peers check afterwards for packages it skipped.
What Node.js version does NestJS 12 need?
According to the NestJS migration guide, running a NestJS 12 app needs Node 20.19+ or 22.12+. The CLI commands, including nest upgrade, need Node 22.22.3+, 24.15+ or 26+. In our benchmarks, Node 24 and 26 also served more than twice the requests per second of Node 22.
Should I upgrade TypeScript before NestJS 12?
We did, and it kept every error attributable to one change. The migration guide says nest upgrade also bumps TypeScript to 6. In our run TypeScript was already 6.0.3, so it left it alone. Either way, expect the TypeScript 6 deprecation errors and fix them in tsconfig.
Does NestJS 12 require converting my app to ESM?
No. NestJS 12's core packages are ESM, but a CommonJS app loads them through Node's require(esm). Our CommonJS API ran unchanged on Node 22.23.2 through 26.11.1. Converting your own code to ESM is optional and is a separate decision.
Why does TypeScript 6 say 'moduleResolution=node10' is deprecated?
TypeScript 6 turns long-standing deprecations into errors, and "moduleResolution": "Node" means the old node10 algorithm. For a CommonJS NestJS API, switch to "Bundler" and keep "module": "CommonJS", a combination TypeScript 6 explicitly allows. The compiled output of our API didn't change.
Is NestJS 12 slower than NestJS 11?
Not in throughput. Under realistic load, NestJS 12 matched NestJS 11 within run-to-run variation on Node 22, 24 and 26. It started about 100 ms slower and used 16-23 MiB more memory at idle. A benchmark that raises the rate limit far beyond production values can wrongly show it twice as slow.
How do I fix "Option 'baseUrl' is deprecated" in TypeScript 6?
Remove baseUrl from tsconfig.json, and make every paths entry start with ./, for example "@/*": ["./src/*"]. Paths then resolve relative to the tsconfig file. Don't use "ignoreDeprecations": "6.0": TypeScript 7 removes baseUrl entirely.
Is Node 24 faster than Node 22 for NestJS?
In our benchmark, yes, by a wide margin. The same NestJS 11 API served 14,972 requests per second on Node 22.23.3 and 33,210 on Node 24.21.0, and NestJS 12 showed the same jump. A CPU profile showed far less time spent in reflect-metadata on Node 24.
Why does my NestJS benchmark get slower after upgrading @nestjs/throttler?
Throttler 6.7.1 stores one timestamp per request for each client and filters that list on every request. A load test from one client with a very high limit grows the list into the tens of thousands, which halved our throughput. At a realistic limit with many clients, it matched throttler 6.5.0.
Can I use Jest with NestJS 12?
Not with Jest's default configuration, in our tests. Jest 30.5.2 failed to load the NestJS 12 packages from a CommonJS test on Node 22, 24 and 26. With --experimental-vm-modules it worked on Node 24 and 26. vitest needed no changes.
Should I use TypeScript 7 with NestJS?
Not for builds yet. TypeScript 7.0 has no programmatic API, so nest build refuses to run with it. Through npm aliases you can use it for tsc --noEmit type-checking alongside TypeScript 6. In a Next.js workspace, next build breaks with that setup.
Closing: Measure the thing you ship
The upgrade itself was the easy part: six manual fixes, no application code. What took the time was the benchmark, and specifically its first conclusion, which was wrong.
A rate limiter configured for a load test measured itself, a deleted cache made the tests look more than twice as slow, and a Node upgrade did more for throughput than the framework upgrade we were testing. None of that shows up unless you keep the raw numbers and re-run them.
The upgrade steps above are the ones we're applying to the public starter.
Series: [1] Monorepo setup · [2] Landing page · [3] Contentful · [4] Cal.diy embed · [5] Vercel + Railway · [6] Oracle Cloud VPS · [7] NestJS 12 + TypeScript 6
Piotr Żarów
CEO at Dev and Deliver
Share this post
Related posts
Want to light up your ideas with us?
Kickstart your new project with us in just 1 step!
Prefer to call or write a traditional e-mail?









