Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

NestJS  Module 1 – Getting started with NestJS 12

Your first NestJS 12 project with the CLI

Create a NestJS 12 project with nest new, choose the ESM or CommonJS layout, read every generated file, then build, test, lint and run the starter.

  • Beginner
  • 20 minutes
  • Examples run with Node.js 24.21.0, NestJS 12.1.2 and TypeScript 6.0.3
  • By MySmartCoPilot
Free lesson, Pro answers: 20 minutes
Free: the whole lesson. With a pass: the model answers to its 2 interview questions.

What you will learn

  • Check that a machine's Node.js can run a Nest 12 app and the CLI's generators
  • Create a project with nest new and choose between the ESM (Vitest) and CommonJS (Jest) layouts
  • Explain what main.ts, the root module, the controller, the service and the tests each do
  • Build, test, lint and run the starter, and preview new code with nest generate --dry-run

Before you start

What you’ll learn · Before you start

What you will learn

  • Check that a machine's Node.js can run a Nest 12 app and the CLI's generators
  • Create a project with nest new and choose between the ESM (Vitest) and CommonJS (Jest) layouts
  • Explain what main.ts, the root module, the controller, the service and the tests each do
  • Build, test, lint and run the starter, and preview new code with nest generate --dry-run

Before you start

On this page

nest new tiffintrail writes 15 files in a few seconds, and every one of them has a job: one starts the server, one wires the classes together, two answer the first request, two test it, and the other nine are the README, package.json and the settings of the CLI, TypeScript, the test runner, the linter and the formatter. Knowing which file does what is the difference between changing the starter with confidence and guessing.

This lesson creates the starter of TiffinTrail, the tiffin service this track builds, with the Nest CLI 12.0.8. You will check the two Node.js versions Nest 12 asks for, choose between the ESM and CommonJS layouts, read every generated file, build, test, lint and run the result, and preview new code with nest generate.

Check Node.js twice: once to run, once to generate

Nest 12 asks for two different minimum versions of Node.js. Running an app needs one; the CLI’s code generators need a newer one. Each package states its own floor in the engines field of its package.json, so the first command of the session below prints what this machine runs and what three installed packages ask for:

new-project/engines.mjs JavaScript · new-project/engines.mjs
// Prints the Node.js that runs this program and the Node.js versions three installed Nest packages ask for.
import { readFileSync } from 'node:fs';

console.log(`This Node.js: ${process.version}`);
for (const name of ['@nestjs/core', '@nestjs/cli', '@nestjs/schematics']) {
  const pkg = JSON.parse(readFileSync(`../starter/node_modules/${name}/package.json`, 'utf8'));
  console.log(`${`${name} ${pkg.version}`.padEnd(28)} engines.node: ${pkg.engines?.node ?? '(none)'}`);
}

Its output is the first part of the next session, under node engines.mjs. @nestjs/core says only >= 20, but the migration guide is stricter: Nest 12’s packages are ES modules, and a CommonJS app can load them only through require(esm), which Node.js has unflagged since 20.19 and 22.12. The generators in @nestjs/schematics 12.0.6 ask for ^22.22.3 || ^24.15.0 || >=26.0.0, a floor they inherit from the Angular devkit they are built on.

Node.js Runs a Nest 12 app Runs nest new and nest generate
20.19 yes no
22.12 yes no
22.22.3 yes yes
24.15 yes yes
24.21.0, this track yes yes
26 yes yes

Pin Node.js for the whole team

Put one version in the project, where every tool reads it: a .node-version, .nvmrc or mise.toml file for the version managers, and engine-strict=true in the project’s .npmrc, which makes npm refuse any package whose engines field rules out the running Node.js. Then a CI image on 22.12 fails at its first npm ci, because the generators’ packages rule it out, instead of in the middle of nest generate.

Checkpoint

