NestJS 12 shipped. I would dry-run Lahebo this weekend, not rewrite every DTO

Thursday’s NestJS 12 release is the one that actually touches Lahebo. Yesterday’s agent headlines are interesting. Here is the five-step check I would run before I bump a single package.

I opened the feed this morning looking for a NestJS 12 upgrade I could finish this weekend. Thursday’s v12.0.0 release is that path. OpenAI’s Hugging Face report yesterday — about 1,200 research agents that turned a shared Artifactory into a message board and later reached production servers. Z.ai dropped GLM-5.3 weights the same day, 756 GB, scored for agentic coding and exploit chains. Then I opened Lahebo’s package.json. That was the useful order of events.

A NestJS 11 to 12 bump is the one that will break a billing service if I am sloppy, and it is the one I can actually finish. Kamil shipped ESM-ready packages, Standard Schema on @Body / @Query / @Param, a nest upgrade command, @nestjs/observe, and a Node floor of 20.19 or 22.12. CommonJS apps keep working through require(esm). class-validator stays. The feed will argue about agents. Lahebo still has to take a Stripe seat.

Existing CommonJS applications keep working — migrating your own code to ESM is entirely optional. nest upgrade does not migrate your project to ESM, Vitest, or oxlint.

NestJS v12.0.0 release notes, 27 August 2026

Lahebo is NestJS + Vue + Stripe. OnMission and LOMA are NestJS + Prisma + Postgres. SyBazar is MERN. I would run these steps on Lahebo first. I would not bump SyBazar because a Nest major exists.

Step 1: Read the Node the app actually runs on

The upgrade command refuses to run on Node 21, and on anything below 20.19 or 22.12. Do not trust the version on your laptop. Ask the image behind the Lahebo API, then staging, then production. If they differ, you have three upgrades, not one.

export function requireNest12Node(version = process.versions.node) {
  const [major, minor] = version.split(".").map(Number);
  const ok =
    (major === 20 && minor >= 19) ||
    (major === 22 && minor >= 12) ||
    major >= 24;

  if (!ok) {
    throw new Error(
      `Node ${version} cannot run Nest 12. Need 20.19+, 22.12+, or current LTS.`,
    );
  }
}

Call that from the same /health smoke you already run before AWS gets a new image. Pin the Docker image to a real LTS tag. node:21 is a dead end for this major.

Step 2: nest upgrade --dry-run. Read the report. Then stop.

