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 4 – Modules, dynamic modules and lifecycle

Standalone apps, CLI commands and the REPL

Run Nest providers without HTTP, in scripts built on createApplicationContext(), in an operations CLI with nest-commander, and in the Nest REPL.

  • Intermediate
  • 20 minutes
  • Examples run with Node.js 24.21.0, NestJS 12.1.2, TypeScript 6.0.3 and quickjs 0.32.0
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: An application context is Nest without the HTTP server · A script, a CLI command or the REPL: three doors to the same providers. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Run Nest providers without an HTTP server with createApplicationContext(), and close the context so that a script ends with the right exit status
  • Choose between a script, a CLI command and the REPL for a job that needs an app's providers
  • Build an operations CLI with nest-commander commands, required options and exit codes that a scheduler can trust
  • Inspect and call providers from the Nest REPL
  • Diagnose a script that exits without a message, and keep its error logger

Before you start

What you’ll learn · Before you start

What you will learn

  • Run Nest providers without an HTTP server with createApplicationContext(), and close the context so that a script ends with the right exit status
  • Choose between a script, a CLI command and the REPL for a job that needs an app's providers
  • Build an operations CLI with nest-commander commands, required options and exit codes that a scheduler can trust
  • Inspect and call providers from the Nest REPL
  • Diagnose a script that exits without a message, and keep its error logger

Before you start

On this page

At 10:30 IST TiffinTrail stops taking lunch orders, and a job must close the day: send each kitchen its orders and move the late ones to tomorrow. That job needs the API’s OrdersService and none of the API’s HTTP server, and it must end with an exit status a scheduler can read.

Nest runs its modules without HTTP in application context: What NestFactory.createApplicationContext() returns: an app's modules and providers, built and with their lifecycle hooks run, but no HTTP server, routes or request pipeline. Scripts, workers and CLIs run on it.: scripts, CLI commands and a REPL: A read-eval-print loop: a prompt that runs each line typed and prints the result. Nest's repl() from @nestjs/core opens one on an app's modules, with helpers such as get() and debug().. This lesson closes the day with a script, then builds an operations CLI with nest-commander, opens the REPL on the same modules, and finds out why one script died without a word.

An application context is Nest without the HTTP server

NestFactory.createApplicationContext(AppModule) reads the same modules as NestFactory.create() and builds the same providers, and it opens no port. The close-day script asks the context for OrdersService, runs closeDay(), prints the summary and closes the context. The time comes from --at, so that a run can be repeated:

ops/src: the close-day script and the orders module

TypeScript (close-day.ts) · ops/src/close-day.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
import { OrdersController, OrdersModule, OrdersService } from './orders.js';

// node dist/close-day.js --at <ISO time>: closes the lunch day of that instant, as a cron job would at 10:30 IST.
const at = new Date(process.argv[process.argv.indexOf('--at') + 1] ?? '');

const app = await NestFactory.createApplicationContext(AppModule, { logger: ['error', 'warn'] });
try {
  for (const line of app.select(OrdersModule).get(OrdersService, { strict: true }).closeDay(at)) console.log(line);
  // A controller is built too, but no route exists, and calling it directly skips the global interceptor.
  console.log(`OrdersController.list() called directly: ${JSON.stringify(app.get(OrdersController).list())}`);
} catch (e) {
  console.error((e as Error).message);
  process.exitCode = 1;
} finally {
  await app.close();
}

TypeScript (orders.ts) · ops/src/orders.ts

import { Controller, Get, Injectable, Module, type OnApplicationShutdown, type OnModuleInit } from '@nestjs/common';

export interface Order {
  id: number;
  kitchenId: string;
  /** When the customer placed it, in UTC. */
  placedAt: string;
  paise: number;
  status: 'placed' | 'sent-to-kitchen' | 'moved-to-tomorrow';
}

/** India Standard Time is UTC+05:30 all year. */
const IST_OFFSET_MS = (5 * 60 + 30) * 60_000;

/** The lunch day an instant belongs to, as YYYY-MM-DD in IST. */
export function istDay(at: Date): string {
  return new Date(at.getTime() + IST_OFFSET_MS).toISOString().slice(0, 10);
}