Check: The two Node.js floors of Nest 12

  1. Question 1 of 2 A CI image runs Node.js 22.12.0. The app's tests pass there, but a job that runs nest generate fails. Why?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The generators need 22.22.3 or later on the 22 line; 22.12 is enough only to run the app

    The two floors differ. Running needs require(esm), unflagged since 20.19 and 22.12. The generators inherit a stricter floor from the Angular devkit: 22.22.3, 24.15 or 26.

  2. Question 2 of 2 What does @nestjs/core 12.1.2 declare in engines.node, as the session printed it?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: >= 20

    @nestjs/core says >= 20, @nestjs/cli >= 20.11 and @nestjs/schematics ^22.22.3 || ^24.15.0 || >=26.0.0. The migration guide narrows the first to 20.19 or 22.12 and later, for require(esm).

nest new writes fifteen files

This session prints the versions, then runs the CLI’s new command the way you would, with four flags explained below, and finally compares the new project with the starter folder of this lesson:

nest new, then a comparison with the starter JavaScript · new-project/new.mjs
import { session } from '../session.mjs';

// The CLI of the starter's own dependencies creates a new project here, without installing packages (the session
// has no network), and diff compares it with the starter folder of this lesson: no output means the same files.
session(
  `
node engines.mjs
nest --version
nest new tiffintrail --skip-install --skip-git --package-manager npm --no-observe
diff -r -x README.md -x package-lock.json -x node_modules -x first-request.mjs tiffintrail ../starter
`,
  { packages: '../starter' },
);

Output

$ node engines.mjs
This Node.js: v24.21.0
@nestjs/core 12.1.2          engines.node: >= 20
@nestjs/cli 12.0.8           engines.node: >= 20.11
@nestjs/schematics 12.0.6    engines.node: ^22.22.3 || ^24.15.0 || >=26.0.0
$ nest --version
12.0.8
$ nest new tiffintrail --skip-install --skip-git --package-manager npm --no-observe
✨  We will scaffold your app in a few seconds..

CREATE tiffintrail/.oxlintrc.json (206 bytes)
CREATE tiffintrail/.prettierrc (52 bytes)
CREATE tiffintrail/README.md (6981 bytes)
CREATE tiffintrail/nest-cli.json (171 bytes)
CREATE tiffintrail/package.json (1430 bytes)
CREATE tiffintrail/tsconfig.build.json (170 bytes)
CREATE tiffintrail/tsconfig.json (601 bytes)
CREATE tiffintrail/vitest.config.e2e.ts (245 bytes)
CREATE tiffintrail/vitest.config.ts (350 bytes)
CREATE tiffintrail/src/app.controller.ts (277 bytes)
CREATE tiffintrail/src/app.module.ts (255 bytes)
CREATE tiffintrail/src/app.service.ts (142 bytes)
CREATE tiffintrail/src/main.ts (237 bytes)
CREATE tiffintrail/src/app.controller.spec.ts (623 bytes)
CREATE tiffintrail/test/app.e2e-spec.ts (731 bytes)


                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.


                   🍷  Donate: https://opencollective.com/nest
$ diff -r -x README.md -x package-lock.json -x node_modules -x first-request.mjs tiffintrail ../starter

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node new.mjs

The flags: --skip-install because this recorded session has no network (on your machine, leave it out and let the CLI install the packages), --skip-git to skip git init, --package-manager npm to skip the question about npm, Yarn, pnpm or Bun, and --no-observe to skip the question about the Nest team’s hosted monitoring SDK, which this track does not use. In a terminal, nest new also asks which module system to use; without a terminal it takes the default, ESM.

diff printed nothing, so the starter folder below is exactly what the CLI wrote, apart from the README, the lock file this lesson adds to pin every package version, and first-request.mjs, the one file of the lesson. Here is what each generated file is for:

File Its job
src/main.ts Creates the app from AppModule and starts listening
src/app.module.ts The root module: The module passed to NestFactory.create() in main.ts, AppModule in a new project. Nest builds the app by reading it and every module it imports.: lists the controller and the provider
src/app.controller.ts Answers GET /
src/app.service.ts Holds the logic the controller calls
src/app.controller.spec.ts A unit test of the controller
test/app.e2e-spec.ts An end-to-end test: A test that starts the whole app and sends it real HTTP requests, as test/app.e2e-spec.ts does with supertest, rather than calling one class directly. over HTTP
package.json Dependencies and the npm scripts
tsconfig.json, tsconfig.build.json Compiler settings, and which files a build includes
nest-cli.json Settings of the Nest CLI: The nest command of @nestjs/cli: it creates projects (nest new), generates files (nest generate), builds them (nest build) and starts them (nest start).
vitest.config.ts, vitest.config.e2e.ts The test runner for unit and end-to-end tests
.oxlintrc.json, .prettierrc The linter’s rules and the formatter’s style
README.md Instructions for the project

Checkpoint

Check: What nest new generates

  1. Question 1 of 2 Why did the recorded session run nest new with --skip-install?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: The recorded session has no network, so it could not download packages

    The session runs without network access beyond the loopback address, so it skips the install; the lesson's starter folder comes with a lock file instead. Without the flag, nest new installs the packages itself.

  2. Question 2 of 2 Which generated file starts the HTTP server?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: src/main.ts

    main.ts is the entry file: NestFactory.create(AppModule) builds the app and app.listen(process.env.PORT ?? 3000) starts the server.

main.ts starts one module, and the module wires the rest

Four files make the app. Read them in the order Nest uses them:

The four files of the app

TypeScript (main.ts) · starter/src/main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
await bootstrap();

TypeScript (app.module.ts) · starter/src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

TypeScript (app.controller.ts) · starter/src/app.controller.ts

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service.js';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

TypeScript (app.service.ts) · starter/src/app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

main.ts hands AppModule to NestFactory.create(). The module’s @Module() decorator lists one controller and one provider. The controller’s constructor asks for an AppService, and the service is marked @Injectable(), so Nest can create it and pass it in. Step through what happens when main.ts runs, and the first request after it:

Interactive · stage by stage How the starter boots, and its first request Step through main.ts. The steps name the lines the starter prints on the way; the last two steps follow GET / to the answer.
How the starter boots, and its first request, step 7 of 7. Showing NestFactory.create, Build the providers, Build the controllers, Map the routes, app.listen(), GET / arrives, The answer. In focus: The answer.NestFactory.createreads AppModuleBuild the providersnew AppService()Build the controllersnew AppController(service)Map the routesGET / → getHello()app.listen()port 3000, or PORTGET / arrivesExpress hands it to NestThe answer200 Hello World!

The drawing shows the whole scene; its 7 steps are listed below.

Every step as text
  1. Start. main.ts calls NestFactory.create(AppModule). Nest logs "Starting Nest application..." and reads the @Module() decorator of AppModule: one controller, one provider, no imports.
  2. Providers. AppService has no constructor parameters, so Nest creates it first and keeps that one instance for every class that asks for it.
  3. Controllers. AppController's constructor asks for an AppService. Nest finds that type in the recorded metadata of the constructor and passes in the instance it already built; the controller never calls new itself. With the providers and the controller built, Nest logs "AppModule dependencies initialized".
  4. Routes. Nest reads @Controller() and @Get() and registers one Express route. The log shows it as "AppController {/}:" and then "Mapped {/, GET} route".
  5. Listen. app.listen(process.env.PORT ?? 3000) opens the port, and Nest logs "Nest application successfully started". Everything so far happened once; from now on Nest only answers requests.
  6. A request. A browser asks for GET /. Express receives it and calls the route Nest registered, which calls getHello() on the one AppController instance.
  7. The answer. getHello() returns the string Hello World! from AppService. Nest sends it with status 200, and Express marks it text/html with X-Powered-By: Express and an ETag.