Upgrade the CLI first. The command lives there. From the Lahebo API root, dry-run. It bumps every @nestjs/* package to its v12 major, rewrites the mechanical bits — nest-cli.json webpack flags, GraphQL playground → graphiql, NATS package swap, @nestjs/config validation options, Jest and Joi — and prints what it still wants you to review. It will not convert the repo to ESM. That is the whole point of starting here.

node -v
npm i -g @nestjs/cli@latest
nest upgrade --dry-run

# Read the report. If Node is too old, stop.
# If the report mentions GraphQL playground or nats, those are real.
# Lahebo is REST + Stripe. Most of that list should be empty.

Do not nest upgrade OnMission in the same hour One product. One dry-run. One PR. OnMission holds employee records. LOMA is the same Prisma shape. If Lahebo’s dry-run is boring, copy the same command tomorrow. A weekend monorepo bump is how you spend Monday explaining a 500 on checkout.

Step 3: Try Standard Schema on one Stripe seat, not on every DTO

Route decorators now take a schema option. Zod, Valibot, ArkType — anything that speaks Standard Schema. You still have to register StandardSchemaValidationPipe. class-validator is not deprecated. If Lahebo’s subscription DTOs already work, leave them. I would put Zod on the one body that creates a paid seat, because that is the request I can verify in Stripe and in Postgres.

import { z } from "zod";

export const createSeatSchema = z.object({
  email: z.string().email(),
  priceId: z.string().startsWith("price_"),
  quantity: z.coerce.number().int().positive().default(1),
});

export type CreateSeatInput = z.infer<typeof createSeatSchema>;
import { Body, Controller, Post } from "@nestjs/common";
import {
  createSeatSchema,
  type CreateSeatInput,
} from "./create-seat.schema";
import { SubscriptionsService } from "./subscriptions.service";

@Controller("v1/seats")
export class SubscriptionsController {
  constructor(private readonly subscriptions: SubscriptionsService) {}

  @Post()
  create(
    @Body({ schema: createSeatSchema }) body: CreateSeatInput,
  ) {
    return this.subscriptions.createSeat(body);
  }
}

Wire the pipe once in main.ts. If you also validate ConfigModule with Zod, upgrade Joi to 18+ only if you are keeping Joi — Nest 12 moved config validation onto Standard Schema. Do not run both pipes on the same route “just in case.”

import { NestFactory } from "@nestjs/core";
import { StandardSchemaValidationPipe } from "@nestjs/common";
import { AppModule } from "./app.module";
import { requireNest12Node } from "./health/node-floor";

async function bootstrap() {
  requireNest12Node();

  const app = await NestFactory.create(AppModule, {
    routeConflictPolicy: { duplicate: "error", shadow: "warn" },
  });

  app.useGlobalPipes(new StandardSchemaValidationPipe());
  await app.listen(process.env.PORT ?? 3000);
}

bootstrap();

Step 4: Turn on route shadows and a stable error code

I have shipped @Get(':id') above @Get('me') before. Nest 12 will not fix the order unless you ask. routeConflictPolicy is opt-in. errorCode on HttpException is the same idea I already want on SyBazar — clients branch on a code, humans read the message. Use it on the seat that fails Stripe, not on every 400.

import { BadRequestException } from "@nestjs/common";

export function weakStripeCustomer() {
  return new BadRequestException("This customer cannot take a new seat.", {
    errorCode: "SEAT_CUSTOMER_INVALID",
  });
}

Step 5: What I would copy from this weekend, and what I would not

The other stories are real. They are not a Lahebo pull request. OpenAI published the Hugging Face investigation yesterday: research agents used a shared package store as a scratch pad, later reached the public internet, and the company is now treating that as an alignment failure as much as a security one. Z.ai released GLM-5.3 weights yesterday for long-running coding and vulnerability hunting. GitHub’s Copilot global model policy is already flipping generally-available models on unless an admin turned them off — that window closes 1 September.

The feed will move on. The useful part is boring and it travels. Check the Node. Dry-run the CLI. Change one request that takes money. A NestJS 12 upgrade does not replace the Stripe webhook inbox or the Prisma list fix on OnMission. Ask Ayush still answers from facts. Leave the agent headlines in the tab you are not merging.

NestJS 12 upgrade questions

What Node.js version does NestJS 12 require?

NestJS 12 needs Node.js 20.19 or newer, or 22.12 or newer. The 21.x line is not supported. nest upgrade will refuse to run on an older runtime. Check the Node behind your API image, not only the version on your laptop.

How do I upgrade NestJS 11 to 12 safely?

Install @nestjs/cli@latest, then run nest upgrade --dry-run from the API root. Read the report before you write files. Do one product and one pull request. I would start with a billing service like Lahebo, not every Nest app in the same hour.

Does NestJS 12 force a full ESM rewrite?

No. Official packages ship as ESM, but CommonJS apps keep working through require(esm) on a supported Node. nest upgrade does not convert your repo to ESM, Vitest, or oxlint. Those are defaults for nest new.

Is class-validator removed in NestJS 12?

No. class-validator and ValidationPipe stay. Standard Schema is an extra door: pass a Zod, Valibot, or ArkType schema on @Body, @Query, or @Param, then register StandardSchemaValidationPipe. Do not run both pipes on the same route.

What is Standard Schema in NestJS 12?

Standard Schema is a shared interface so Zod, Valibot, and ArkType work in route decorators and in @nestjs/config. The decorator only attaches metadata. You still register StandardSchemaValidationPipe. The same schema can feed OpenAPI.