/** The lunch cut-off of a day: 10:30 IST, which is 05:00 UTC. */
export function cutOffOf(day: string): Date {
  return new Date(Date.parse(`${day}T10:30:00Z`) - IST_OFFSET_MS);
}

/** Stands for the orders table: six orders of one morning, loaded at start-up. */
@Injectable()
export class OrdersRepository implements OnModuleInit, OnApplicationShutdown {
  orders: Order[] = [];

  onModuleInit() {
    this.orders = [
      { id: 301, kitchenId: 'pune-07', placedAt: '2026-09-16T23:40:00Z', paise: 12000, status: 'placed' },
      { id: 302, kitchenId: 'pune-07', placedAt: '2026-09-17T02:15:00Z', paise: 12000, status: 'placed' },
      { id: 303, kitchenId: 'pune-12', placedAt: '2026-09-17T03:05:00Z', paise: 9500, status: 'placed' },
      { id: 304, kitchenId: 'pune-07', placedAt: '2026-09-17T04:20:00Z', paise: 15000, status: 'placed' },
      { id: 305, kitchenId: 'pune-12', placedAt: '2026-09-17T04:59:00Z', paise: 9500, status: 'placed' },
      { id: 306, kitchenId: 'pune-07', placedAt: '2026-09-17T05:07:00Z', paise: 12000, status: 'placed' },
    ];
    console.log(`orders loaded: ${this.orders.length}`);
  }

  onApplicationShutdown() {
    console.log('orders saved, connection closed');
  }
}

@Injectable()
export class OrdersService {
  constructor(private readonly repo: OrdersRepository) {}

  /** Sends the day's orders placed before the cut-off to the kitchens, and moves later ones to the next day. */
  closeDay(now: Date): string[] {
    if (Number.isNaN(now.getTime())) throw new Error('close-day: --at must be an ISO time such as 2026-09-17T05:00:00Z');
    const day = istDay(now);
    const cutOff = cutOffOf(day);
    if (now < cutOff) return [`${day}: still open, ${Math.round((cutOff.getTime() - now.getTime()) / 60_000)} minutes to the 10:30 IST cut-off`];
    const perKitchen = new Map<string, number>();
    const moved: number[] = [];
    for (const order of this.repo.orders) {
      if (order.status !== 'placed' || istDay(new Date(order.placedAt)) !== day) continue;
      if (new Date(order.placedAt) < cutOff) {
        order.status = 'sent-to-kitchen';
        perKitchen.set(order.kitchenId, (perKitchen.get(order.kitchenId) ?? 0) + 1);
      } else {
        order.status = 'moved-to-tomorrow';
        moved.push(order.id);
      }
    }
    return [
      `${day}: closed at the 10:30 IST cut-off (${cutOff.toISOString()})`,
      ...[...perKitchen].map(([kitchen, n]) => `   ${kitchen}: ${n} lunches sent to the kitchen`),
      `   moved to tomorrow: ${moved.length ? moved.join(', ') : 'none'}`,
    ];
  }

  /** The orders of a kitchen placed before the cut-off of a day, as the kitchen's display received them. */
  sentToKitchen(kitchenId: string, day: string): Order[] {
    const cutOff = cutOffOf(day);
    return this.repo.orders.filter(
      (o) => o.kitchenId === kitchenId && istDay(new Date(o.placedAt)) === day && new Date(o.placedAt) < cutOff,
    );
  }

  /** Lunches per kitchen for a day, placed before its cut-off. */
  lunchesPerKitchen(day: string): Map<string, number> {
    const counts = new Map<string, number>();
    for (const kitchen of new Set(this.repo.orders.map((o) => o.kitchenId))) counts.set(kitchen, this.sentToKitchen(kitchen, day).length);
    return counts;
  }
}

@Controller('orders')
export class OrdersController {
  constructor(private readonly repo: OrdersRepository) {}

  @Get()
  list() {
    return this.repo.orders.map((o) => o.id);
  }
}

@Module({ controllers: [OrdersController], providers: [OrdersRepository, OrdersService], exports: [OrdersService] })
export class OrdersModule {}

Step through what the context builds for it, and what it leaves out:

Interactive · step by step What createApplicationContext() builds, and what it leaves out Step through one run of the close-day script, from the call to app.close().
What createApplicationContext() builds, and what it leaves out, step 6 of 6. Showing Application context, HTTP server, AppModule and imports, Routes, Providers, controllers (closeDay() runs here), Guards and interceptors. In focus: Application context.scansbuildsApplication contextcreateApplicationContext()HTTP servernever createdAppModule and importsscanned as by create()Routesnone registeredProviders, controllerscloseDay() runs hereGuards and interceptorsapplied to requests onlyclosed: exit status 0

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

Every step as text
  1. A context, not a server. The script calls NestFactory.createApplicationContext(AppModule). It asks for Nest's container and nothing that listens, so no port is opened.
  2. The modules are read. Nest scans AppModule and every module it imports, exactly as NestFactory.create() would, so the script sees the same providers as the API.
  3. Everything is built. Nest builds every provider and controller and runs onModuleInit() and onApplicationBootstrap(). The run printed orders loaded, 6, from the repository's hook.
  4. Nothing for HTTP. No HTTP server is created and no route is registered. Guards, pipes and interceptors act on requests, so the controller method the script called directly skipped the global interceptor and returned the bare ids.
  5. The script takes a provider. app.select(OrdersModule).get(OrdersService, { strict: true }) returns the provider of that module, and closeDay() runs on the same code the API uses.
  6. app.close() lets it end. await app.close() runs the shutdown hooks, so the run printed orders saved, and leaves nothing open, so the process ends and the cron job sees its exit status.

The close-day script before the cut-off, at it, and with a bad time JavaScript · ops/close-day.mjs
import { session } from '../session.mjs';

// The close-day script, run as a cron job would run it: before the 10:30 IST cut-off, at it, and with a bad time.
session(`
npx tsc -p tsconfig.json
node dist/close-day.js --at 2026-09-17T04:45:00Z
node dist/close-day.js --at 2026-09-17T05:00:00Z
!node dist/close-day.js --at yesterday
`);

Output

$ npx tsc -p tsconfig.json
$ node dist/close-day.js --at 2026-09-17T04:45:00Z
orders loaded: 6
2026-09-17: still open, 15 minutes to the 10:30 IST cut-off
OrdersController.list() called directly: [301,302,303,304,305,306]
orders saved, connection closed
$ node dist/close-day.js --at 2026-09-17T05:00:00Z
orders loaded: 6
2026-09-17: closed at the 10:30 IST cut-off (2026-09-17T05:00:00.000Z)
   pune-07: 3 lunches sent to the kitchen
   pune-12: 2 lunches sent to the kitchen
   moved to tomorrow: 306
OrdersController.list() called directly: [301,302,303,304,305,306]
orders saved, connection closed
$ node dist/close-day.js --at yesterday
orders loaded: 6
close-day: --at must be an ISO time such as 2026-09-17T05:00:00Z
orders saved, connection closed
[exit status 1]

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

At 10:15 IST the day was still open, 15 minutes before the cut-off. At 10:30 the script sent 3 lunches to pune-07 and 2 to pune-12 and moved order 306, placed at 10:37 IST, to tomorrow; order 301 counted for the 17th, because the lunch day is the date in India. orders loaded and orders saved show the context running the module’s lifecycle hooks, at creation and in app.close(). The documentation tells scripts to end with await app.close() so that the process exits; the finally block does it even after the bad --at, which set process.exitCode to 1. Two options of createApplicationContext() matter to every script: logger sets which levels Nest prints, and abortOnError decides whether a start-up error ends the process.

The controller was built too, but nothing in a context handles requests. The documentation says guards, pipes and interceptors are not available there, and calling a controller method through app.get() skips a global interceptor: the run printed the bare ids, without the { data } envelope the API’s interceptor adds.

Key idea

An application context builds every module and provider of an app, runs their lifecycle hooks and opens no port, so a script reuses the API’s services and ends, with its exit status, once it closes the context.

Checkpoint

Check: What an application context builds

  1. Question 1 of 2 What does NestFactory.createApplicationContext(AppModule) do? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • It builds every module, provider and controller of the app
    • It runs the start-up lifecycle hooks

    A context is the app's container without the network part: the same providers and hooks, no listener.

  2. Question 2 of 2 A script built on createApplicationContext() finishes its work, but the process never exits, and the database pool it uses is never closed. What is missing?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: await app.close() at the end, best in a finally block, which runs the shutdown hooks that close the pool

    The pool's connections keep the event loop alive until its shutdown hook closes them, and only app.close() runs that hook in a script.