Two details of the ESM layout show in these files. Relative imports end in .js, as in './app.module.js', even though the source file is app.module.ts, because the import names the file the compiler will write. And main.ts ends with await bootstrap(); at the top level of the file, which only an ES module: A JavaScript file that uses import and export, the module format of the language standard. In Node.js a .js file is one when the nearest package.json says "type": "module". may do.

Key idea

Nest builds the whole object graph once, at start-up: providers first, then the controllers that ask for them, then one route per handler. A request only runs methods on objects that already exist.

Checkpoint

Check: How the starter boots

  1. Question 1 of 2 How does AppController get the AppService it uses?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nest builds one AppService and passes it to AppController's constructor

    At start-up Nest creates the providers a module lists and then the controllers, passing each constructor the providers its parameters name. That is dependency injection.

  2. Question 2 of 2 The source file is app.module.ts, yet main.ts imports './app.module.js'. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: The import names the file the compiler will write, which ESM imports must spell out in full

    With "type": "module" and moduleResolution nodenext, relative imports must name the emitted file, extension included. The CommonJS template leaves the extension out.

Build, test, lint and run the starter

The starter’s package.json defines the scripts you will use every day. This session runs five of them in the starter folder, then starts the built app once with first-request.mjs and sends it one request:

The starter's scripts, then its first request JavaScript · scripts/scripts.mjs
import { session } from '../session.mjs';

// The starter's own scripts, then one request to the built app. --reporter=verbose makes Vitest list every test.
session(
  `
cd ../starter
npm run build
ls dist
npm test -- --reporter=verbose
npm run test:e2e -- --reporter=verbose
npm run lint
node first-request.mjs
`,
  { packages: '../starter' },
);

Output

$ cd ../starter
$ npm run build

> tiffintrail@0.0.1 build
> nest build
$ ls dist
app.controller.d.ts
app.controller.js
app.controller.js.map
app.module.d.ts
app.module.js
app.module.js.map
app.service.d.ts
app.service.js
app.service.js.map
main.d.ts
main.js
main.js.map
$ npm test -- --reporter=verbose

> tiffintrail@0.0.1 test
> vitest run --reporter=verbose

The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead.

 RUN  v4.1.11 .

 ✓ src/app.controller.spec.ts > AppController > root > should return "Hello World!"

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  <time>
   Duration  <time>
$ npm run test:e2e -- --reporter=verbose

> tiffintrail@0.0.1 test:e2e
> vitest run --config ./vitest.config.e2e.ts --reporter=verbose

The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead.

 RUN  v4.1.11 .

 ✓ test/app.e2e-spec.ts > AppController (e2e) > / (GET)

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  <time>
   Duration  <time>
$ npm run lint

> tiffintrail@0.0.1 lint
> oxlint --type-aware src/ test/

Found 0 warnings and 0 errors.
Finished in <time> on 6 files with 111 rules using <n> threads.
$ node first-request.mjs
[Nest] <pid>  - <time>     LOG [NestFactory] Starting Nest application...
[Nest] <pid>  - <time>     LOG [InstanceLoader] AppModule dependencies initialized +<n>ms
[Nest] <pid>  - <time>     LOG [RoutesResolver] AppController {/}: +<n>ms
[Nest] <pid>  - <time>     LOG [RouterExplorer] Mapped {/, GET} route +<n>ms
[Nest] <pid>  - <time>     LOG [NestApplication] Nest application successfully started +<n>ms
GET / -> 200 Hello World!
  content-type: text/html; charset=utf-8
  x-powered-by: Express
  etag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node scripts.mjs

starter/first-request.mjs JavaScript · starter/first-request.mjs
// Starts the built starter on a port the system picks, sends GET / as a browser would, prints the answer and stops.
// Run it after npm run build: it imports the compiled module from dist/.
import { NestFactory } from '@nestjs/core';
import { AppModule } from './dist/app.module.js';

const app = await NestFactory.create(AppModule);
await app.listen(0, '127.0.0.1');

const res = await fetch(`${await app.getUrl()}/`);
console.log(`GET / -> ${res.status} ${await res.text()}`);
for (const name of ['content-type', 'x-powered-by', 'etag']) console.log(`  ${name}: ${res.headers.get(name)}`);

await app.close();

What each command did:

Command What it runs What it showed
npm run build nest build, which compiles src/ with TypeScript dist/ with .js, .d.ts and .js.map files
npm test Vitest on the *.spec.ts files 1 test passed: getHello() returns Hello World!
npm run test:e2e Vitest on *.e2e-spec.ts, with supertest 1 test passed: GET / answers 200
npm run lint oxlint with type-aware rules 0 warnings and 0 errors in 6 files
node first-request.mjs the built app, one request the boot log, then 200 Hello World!

A few lines of that output are worth reading closely. The boot log is the figure above in words: AppModule dependencies initialized, then Mapped {/, GET} route. The answer arrived as text/html, because Express sends a string as HTML, with X-Powered-By: Express and an ETag. And Vite, which Vitest runs on, notes that the vite-tsconfig-paths plugin of the generated config is no longer needed; it is a notice, and the tests still pass.

The two scripts that keep running are not in the session: npm run start builds and starts the app on port 3000, and npm run start:dev also watches src/ and restarts on every change.

Checkpoint

Check: Build, test, lint and run

  1. Question 1 of 2 Which files did npm run build write into dist/ for app.module.ts?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: app.module.js, app.module.d.ts and app.module.js.map

    ls dist listed a .js, a .d.ts and a .js.map file for each of the four source files: 12 files.

  2. Question 2 of 2 GET / answered Hello World!. Which Content-Type did the starter send?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: text/html; charset=utf-8

    first-request.mjs printed content-type: text/html; charset=utf-8, x-powered-by: Express and an ETag. Return an object when the client expects JSON.

ESM or CommonJS: what the question changes

When nest new asks for a module system, it chooses between two templates that @nestjs/schematics ships: one for ES modules with Vitest, which is the default, and one for CommonJS: Node.js's older module format, with require() and module.exports. A .js file is CommonJS unless the nearest package.json says "type": "module". with Jest. This program reads both templates from the installed package and prints how they differ:

layouts/templates.mjs JavaScript · layouts/templates.mjs
// Compares the two project templates that nest new chooses between when it asks for a module system:
// ts-esm (ES modules, Vitest) and ts (CommonJS, Jest), as @nestjs/schematics ships them.
import { readFileSync } from 'node:fs';

const root = '../starter/node_modules/@nestjs/schematics/dist/lib/application/files';
const read = (template, file) => readFileSync(`${root}/${template}/${file}`, 'utf8');
// The templates hold EJS tags; fill them in as nest new does for a TypeScript project without the monitoring SDK.
const render = (text) => text.replace(/<% if \(observe\) \{ %>[\s\S]*?<% \} %>/g, '').replace(/<%= strict %>/g, 'true').replace(/<%=[^%]*%>/g, '');
const json = (template, file) => JSON.parse(render(read(template, file)));

const pkg = { esm: json('ts-esm', 'package.json'), cjs: json('ts', 'package.json') };
const tsconfig = { esm: json('ts-esm', 'tsconfig.json'), cjs: json('ts', 'tsconfig.json') };
const main = { esm: render(read('ts-esm', 'src/main.ts')), cjs: render(read('ts', 'src/main.ts')) };