A script, a CLI command or the REPL: three doors to the same providers

Standalone apps are what the documentation calls standalone applications, Nest without a network listener, and they come in three shapes. A script does one job and ends, like the close-day script. CLI commands give a team one program with many jobs, each with named options and help, such as ops orders replay --kitchen pune-07. The REPL is a prompt in which a person types calls to providers and sees the results, one line at a time.

A script CLI commands with nest-commander The REPL
Built on createApplicationContext() CommandFactory.run(), which wraps it repl() of @nestjs/core
Who runs it a scheduler or a deploy step people and schedulers, by name a developer, by hand
Inputs arguments you parse options declared on each command any JavaScript typed at the prompt
Fits one recurring job a toolbox of operations jobs looking around a development database

Whichever door a job uses, standalone apps share the rules of the close-day script: build the context, call the providers, close the context.

CLI commands with nest-commander are classes marked @Command() that extend CommandRunner, with @Option() methods that parse their flags; Nest treats every command as an injectable provider. CommandFactory.run(AppModule) builds the context, runs the command and closes the app when it is done, as the recipe in the Nest documentation describes. Its serviceErrorHandler option receives the errors that commands throw. nest-commander is a third-party package, not maintained by the Nest core team, and version 3.21.0 accepts Nest 12.

The REPL starts from a repl.ts beside main.ts that calls await repl(AppModule), run with npm run start -- --entryFile repl. At its prompt, get() or $ returns a provider, methods() lists its public methods and debug() prints the modules.

The REPL runs real methods

A call at the REPL prompt is a real call, and a method that writes to the database writes. Point the REPL at a development database; jobs that change production data belong in reviewed scripts or commands.

Checkpoint

Check: Scripts, CLI commands and the REPL

  1. Question 1 of 2 On-call engineers need to re-send one kitchen's orders by hand, with a required kitchen id, a dry run and help text, and the same job will later run from a scheduler. Which of the three fits?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A CLI command with nest-commander, such as ops orders replay

    One recurring job is a script; a toolbox of named jobs with options is a CLI; questions by hand go to the REPL.

  2. Question 2 of 2 How does a nest-commander command class get the OrdersService it needs?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Through its constructor, because Nest treats every command as an injectable provider

    A command is a provider with a run() method; it is listed in a module's providers and receives what it needs.

An operations CLI with nest-commander: commands, options and exit codes, run Pro: opens with a Pro, Premium or Ultimate pass

An operations CLI turns the jobs a team runs by hand into named, documented commands. This part builds TiffinTrail’s with three commands, runs each one, and measures how every way of ending sets the exit status a scheduler sees.

In this part:

  • Commands are providers: three groups, one module
  • What ends a command, and the exit status a scheduler sees
  • Making an operations command safe to run by hand
  • Testing a command without the command line

1 diagram · 2 runnable examples (JavaScript, TypeScript (commands.ts), TypeScript (ops.ts)) · about 7 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

The REPL on a development database, and scripts that exit without a word Pro: opens with a Pro, Premium or Ultimate pass

The REPL is the fastest way to ask a running app a question, and a script that dies without a message is the slowest failure to explain. This part feeds the REPL a file of commands, then makes a script die three ways and reads what each one leaves behind.

In this part:

  • A REPL session, fed four lines
  • A script that dies without a word, reproduced three ways
  • A checklist for a script a scheduler runs

4 runnable examples (JavaScript, TypeScript) · about 5 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Practise: a script that reports how it ended

The exercise is the frame of every script in the first section: create the context, run the job, close the context whatever happened, and return the exit status:

Exercise · Easy · JavaScript

Run a standalone job, close it and report how it ended

Write the frame every script built on NestFactory.createApplicationContext() needs, the one the close-day script of the lesson follows: create the context, run the job, close the context whatever happened, and say how it ended, so that a cron job or a deploy step can tell success from failure by the exit code.