console.log(`package.json "type":  ESM ${pkg.esm.type ?? '(not set)'}  |  CommonJS ${pkg.cjs.type ?? '(not set)'}`);
console.log('test script:');
console.log(`  ESM       ${pkg.esm.scripts.test}`);
console.log(`  CommonJS  ${pkg.cjs.scripts.test}`);
const only = (a, b) => Object.keys(pkg[a].devDependencies).filter((d) => !(d in pkg[b].devDependencies)).sort().join(', ');
console.log(`dev dependencies only in ESM:       ${only('esm', 'cjs')}`);
console.log(`dev dependencies only in CommonJS:  ${only('cjs', 'esm')}`);
console.log(`tsconfig types:  ESM ${tsconfig.esm.compilerOptions.types.join(', ')}  |  CommonJS ${tsconfig.cjs.compilerOptions.types.join(', ')}`);
const importOf = (text) => /from '(\.\/app\.module[^']*)'/.exec(text)[1];
const lastLine = (text) => text.trim().split('\n').at(-1);
console.log(`main.ts imports the module from:  ESM '${importOf(main.esm)}'  |  CommonJS '${importOf(main.cjs)}'`);
console.log(`main.ts ends with:  ESM ${lastLine(main.esm)}  |  CommonJS ${lastLine(main.cjs)}`);
The two templates, compared JavaScript · layouts/run.mjs
import { session } from '../session.mjs';

session(
  `
node templates.mjs
`,
  { packages: '../starter' },
);

Output

$ node templates.mjs
package.json "type":  ESM module  |  CommonJS (not set)
test script:
  ESM       vitest run
  CommonJS  node --experimental-vm-modules ./node_modules/jest/bin/jest.js
dev dependencies only in ESM:       @vitest/coverage-v8, vite-tsconfig-paths, vitest
dev dependencies only in CommonJS:  @types/jest, jest, ts-jest, ts-loader, ts-node, tsconfig-paths
tsconfig types:  ESM vitest/globals, node  |  CommonJS node, jest
main.ts imports the module from:  ESM './app.module.js'  |  CommonJS './app.module'
main.ts ends with:  ESM await bootstrap();  |  CommonJS void bootstrap();

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node run.mjs

The differences follow from one setting. "type": "module" in package.json makes Node.js treat every .js file of the project as an ES module, so imports need their .js extension and main.ts may await at the top level. Without it, the project is CommonJS, imports stay extension-less and main.ts ends with void bootstrap();. The test runner follows: Vitest loads ES modules natively, while Jest runs through node --experimental-vm-modules so that it can load Nest 12’s packages, which are ES modules even in a CommonJS project. The migration guide adds that Jest can load them only on Node.js 24.9 or later.

For a new service, take the default, ESM with Vitest, unless your team’s tooling needs Jest. An existing CommonJS app does not have to change: Nest 12 runs it as it is.

Checkpoint

Check: ESM or CommonJS layout

  1. Question 1 of 2 Why does the CommonJS template run Jest through node --experimental-vm-modules?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: So that Jest can load Nest 12's packages, which are ES modules even in a CommonJS project

    The template's test script is node --experimental-vm-modules ./node_modules/jest/bin/jest.js. The migration guide adds that Jest loads the ESM-only packages only on Node.js 24.9 or later.

  2. Question 2 of 2 In the CommonJS template, how does main.ts import AppModule, and how does it end?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: From './app.module', and it ends with void bootstrap();

    templates.mjs printed './app.module' and void bootstrap(); for CommonJS, and './app.module.js' and await bootstrap(); for ESM.

nest generate adds a feature, and a dry run shows it first

nest generate, or nest g, runs a schematic: A code generator that the Nest CLI runs to create or change files, such as the application, controller and resource schematics of @nestjs/schematics. that writes the files of a new piece and registers it in a module. --dry-run prints what it would do and writes nothing, which is the safe way to learn what each schematic produces:

Two generators, as dry runs JavaScript · scripts/generate.mjs
import { session } from '../session.mjs';

// What nest generate would write for a new "orders" feature, without writing it (--dry-run).
session(
  `
cd ../starter
nest generate resource orders --dry-run
nest generate controller menus --dry-run
`,
  { packages: '../starter' },
);

Output