runScript(createContext, work) receives two async functions: createContext() returns an application context (an object with an async close()), and work(app) runs the job on it and returns a one-line summary. Return { code, lines }:

  • when everything succeeds: code 0 and lines holding the summary;
  • when createContext() throws: code 1, lines holding start-up failed: <message>, and work is never called;
  • when work throws: lines holds job failed: <message>, code is 1, and the context is still closed;
  • when close() throws: add close failed: <message> after the other lines, and code is 1.

close() is called exactly once whenever a context was created.

Starter code · run-script.mjs

/**
 * Runs one standalone job the way a script built on createApplicationContext() should: create the context, run the
 * work, always close the context, and report the outcome. Returns { code, lines }: the exit code (0 only when
 * everything succeeded) and the lines the script prints.
 */
export async function runScript(createContext, work) {
  const app = await createContext();
  const summary = await work(app);
  return { code: 0, lines: [summary] };
}
The sample tests · run-script.test.mjs
import { assert, test } from 'mysmartcopilot:test';
import { runScript } from './run-script.mjs';

/** A fake application context that records whether close() was called, and can fail on close. */
function fakeContext(closeError) {
  const app = {
    closed: 0,
    async close() {
      app.closed += 1;
      if (closeError) throw new Error(closeError);
    },
  };
  return app;
}

test('a job that succeeds prints its summary, closes the context and exits with 0', async () => {
  const app = fakeContext();
  const result = await runScript(async () => app, async () => '2026-09-17: closed, 5 lunches sent');
  assert.deepEqual(result, { code: 0, lines: ['2026-09-17: closed, 5 lunches sent'] });
  assert.equal(app.closed, 1);
});

test('a job that throws still closes the context, and exits with 1', async () => {
  const app = fakeContext();
  const result = await runScript(async () => app, async () => {
    throw new Error('--at must be an ISO time');
  });
  assert.deepEqual(result, { code: 1, lines: ['job failed: --at must be an ISO time'] });
  assert.equal(app.closed, 1);
});

test('a start-up that fails has no context to close, and exits with 1', async () => {
  let worked = false;
  const result = await runScript(
    async () => {
      throw new Error('menu service unreachable');
    },
    async () => {
      worked = true;
      return 'never';
    },
  );
  assert.deepEqual(result, { code: 1, lines: ['start-up failed: menu service unreachable'] });
  assert.equal(worked, false);
});

test('a close that fails after a good job is reported, and the exit code is 1', async () => {
  const app = fakeContext('pool did not close');
  const result = await runScript(async () => app, async () => 'done');
  assert.deepEqual(result, { code: 1, lines: ['done', 'close failed: pool did not close'] });
});

test('a failed job and a failed close are both reported', async () => {
  const app = fakeContext('pool did not close');
  const result = await runScript(async () => app, async () => {
    throw new Error('kitchen pune-12 not found');
  });
  assert.deepEqual(result, { code: 1, lines: ['job failed: kitchen pune-12 not found', 'close failed: pool did not close'] });
});

test('the work receives the context that was created', async () => {
  const app = fakeContext();
  let received;
  await runScript(async () => app, async (ctx) => {
    received = ctx;
    return 'ok';
  });
  assert.equal(received, app);
});
A hint

Use three separate try blocks, one per step, so that a failure in one does not skip the next: the first returns early when there is no context, the second records the job's failure and sets code to 1, and the third, which always runs, closes the context. A single try … finally around the job and close() works too, as long as an error in close() cannot hide the job's own result.