$ cd ../starter
$ nest generate resource orders --dry-run
CREATE src/orders/orders.controller.spec.ts (582 bytes)
CREATE src/orders/orders.controller.ts (924 bytes)
CREATE src/orders/orders.module.ts (261 bytes)
CREATE src/orders/orders.service.spec.ts (463 bytes)
CREATE src/orders/orders.service.ts (629 bytes)
CREATE src/orders/dto/create-order.dto.ts (31 bytes)
CREATE src/orders/dto/update-order.dto.ts (176 bytes)
CREATE src/orders/entities/order.entity.ts (22 bytes)
UPDATE package.json (1463 bytes)
UPDATE src/app.module.ts (325 bytes)
Dry run enabled. No files written to disk.
$ nest generate controller menus --dry-run
CREATE src/menus/menus.controller.spec.ts (488 bytes)
CREATE src/menus/menus.controller.ts (99 bytes)
UPDATE src/app.module.ts (335 bytes)
Dry run enabled. No files written to disk.

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node generate.mjs

resource orders would create 8 files: a module, a controller and a service with their tests, two DTO classes and an entity class, all under src/orders/. It would also update src/app.module.ts, to import the new OrdersModule, and package.json. Run without --dry-run, it adds @nestjs/mapped-types to the dependencies, because the generated UpdateOrderDto extends PartialType() from it, and installs it. controller menus writes less: the controller and its test, and one update to AppModule. In a terminal, nest g resource also asks which transport to use and whether to generate the CRUD methods; this recording took the defaults, a REST API with CRUD methods.

Checkpoint

Check: nest generate and dry runs

  1. Question 1 of 2 What does nest generate write to disk with --dry-run?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Nothing; it only prints what it would create and update

    --dry-run lists every CREATE and UPDATE the schematic would make and writes nothing, so you can read the plan before you run it for real.

  2. Question 2 of 2 Which existing files would nest generate resource orders change? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 2 of 2

    Answer:

    • package.json
    • src/app.module.ts

    The dry run listed 8 files to create and 2 to update: UPDATE package.json and UPDATE src/app.module.ts.

Practise: build the starter yourself

Do this on your own computer with Node.js 24.15 or later:

Create, run and change the starter

Run npx @nestjs/cli@12.0.8 new tiffintrail and choose npm and ESM. In the new folder run npm run start:dev and open http://localhost:3000/. Change the string in app.service.ts and watch the server restart. Then run npx nest g resource menus --dry-run, read the plan, and run it again without --dry-run.

The questions mix the starter’s files, its scripts and the CLI; a wrong answer brings back a question on the same idea later in the session:

Practice that adapts to you

Practise this lesson

7 questions about 6 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 7 Developers use Node.js 26 on their laptops; production runs Node.js 20.19. Which is true for Nest 12?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: Both run the app; only the laptops can run nest new and nest generate

    Running needs 20.19+ (22.12+ on the 22 line); the generators need 22.22.3+, 24.15+ or 26+. Different machines can be fine for different jobs, but pinning one version for the team avoids surprises.

  2. Question 2 of 7 Someone deletes vitest.config.e2e.ts. Which script stops working?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: npm run test:e2e

    Each script names its own configuration: test:e2e passes --config ./vitest.config.e2e.ts, which picks the *.e2e-spec.ts files.

  3. Question 3 of 7 In which order does Nest set up the starter when main.ts runs?

    Choose one answer.

    Show the answer: Question 3 of 7

    Answer: AppService, then AppController with the service passed in, then the GET / route, then listen

    The boot log reads in this order: AppModule dependencies initialized, AppController {/}:, Mapped {/, GET} route, Nest application successfully started.

  4. Question 4 of 7 You add a MenuService parameter to AppController's constructor but forget to list MenuService in AppModule's providers. When do you find out?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: When the app starts, because Nest cannot build AppController without it

    Because Nest builds the whole object graph at start-up, a missing provider stops the app before it listens, with "Nest can't resolve dependencies of the AppController". The providers lesson of this track reads that message in full.

  5. Question 5 of 7 Run in a session like this lesson's, plain npm test prints the totals but no test names. How do you make it list every test?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: npm test -- --reporter=verbose

    Vitest's default reporter sums up passing files rather than listing their tests, so outside a terminal the session would show only the totals. --reporter=verbose lists every test, and the -- passes the option through npm to Vitest.

  6. Question 6 of 7 An existing Nest 11 app is CommonJS with Jest. After upgrading to Nest 12, does it have to switch to ESM?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: No; it can stay CommonJS, on Node.js 20.19+ or 22.12+, with Jest on Node.js 24.9+

    Nest 12's packages are ES modules, but CommonJS apps load them through require(esm). The upgrade keeps your module format; switching is a separate, optional step.

  7. Question 7 of 7 After nest generate resource orders runs for real, which new dependency appears in package.json?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: @nestjs/mapped-types

    The resource generator updates package.json with @nestjs/mapped-types and installs it, because UpdateOrderDto is PartialType(CreateOrderDto).