The sample tests run on this device, in your browser (QuickJS): nothing is sent to mysmartcopilot.com. The first run downloads JavaScript (about 0.6 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Then the self-check on application contexts and on the three ways to run a job, which asks first about the idea you are least sure of:

Practice that adapts to you

Practise this lesson

7 questions about 5 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 The API wraps every response in { data } with a global interceptor. In the close-day script, app.get(OrdersController).list() returned the bare ids. Why?

    Choose one answer.

    Show the answer: Question 1 of 7

    Answer: Interceptors act on requests, and a method called directly in a context is not a request

    Guards, pipes and interceptors belong to the request pipeline, so scripts call services, not controllers.

  2. Question 2 of 7 Which of these does createApplicationContext() not do?

    Choose one answer.

    Show the answer: Question 2 of 7

    Answer: Open a port and register the routes

    A context is the container and its lifecycle, without the HTTP server.

  3. Question 3 of 7 The close-day script catches an error, prints it, sets process.exitCode to 1, and closes the context in finally. Which exit status does the scheduler see?

    Type a number.

    Show the answer: Question 3 of 7

    Answer: 1

    The bad --at run printed the message, closed the context (orders saved) and ended with exit status 1.

  4. Question 4 of 7 An order is placed at 23:40 UTC. For which lunch day does the close-day script count it?

    Choose one answer.

    Show the answer: Question 4 of 7

    Answer: The next calendar day, because 23:40 UTC is 05:10 IST on the following date

    Adding 5 hours 30 minutes to 23:40 UTC gives 05:10 IST on the next date, well before that day's 10:30 IST cut-off, which is 05:00 UTC.

  5. Question 5 of 7 How does the Nest documentation start the REPL?

    Choose one answer.

    Show the answer: Question 5 of 7

    Answer: From a repl.ts that awaits repl(AppModule), with npm run start -- --entryFile repl

    repl() builds the app from the module you pass and opens the prompt with Nest's helpers.

  6. Question 6 of 7 Who maintains nest-commander?

    Choose one answer.

    Show the answer: Question 6 of 7

    Answer: Its own authors; the Nest documentation calls it a third-party package not managed by the core team

    Check a third-party package's peer dependencies before an upgrade of Nest; 3.21.0 accepts Nest 8 to 12.

  7. Question 7 of 7 A teammate wants to fix 40 orders in production by typing calls at the REPL prompt. What do you advise?

    Choose one answer.

    Show the answer: Question 7 of 7

    Answer: Write the fix as a script or a command that can be reviewed before it runs, and keep the REPL for a development database

    Jobs that change production data belong in reviewed scripts or commands, as the lesson's warning says; the REPL is for looking around a development app.

Interview questions

  1. Fresher to mid · Warm-up

    What is the difference between NestFactory.create() and NestFactory.createApplicationContext(), and when would you use the second?

    Both read the root module and every module it imports, build the same providers and run the start-up lifecycle hooks. NestFactory.create() also creates an HTTP server through the adapter, registers the routes of the controllers and puts guards, pipes, interceptors and filters in front of them. createApplicationContext() stops before that: no server, no port, no routes, and nothing of the request pipeline. Use it for work that needs the app's services without serving requests: a script run by a scheduler, such as this lesson's close-day job at the 10:30 IST cut-off, a queue worker, or the base of a CLI. Take providers with app.get() or app.select(Module).get(Token, { strict: true }), and end with await app.close(), which runs the shutdown hooks and lets the process exit.

  2. Mid to senior

    A nightly billing command built with nest-commander reports success to the scheduler every night, yet some mornings no invoices exist, and the logs of those nights show an error message. What is the likely cause, and how do you make failures visible to the scheduler and to people?

    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

    A data-fix script built on createApplicationContext() ends with exit status 1 in CI and prints nothing at all; on a laptop it works. How do you find out what failed, and what do you change so that it can never fail silently again?

    Model answer, Pro. First step and follow-ups

  4. Mid to staff

    During a 20-minute outage, one kitchen's display missed 40 orders. The on-call engineer must send them again now, and the same may be needed after the next outage. Would you use the Nest REPL, a one-off script or a CLI command? Argue for one, and say what you would build in to make it safe.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • NestFactory.createApplicationContext() builds every module and provider and runs their lifecycle hooks, with no HTTP server, so a script reuses the API’s services.
  • A script ends with await app.close(), which runs the shutdown hooks and lets the process exit with the status the script set.
  • Guards, pipes and interceptors act on requests, so they do not apply to methods a script calls directly.
  • A script fits one job, CLI commands with nest-commander fit a toolbox of named jobs with options, and the REPL fits questions asked by hand against a development app.

Terms in this lesson

application context
What NestFactory.createApplicationContext() returns: an app's modules and providers, built and with their lifecycle hooks run, but no HTTP server, routes or request pipeline. Scripts, workers and CLIs run on it.
REPL
A read-eval-print loop: a prompt that runs each line typed and prints the result. Nest's repl() from @nestjs/core opens one on an app's modules, with helpers such as get() and debug().

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.