Interview questions

Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.

  1. Fresher to mid · Warm-up

    What does nest new generate, and which file starts the application?

    It writes a ready-to-run project of 15 files with Nest CLI 12.0.8. src/main.ts starts the app: it passes AppModule to NestFactory.create() and calls app.listen(process.env.PORT ?? 3000). src/app.module.ts is the root module, which lists AppController and AppService; the controller answers GET / with the string the service returns. Two tests come with it, a unit test of the controller and an end-to-end test over HTTP with supertest. The other files configure the project: package.json with its scripts, tsconfig.json and tsconfig.build.json for the compiler, nest-cli.json for the CLI, two Vitest configs, and the oxlint and Prettier settings. In a terminal it first asks for a package manager, a module system (ESM with Vitest, the default, or CommonJS with Jest) and whether to set up the Nest team's observability package.

  2. Mid to senior

    A CI job that runs nest generate fails on an image with Node.js 22.12, although the app's own tests pass on the same image. Explain the two Node.js requirements of Nest 12, and how you would pin Node.js for the team and its CI.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Mid to senior

    Your team is starting a Nest 12 service and nest new asks for a module system. Which do you choose, ESM or CommonJS, and what changes in imports, tests and tooling with each?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Running a Nest 12 app needs Node.js 20.19+ (22.12+ on the 22 line); the CLI’s generators need 22.22.3+, 24.15+ or 26+, so pin a version that meets both for the whole team.
  • nest new writes 15 files: main.ts starts the app, AppModule lists the controller and the provider, and the rest configure TypeScript, Vitest, oxlint and Prettier.
  • At start-up Nest creates AppService, passes it to AppController’s constructor and maps GET /; a request only calls methods on objects that already exist.
  • npm run build compiles to dist/, npm test and npm run test:e2e run Vitest, npm run lint runs oxlint, and a string answer goes out as text/html with X-Powered-By: Express.
  • ESM projects use .js in imports, top-level await and Vitest; CommonJS projects use extension-less imports and Jest through --experimental-vm-modules.
  • nest generate writes a feature and registers it in a module; --dry-run shows the plan first.

Terms in this lesson

CommonJS
Node.js's older module format, with require() and module.exports. A .js file is CommonJS unless the nearest package.json says "type": "module".
end-to-end test
A test that starts the whole app and sends it real HTTP requests, as test/app.e2e-spec.ts does with supertest, rather than calling one class directly.
ES module
A JavaScript file that uses import and export, the module format of the language standard. In Node.js a .js file is one when the nearest package.json says "type": "module".
Nest CLI
The nest command of @nestjs/cli: it creates projects (nest new), generates files (nest generate), builds them (nest build) and starts them (nest start).
root module
The module passed to NestFactory.create() in main.ts, AppModule in a new project. Nest builds the app by reading it and every module it imports.
schematic
A code generator that the Nest CLI runs to create or change files, such as the application, controller and resource schematics of @nestjs/schematics.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.