[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"search-suggestions":44,"article-nestjs-weekly-lifecycle-hook-order":93,"path-membership-nestjs-weekly-lifecycle-hook-order":511,"related-nestjs-weekly-lifecycle-hook-order":512,"code:ts:true:umdl2p":616,"code:ts:true:14rbl90":617,"code:ts:true:1035pyf":618,"code:ts:true:inggyh":619,"code:ts:true:1hdwmog":620,"code:ts:true:1qsp3t4":621,"code:ts:true:1h1hqkv":622,"comments-01a1358a-158e-70df-8178-64af7e9a5932":623},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,[45,49,53,57,61,65,69,73,77,81,85,89],{"slug":46,"name":47,"articles":48},"webdev","Webdev",151,{"slug":50,"name":51,"articles":52},"javascript","Javascript",124,{"slug":54,"name":55,"articles":56},"frontend","Frontend",88,{"slug":58,"name":59,"articles":60},"tutorial","Tutorial",58,{"slug":62,"name":63,"articles":64},"css","Css",45,{"slug":66,"name":67,"articles":68},"performance","Performance",25,{"slug":70,"name":71,"articles":72},"typescript","Typescript",21,{"slug":74,"name":75,"articles":76},"react","React",19,{"slug":78,"name":79,"articles":80},"browser","Browser",14,{"slug":82,"name":83,"articles":84},"node","Node",13,{"slug":86,"name":87,"articles":88},"programming","Programming",12,{"slug":90,"name":91,"articles":92},"accessibility","Accessibility",10,{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":84,"publishedAt":100,"viewCount":101,"likeCount":19,"commentCount":19,"author":102,"vertical":107,"topic":108,"tags":111,"_count":116,"playground":118,"body":120,"bodyMd":466,"seo":467,"translationGroupId":469,"series":470,"podcastUrl":97,"verticalId":5,"thread":500,"assessments":502,"translations":507,"quiz":509},"01a1358a-158e-70df-8178-64af7e9a5932","nestjs-weekly-lifecycle-hook-order","NestJS 12 Lifecycle Hooks: The Order You Can't Assume",null,"NestJS 12 changed how lifecycle hooks are ordered. Learn what onModuleInit and onApplicationBootstrap really guarantee, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-lifecycle-hook-order.png","2026-10-23T06:10:32.276Z",24,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},"nestjs","Nestjs",[112,113,114,115],{"slug":109,"name":110,"color":97},{"slug":70,"name":71,"color":97},{"slug":82,"name":83,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},1,{"slug":95,"title":119},"NestJS lifecycle hook order — interactive playground",{"blocks":121,"version":117},[122,126,129,134,137,146,149,152,155,158,172,175,178,184,188,191,194,197,200,203,206,209,212,215,218,221,224,227,231,239,242,245,248,252,255,258,261,264,267,270,273,276,280,284,287,290,293,296,299,303,306,309,312,315,318,321,324,332,335,343,346,349,352,356,359,362,365,368,371,374,377,380,420,423,430,433,436,439,442,448,451,454,457,460],{"id":123,"html":124,"type":125},"b1","\u003Cp>A NestJS app upgrades from v11 to v12. No application code changes — just \u003Ccode>npm i @nestjs\u002Fcore@latest @nestjs\u002Fcommon@latest\u003C\u002Fcode>. The test suite, which has been green for months, starts failing intermittently: a report service reads a cache that a metrics service is supposed to have already populated, and about one run in three, it hasn&#39;t been.\u003C\u002Fp>","paragraph",{"id":127,"html":128,"type":125},"b2","\u003Cp>Nothing in the diff touches either service. The bug was always there — it just used to be invisible, because the old, undocumented order happened to keep working. The \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.0.0\">official v12.0.0 release notes\u003C\u002Fa> list the cause in one line, under breaking changes: \u003Cstrong>&quot;Lifecycle hooks are now invoked by component hierarchy level.&quot;\u003C\u002Fstrong> The fix isn&#39;t a workaround for v12 — it&#39;s realizing that &quot;hook order&quot; was never a promise Nest made for providers like these two.\u003C\u002Fp>",{"id":130,"html":131,"text":132,"type":133,"level":31},"b3","What you&#39;ll learn","What you'll learn","heading",{"id":135,"html":136,"type":125},"b4","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":138,"type":139,"items":140,"ordered":18},"b5","list",[141,142,143,144,145],"Name every NestJS lifecycle hook and state precisely when each one fires","Explain which part of hook ordering Nest actually guarantees — and which part it has always reserved the right to change","Recognize the v12 breaking change for what it is, and know why it broke code that looked correct","Fix a hook-ordering bug the right way: with dependency injection, not a delay or a flag","Enable and reason about shutdown hooks (\u003Ccode>onModuleDestroy\u003C\u002Fcode>, \u003Ccode>beforeApplicationShutdown\u003C\u002Fcode>, \u003Ccode>onApplicationShutdown\u003C\u002Fcode>) correctly",{"id":147,"html":148,"text":148,"type":133,"level":31},"b6","Who this is for",{"id":150,"html":151,"type":125},"b7","\u003Cp>You&#39;ve written at least one \u003Ccode>onModuleInit()\u003C\u002Fcode> in a NestJS service and injected a provider into another provider&#39;s constructor. You don&#39;t need any prior lifecycle-hook experience beyond that — this article builds the full model from there.\u003C\u002Fp>",{"id":153,"html":154,"type":125},"b8","\u003Cp>This article is written against \u003Cstrong>NestJS 12.x\u003C\u002Fstrong> (verified against \u003Ccode>@nestjs\u002Fcore\u003C\u002Fcode>&#39;s npm dist-tags and the \u003Ccode>nestjs\u002Fnest\u003C\u002Fcode> GitHub release history: \u003Ccode>latest\u003C\u002Fcode> is \u003Ccode>12.2.0\u003C\u002Fcode>, \u003Ccode>v12.0.0\u003C\u002Fcode> shipped August 27, 2026, and the 11.x line has moved to the \u003Ccode>legacy\u003C\u002Fcode> npm tag at \u003Ccode>11.2.7\u003C\u002Fcode>). The point releases since v12.0.0 (through 12.2.0) fix shutdown-sequence and startup-failure edge cases but don&#39;t change the \u003Ccode>onModuleInit\u003C\u002Fcode>\u002F\u003Ccode>onApplicationBootstrap\u003C\u002Fcode> ordering model described below. Everything here is core-container behavior — it works identically whether your \u003Ccode>HttpAdapter\u003C\u002Fcode> is Express or Fastify, since lifecycle hooks fire before Nest ever hands a request to either one.\u003C\u002Fp>",{"id":156,"html":157,"text":157,"type":133,"level":31},"b9","Table of contents",{"id":159,"type":139,"items":160,"ordered":18},"b10",[161,162,163,164,165,166,167,168,169,170,171],"\u003Ca href=\"#the-problem-a-hook-order-nobody-promised\">The problem: a hook order nobody promised\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-constructed-order-vs-hook-order\">The mental model: constructed order vs. hook order\u003C\u002Fa>","\u003Ca href=\"#stage-1-the-five-lifecycle-hooks\">Stage 1: the five lifecycle hooks\u003C\u002Fa>","\u003Ca href=\"#stage-2-the-order-nest-does-guarantee\">Stage 2: the order Nest does guarantee\u003C\u002Fa>","\u003Ca href=\"#stage-3-the-order-it-never-guaranteed-and-v12-changed\">Stage 3: the order it never guaranteed — and v12 changed\u003C\u002Fa>","\u003Ca href=\"#stage-4-the-fix-is-di-not-a-delay\">Stage 4: the fix is DI, not a delay\u003C\u002Fa>","\u003Ca href=\"#stage-5-shutdown-hooks-and-async-hooks\">Stage 5: shutdown hooks and async hooks\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge cases and gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat sheet\u003C\u002Fa>",{"id":173,"html":174,"text":174,"type":133,"level":31},"b11","The problem: a hook order nobody promised",{"id":176,"html":177,"type":125},"b12","\u003Cp>Here&#39;s the setup, trimmed to the part that matters:\u003C\u002Fp>",{"id":179,"code":180,"type":181,"language":182,"highlight":183},"b13","\u002F\u002F metrics.module.ts\nlet requestCounterSeed = 0;\n\n@Injectable()\nexport class MetricsService implements OnModuleInit {\n  onModuleInit() {\n    requestCounterSeed = loadSeedFromDisk(); \u002F\u002F populates module-scope state\n  }\n}","code","ts",[],{"id":185,"code":186,"type":181,"language":182,"highlight":187},"b14","\u002F\u002F reports.service.ts\n@Injectable()\nexport class ReportsService implements OnModuleInit {\n  private baseline = 0;\n\n  onModuleInit() {\n    this.baseline = requestCounterSeed; \u002F\u002F reads it back — no DI involved\n  }\n}",[],{"id":189,"html":190,"type":125},"b15","\u003Cp>\u003Ccode>ReportsService\u003C\u002Fcode> never injects \u003Ccode>MetricsService\u003C\u002Fcode>. There&#39;s no \u003Ccode>constructor(private metrics: MetricsService)\u003C\u002Fcode> anywhere — the two talk through a module-scope variable instead, and the code &quot;works&quot; as long as \u003Ccode>MetricsService\u003C\u002Fcode>&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> happens to run before \u003Ccode>ReportsService\u003C\u002Fcode>&#39;s. Under Nest 10 and 11, it reliably did — because hook order followed each module&#39;s position in \u003Ccode>AppModule\u003C\u002Fcode>&#39;s \u003Ccode>imports\u003C\u002Fcode> array, and \u003Ccode>MetricsModule\u003C\u002Fcode> happened to be listed first.\u003C\u002Fp>",{"id":192,"html":193,"type":125},"b16","\u003Cp>It is not a coincidence that this looks fragile. It \u003Cem>is\u003C\u002Fem> fragile — it was fragile before the upgrade too. The v12 release note doesn&#39;t introduce the bug; it removes the accident that was hiding it. Once hook invocation moved to being ordered &quot;by component hierarchy level&quot; instead of import position, \u003Ccode>ReportsService.onModuleInit\u003C\u002Fcode> can run first, and \u003Ccode>requestCounterSeed\u003C\u002Fcode> is still \u003Ccode>0\u003C\u002Fcode> when it reads it.\u003C\u002Fp>",{"id":195,"html":196,"text":196,"type":133,"level":31},"b17","The mental model: constructed order vs. hook order",{"id":198,"html":199,"type":125},"b18","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> NestJS gives you exactly one hard guarantee about initialization order, and it comes from the dependency graph, not from where a hook happens to live. If provider \u003Ccode>B\u003C\u002Fcode> is constructor-injected into provider \u003Ccode>A\u003C\u002Fcode>, Nest must fully construct \u003Ccode>B\u003C\u002Fcode> — and run \u003Ccode>B\u003C\u002Fcode>&#39;s own \u003Ccode>onModuleInit\u003C\u002Fcode> — before it can finish constructing \u003Ccode>A\u003C\u002Fcode> and run \u003Ccode>A\u003C\u002Fcode>&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode>. That ordering is real, structural, and the same in every version, because it falls directly out of how constructor injection works: you cannot hand \u003Ccode>A\u003C\u002Fcode> a \u003Ccode>B\u003C\u002Fcode> that doesn&#39;t exist yet.\u003C\u002Fp>",{"id":201,"html":202,"type":125},"b19","\u003Cp>Everything else — the order of two providers that share \u003Cstrong>no\u003C\u002Fstrong> dependency edge, sitting in sibling modules that never import each other — has never been part of that contract. It happened to be stable for a long time because Nest&#39;s internal bootstrap walked the module tree in a specific, observable order. &quot;Observable&quot; is not &quot;guaranteed&quot;: nothing in Nest&#39;s public API ever promised that two unrelated providers would always initialize in the same relative order, which is exactly why the v12 release notes can change it and call it a breaking change rather than a bug fix — it was breaking an assumption, not a contract.\u003C\u002Fp>",{"id":204,"html":205,"type":125},"b20","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> if two providers&#39; initialization order matters to your code, that requirement has to be expressed as a dependency — an injected constructor parameter — not inferred from import position. If there&#39;s no edge in the graph, there&#39;s no promise about the order.\u003C\u002Fp>",{"id":207,"html":208,"type":125},"b21","\u003C!-- playground:start -->",{"id":210,"html":211,"text":211,"type":133,"level":31},"b22","🎮 Try it yourself",{"id":213,"html":214,"type":125},"b23","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-lifecycle-hook-order\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":216,"html":217,"type":125},"b24","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":219,"html":220,"type":125},"b25","\u003C!-- playground:end -->",{"id":222,"html":223,"text":223,"type":133,"level":31},"b26","Stage 1: the five lifecycle hooks",{"id":225,"html":226,"type":125},"b27","\u003Cp>Nest exposes five lifecycle interfaces a provider (or module class) can implement. Each is a plain TypeScript interface with one method, and implementing it is how you opt in:\u003C\u002Fp>",{"id":228,"code":229,"type":181,"language":182,"highlight":230},"b28","import {\n  OnModuleInit,\n  OnApplicationBootstrap,\n  OnModuleDestroy,\n  BeforeApplicationShutdown,\n  OnApplicationShutdown,\n} from '@nestjs\u002Fcommon';",[],{"id":232,"type":139,"items":233,"ordered":18},"b29",[234,235,236,237,238],"\u003Cstrong>\u003Ccode>onModuleInit()\u003C\u002Fcode>\u003C\u002Fstrong> — called once the host module&#39;s own dependencies have been resolved and constructed. This is the hook for &quot;set up something that needs this provider&#39;s injected dependencies to already exist.&quot;","\u003Cstrong>\u003Ccode>onApplicationBootstrap()\u003C\u002Fcode>\u003C\u002Fstrong> — called once \u003Cstrong>every\u003C\u002Fstrong> module in the app has finished \u003Ccode>onModuleInit\u003C\u002Fcode>, but before Nest starts listening for connections. Use it for work that should only start once the whole graph is up, not just this provider&#39;s own corner of it.","\u003Cstrong>\u003Ccode>onModuleDestroy()\u003C\u002Fcode>\u003C\u002Fstrong> — the mirror of \u003Ccode>onModuleInit\u003C\u002Fcode>, run during teardown.","\u003Cstrong>\u003Ccode>beforeApplicationShutdown(signal?: string)\u003C\u002Fcode>\u003C\u002Fstrong> — runs after \u003Ccode>onModuleDestroy\u003C\u002Fcode>, with access to the OS signal (\u003Ccode>SIGTERM\u003C\u002Fcode>, \u003Ccode>SIGINT\u003C\u002Fcode>) that triggered shutdown, if there was one.","\u003Cstrong>\u003Ccode>onApplicationShutdown(signal?: string)\u003C\u002Fcode>\u003C\u002Fstrong> — the last hook to run, immediately before the process would otherwise exit.",{"id":240,"html":241,"type":125},"b30","\u003Cp>\u003Ccode>onModuleInit\u003C\u002Fcode> and \u003Ccode>onApplicationBootstrap\u003C\u002Fcode> only fire if you actually call \u003Ccode>app.init()\u003C\u002Fcode> or \u003Ccode>app.listen()\u003C\u002Fcode> — a \u003Ccode>TestingModule\u003C\u002Fcode> you only \u003Ccode>compile()\u003C\u002Fcode> but never \u003Ccode>init()\u003C\u002Fcode> skips both, which is a common source of &quot;why didn&#39;t my hook run in this test&quot; confusion.\u003C\u002Fp>",{"id":243,"html":244,"text":244,"type":133,"level":31},"b31","Stage 2: the order Nest does guarantee",{"id":246,"html":247,"type":125},"b32","\u003Cp>The one ordering rule worth memorizing: \u003Cstrong>a provider&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> runs after the \u003Ccode>onModuleInit\u003C\u002Fcode> of every provider it depends on, transitively.\u003C\u002Fstrong> This is a direct consequence of constructor injection being resolved depth-first.\u003C\u002Fp>",{"id":249,"code":250,"type":181,"language":182,"highlight":251},"b33","@Injectable()\nexport class CacheService implements OnModuleInit {\n  onModuleInit() {\n    console.log('CacheService ready');\n  }\n}\n\n@Injectable()\nexport class OrdersService implements OnModuleInit {\n  constructor(private readonly cache: CacheService) {}\n\n  onModuleInit() {\n    console.log('OrdersService ready'); \u002F\u002F always logs after CacheService\n  }\n}",[],{"id":253,"html":254,"type":125},"b34","\u003Cp>Because \u003Ccode>OrdersService\u003C\u002Fcode> injects \u003Ccode>CacheService\u003C\u002Fcode>, \u003Ccode>&quot;CacheService ready&quot;\u003C\u002Fcode> is guaranteed to print before \u003Ccode>&quot;OrdersService ready&quot;\u003C\u002Fcode> — in Nest 10, 11, 12, and every future version, because changing it would mean handing \u003Ccode>OrdersService\u003C\u002Fcode> a \u003Ccode>CacheService\u003C\u002Fcode> whose own setup hasn&#39;t run yet. That&#39;s not an implementation detail Nest could casually break; it&#39;s the definition of what constructor injection means.\u003C\u002Fp>",{"id":256,"html":257,"type":125},"b35","\u003Cp>\u003Ccode>onApplicationBootstrap\u003C\u002Fcode> extends the same guarantee one level further: because it only fires after \u003Cem>every\u003C\u002Fem> module&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> has completed, anything you do there can safely assume the entire app&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> work — dependency-ordered or not — is already done.\u003C\u002Fp>",{"id":259,"html":260,"text":260,"type":133,"level":31},"b36","Stage 3: the order it never guaranteed — and v12 changed",{"id":262,"html":263,"type":125},"b37","\u003Cp>What Nest has never promised is the relative order between two providers that share no dependency edge — like \u003Ccode>MetricsService\u003C\u002Fcode> and \u003Ccode>ReportsService\u003C\u002Fcode> above. Before v12, that order was a side effect of how Nest walked the module tree (roughly, each module&#39;s \u003Ccode>imports\u003C\u002Fcode> in declaration order, depth-first). It was deterministic in practice, which is exactly what made it easy to depend on by accident.\u003C\u002Fp>",{"id":265,"html":266,"type":125},"b38","\u003Cp>The v12.0.0 release notes list the actual change under breaking changes, verbatim: \u003Cstrong>&quot;Lifecycle hooks are now invoked by component hierarchy level.&quot;\u003C\u002Fstrong> The notes don&#39;t spell out the full new algorithm step by step — their own guidance is to &quot;review ordering assumptions between related providers\u002Fmodules in init, teardown, and tests&quot; rather than to assume your old mental model of &quot;import order&quot; still predicts anything. That&#39;s the honest, practical takeaway: don&#39;t try to reverse-engineer the new implicit order and depend on \u003Cem>that\u003C\u002Fem> instead — it&#39;s exactly the same mistake with a different answer.\u003C\u002Fp>",{"id":268,"html":269,"text":269,"type":133,"level":31},"b39","Stage 4: the fix is DI, not a delay",{"id":271,"html":272,"type":125},"b40","\u003Cp>The wrong fixes are tempting and all fragile: reordering \u003Ccode>imports\u003C\u002Fcode> in \u003Ccode>AppModule\u003C\u002Fcode> (back to relying on an undocumented detail, just a different one), adding a \u003Ccode>setTimeout\u003C\u002Fcode> before reading the shared state (a race condition with extra steps), or introducing a global &quot;ready&quot; flag with a polling loop. All three fix today&#39;s symptom and leave the actual defect — an implicit dependency — in place.\u003C\u002Fp>",{"id":274,"html":275,"type":125},"b41","\u003Cp>The real fix is to make the dependency explicit, the same way you would for any other cross-service call:\u003C\u002Fp>",{"id":277,"code":278,"type":181,"language":182,"highlight":279},"b42","@Injectable()\nexport class ReportsService implements OnModuleInit {\n  private baseline = 0;\n\n  constructor(private readonly metrics: MetricsService) {}\n\n  onModuleInit() {\n    this.baseline = this.metrics.getSeed(); \u002F\u002F guaranteed: metrics already initialized\n  }\n}",[],{"id":281,"code":282,"type":181,"language":182,"highlight":283},"b43","@Injectable()\nexport class MetricsService implements OnModuleInit {\n  private seed = 0;\n\n  onModuleInit() {\n    this.seed = loadSeedFromDisk();\n  }\n\n  getSeed() {\n    return this.seed;\n  }\n}",[],{"id":285,"html":286,"type":125},"b44","\u003Cp>Once \u003Ccode>ReportsService\u003C\u002Fcode> constructor-injects \u003Ccode>MetricsService\u003C\u002Fcode>, Stage 2&#39;s guarantee applies again: Nest cannot construct \u003Ccode>ReportsService\u003C\u002Fcode> without \u003Ccode>MetricsService\u003C\u002Fcode> already existing, and \u003Ccode>MetricsService.onModuleInit\u003C\u002Fcode> is guaranteed to have already run. The module-scope variable is gone, replaced by a method call on an instance Nest already knows how to order correctly — because now there&#39;s an edge in the graph for it to order \u003Cem>by\u003C\u002Fem>.\u003C\u002Fp>",{"id":288,"html":289,"type":125},"b45","\u003Cp>\u003Cstrong>Key concept:\u003C\u002Fstrong> a lifecycle-ordering bug is almost always a missing edge in the dependency graph wearing a disguise. Add the edge (inject the thing), and the ordering problem disappears on its own — you don&#39;t have to solve it, Nest already does.\u003C\u002Fp>",{"id":291,"html":292,"text":292,"type":133,"level":31},"b46","Stage 5: shutdown hooks and async hooks",{"id":294,"html":295,"type":125},"b47","\u003Cp>Every hook in Stage 1 can be \u003Ccode>async\u003C\u002Fcode> — return a \u003Ccode>Promise\u003C\u002Fcode>, and Nest waits for it before moving to the next stage. An \u003Ccode>async onModuleInit()\u003C\u002Fcode> that awaits a database connection genuinely blocks \u003Ccode>app.listen()\u003C\u002Fcode> until it resolves, which is usually exactly what you want (don&#39;t accept traffic before you can serve it) but is worth knowing explicitly, since a slow connection now shows up as a slow boot, not a slow first request.\u003C\u002Fp>",{"id":297,"html":298,"type":125},"b48","\u003Cp>Teardown hooks (\u003Ccode>onModuleDestroy\u003C\u002Fcode>, \u003Ccode>beforeApplicationShutdown\u003C\u002Fcode>, \u003Ccode>onApplicationShutdown\u003C\u002Fcode>) only run on \u003Ccode>app.close()\u003C\u002Fcode> or an OS shutdown signal — \u003Cstrong>and only if you&#39;ve opted in\u003C\u002Fstrong>:\u003C\u002Fp>",{"id":300,"code":301,"type":181,"language":182,"highlight":302},"b49","\u002F\u002F main.ts\nconst app = await NestFactory.create(AppModule);\napp.enableShutdownHooks(); \u002F\u002F without this, SIGTERM\u002FSIGINT skip teardown hooks entirely\nawait app.listen(3000);",[],{"id":304,"html":305,"type":125},"b50","\u003Cp>Forgetting \u003Ccode>enableShutdownHooks()\u003C\u002Fcode> is the most common reason a service that&#39;s supposed to flush a queue or close a connection pool on shutdown silently doesn&#39;t — the process just exits, and no teardown hook ever runs.\u003C\u002Fp>",{"id":307,"html":308,"type":125},"b51","\u003C!-- quiz:start -->",{"id":310,"html":311,"text":311,"type":133,"level":31},"b52","🧠 Test yourself",{"id":313,"html":314,"type":125},"b53","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-lifecycle-hook-order\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":316,"html":317,"type":125},"b54","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":319,"html":320,"type":125},"b55","\u003C!-- quiz:end -->",{"id":322,"html":323,"text":323,"type":133,"level":31},"b56","Edge cases and gotchas",{"id":325,"type":139,"items":326,"ordered":18},"b57",[327,328,329,330,331],"\u003Cstrong>Tests that only \u003Ccode>compile()\u003C\u002Fcode>, never \u003Ccode>init()\u003C\u002Fcode>.\u003C\u002Fstrong> \u003Ccode>Test.createTestingModule({...}).compile()\u003C\u002Fcode> builds the module graph but never calls \u003Ccode>onModuleInit\u003C\u002Fcode> or \u003Ccode>onApplicationBootstrap\u003C\u002Fcode>. If a hook-driven side effect doesn&#39;t appear to run in a test, check whether the test ever called \u003Ccode>.init()\u003C\u002Fcode> on the result.","\u003Cstrong>Circular dependencies with \u003Ccode>forwardRef()\u003C\u002Fcode>.\u003C\u002Fstrong> Two providers that depend on each other via \u003Ccode>forwardRef(() =&gt; OtherService)\u003C\u002Fcode> can&#39;t have a clean &quot;B before A&quot; order, because there is no clean order — Nest resolves the cycle, but a hook in either one that assumes the other is fully initialized can still race. Prefer breaking the cycle over patching around it.","\u003Cstrong>Global modules aren&#39;t a shortcut around ordering.\u003C\u002Fstrong> A module marked \u003Ccode>@Global()\u003C\u002Fcode> is visible everywhere without importing it, but it is still initialized according to the same rules — &quot;global&quot; changes visibility, not hook order.","\u003Cstrong>\u003Ccode>onApplicationBootstrap\u003C\u002Fcode> running late in a slow-starting app.\u003C\u002Fstrong> Because it waits for every module&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode>, one slow async hook anywhere in the graph delays \u003Ccode>onApplicationBootstrap\u003C\u002Fcode> for the whole app, not just its own module.","\u003Cstrong>Upgrading from 11.x to 12.x with hidden cross-module hook assumptions.\u003C\u002Fstrong> This is the v12-specific case this article opened with. If a test suite starts flaking right after the upgrade, treat it as a real latent bug the version bump exposed, not a Nest regression to work around.",{"id":333,"html":334,"text":334,"type":133,"level":31},"b58","Best practices",{"id":336,"type":139,"items":337,"ordered":18},"b59",[338,339,340,341,342],"\u003Cstrong>Never let a hook in one provider assume another unrelated provider already ran\u003C\u002Fstrong> unless that assumption is backed by a real constructor-injection edge. If it isn&#39;t, add the edge.","\u003Cstrong>Use \u003Ccode>onApplicationBootstrap\u003C\u002Fcode> for anything that needs the whole app ready\u003C\u002Fstrong>, not just its own module&#39;s dependencies — don&#39;t reach for a hack to delay \u003Ccode>onModuleInit\u003C\u002Fcode> work instead.","\u003Cstrong>Call \u003Ccode>app.enableShutdownHooks()\u003C\u002Fcode> in production\u003C\u002Fstrong> if any provider relies on \u003Ccode>onModuleDestroy\u003C\u002Fcode> or \u003Ccode>onApplicationShutdown\u003C\u002Fcode> to flush state or close connections cleanly.","\u003Cstrong>When you upgrade a major Nest version, read the release&#39;s breaking-changes list before you read anything else\u003C\u002Fstrong> — a change like this one produces no compile error and no obvious runtime error, only an intermittent one.","\u003Cstrong>If you genuinely need to verify ordering empirically\u003C\u002Fstrong>, log each hook with its provider&#39;s name during boot rather than assuming either the &quot;old&quot; or &quot;new&quot; order from memory — the release notes don&#39;t spell out the full new algorithm, so the only way to know your app&#39;s actual order is to look at it.",{"id":344,"html":345,"text":345,"type":133,"level":31},"b60","FAQ",{"id":347,"html":348,"text":348,"type":133,"level":43},"b61","Does onModuleInit run in constructor order?",{"id":350,"html":351,"type":125},"b62","\u003Cp>Only where a real dependency edge exists. If \u003Ccode>A\u003C\u002Fcode> injects \u003Ccode>B\u003C\u002Fcode>, \u003Ccode>B\u003C\u002Fcode>&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> is guaranteed to run first. Between providers with no such edge, there&#39;s no guaranteed order, and NestJS 12&#39;s release notes confirm that relative order can and did change.\u003C\u002Fp>",{"id":353,"html":354,"text":355,"type":133,"level":43},"b63","Why didn&#39;t my onModuleDestroy fire when I sent SIGTERM?","Why didn't my onModuleDestroy fire when I sent SIGTERM?",{"id":357,"html":358,"type":125},"b64","\u003Cp>Almost always because \u003Ccode>app.enableShutdownHooks()\u003C\u002Fcode> was never called. Without it, Nest doesn&#39;t attach the OS signal listeners that trigger the teardown hooks at all.\u003C\u002Fp>",{"id":360,"html":361,"text":361,"type":133,"level":43},"b65","Does an async onModuleInit block app.listen()?",{"id":363,"html":364,"type":125},"b66","\u003Cp>Yes. Nest awaits every \u003Ccode>onModuleInit\u003C\u002Fcode> (and then every \u003Ccode>onApplicationBootstrap\u003C\u002Fcode>) before it starts accepting connections, so a slow one delays the whole app&#39;s startup, not just the part that&#39;s slow.\u003C\u002Fp>",{"id":366,"html":367,"text":367,"type":133,"level":43},"b67","Is this different for Fastify vs Express?",{"id":369,"html":370,"type":125},"b68","\u003Cp>No. Lifecycle hooks fire during module bootstrap, entirely before Nest hands anything to the underlying HTTP adapter, so the behavior is identical regardless of which one you&#39;re using.\u003C\u002Fp>",{"id":372,"html":373,"text":373,"type":133,"level":43},"b69","Will NestJS 13 reorder hooks again?",{"id":375,"html":376,"type":125},"b70","\u003Cp>There&#39;s no way to know in advance, and that uncertainty is the actual point of this article: code that depends only on guaranteed ordering (real dependency edges) can&#39;t be broken by a future reshuffle of the unguaranteed part, because it never relied on it.\u003C\u002Fp>",{"id":378,"html":379,"text":379,"type":133,"level":31},"b71","Cheat sheet",{"id":381,"head":382,"rows":386,"type":419},"b72",[383,384,385],"Hook","Fires when","Guaranteed relative to",[387,391,395,399,403,407,411,415],[388,389,390],"\u003Ccode>onModuleInit()\u003C\u002Fcode>","Host module&#39;s own dependencies constructed","After every injected dependency&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode>",[392,393,394],"\u003Ccode>onApplicationBootstrap()\u003C\u002Fcode>","Every module&#39;s \u003Ccode>onModuleInit\u003C\u002Fcode> has finished","After all \u003Ccode>onModuleInit\u003C\u002Fcode> hooks app-wide",[396,397,398],"\u003Ccode>onModuleDestroy()\u003C\u002Fcode>","\u003Ccode>app.close()\u003C\u002Fcode> or shutdown signal (needs \u003Ccode>enableShutdownHooks()\u003C\u002Fcode>)","Mirrors \u003Ccode>onModuleInit\u003C\u002Fcode>&#39;s dependency order, in reverse",[400,401,402],"\u003Ccode>beforeApplicationShutdown(signal)\u003C\u002Fcode>","After all \u003Ccode>onModuleDestroy\u003C\u002Fcode> hooks","After every \u003Ccode>onModuleDestroy\u003C\u002Fcode>",[404,405,406],"\u003Ccode>onApplicationShutdown(signal)\u003C\u002Fcode>","Last hook before process exit","After \u003Ccode>beforeApplicationShutdown\u003C\u002Fcode>",[408,409,410],"Two providers, no dependency edge","—","\u003Cstrong>Not guaranteed, by design\u003C\u002Fstrong> — don&#39;t rely on it",[412,413,414],"Opt in to teardown hooks","\u003Ccode>app.enableShutdownHooks()\u003C\u002Fcode> in \u003Ccode>main.ts\u003C\u002Fcode>","Must be called explicitly",[416,417,418],"Fix an ordering bug","Constructor-inject the provider you actually depend on","Turns an assumption into a real graph edge","table",{"id":421,"html":422,"text":422,"type":133,"level":31},"b73","Key takeaways",{"id":424,"type":139,"items":425,"ordered":18},"b74",[426,427,428,429],"NestJS guarantees hook order along the \u003Cstrong>dependency graph\u003C\u002Fstrong>: if \u003Ccode>A\u003C\u002Fcode> injects \u003Ccode>B\u003C\u002Fcode>, \u003Ccode>B\u003C\u002Fcode>&#39;s hooks always run first. That rule has never changed and never will, because it&#39;s a consequence of constructor injection, not a scheduling choice.","It has \u003Cstrong>never\u003C\u002Fstrong> guaranteed order between providers with no dependency edge — that was always an implementation detail, and NestJS 12&#39;s release notes confirm it changed.","A hook-ordering bug is a missing dependency edge in disguise. The fix is to inject the thing you depend on, not to chase the new implicit order.","Teardown hooks need \u003Ccode>app.enableShutdownHooks()\u003C\u002Fcode> to run at all — a very common silent gap.",{"id":431,"html":432,"type":125},"b75","\u003Cp>The report service from the top of this article didn&#39;t start being wrong the day it was upgraded to Nest 12 — it was wrong from the day someone reached for a module-scope variable instead of a constructor parameter. The version bump just stopped covering for it. Once you&#39;re reading &quot;hook order&quot; as &quot;the dependency graph, and nothing else,&quot; that whole bug class stops being a mystery you debug after an upgrade and starts being something you can rule out on sight.\u003C\u002Fp>",{"id":434,"html":435,"type":125},"b76","\u003Cp>Has a major-version upgrade ever exposed an ordering assumption you didn&#39;t know your code was making? Drop it in the comments.\u003C\u002Fp>",{"id":437,"html":438,"type":125},"b77","\u003C!-- related:start -->",{"id":440,"html":441,"text":441,"type":133,"level":31},"b78","📚 Read next",{"id":443,"type":139,"items":444,"ordered":18},"b79",[445,446,447],"\u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-dependency-injection-explained-with-cheat-sheet-4bem\">NestJS Dependency Injection Explained (with Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-testing-module-provider-overrides-with-cheat-sheet-1o79\">NestJS Testing Module: Provider Overrides (with Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-module-encapsulation-explained-with-cheat-sheet-10mo\">NestJS Module Encapsulation Explained (with Cheat Sheet)\u003C\u002Fa>",{"id":449,"html":450,"type":125},"b80","\u003C!-- related:end -->",{"id":452,"type":453},"b81","divider",{"id":455,"html":456,"type":125},"b82","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":458,"html":459,"type":125},"b83","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":461,"type":139,"items":462,"ordered":18},"b84",[463,464,465],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","A NestJS app upgrades from v11 to v12. No application code changes — just `npm i @nestjs\u002Fcore@latest @nestjs\u002Fcommon@latest`. The test suite, which has been green for months, starts failing intermittently: a report service reads a cache that a metrics service is supposed to have already populated, and about one run in three, it hasn't been.\n\nNothing in the diff touches either service. The bug was always there — it just used to be invisible, because the old, undocumented order happened to keep working. The [official v12.0.0 release notes](https:\u002F\u002Fgithub.com\u002Fnestjs\u002Fnest\u002Freleases\u002Ftag\u002Fv12.0.0) list the cause in one line, under breaking changes: **\"Lifecycle hooks are now invoked by component hierarchy level.\"** The fix isn't a workaround for v12 — it's realizing that \"hook order\" was never a promise Nest made for providers like these two.\n\n## What you'll learn\n\nBy the end of this article you'll be able to:\n\n- Name every NestJS lifecycle hook and state precisely when each one fires\n- Explain which part of hook ordering Nest actually guarantees — and which part it has always reserved the right to change\n- Recognize the v12 breaking change for what it is, and know why it broke code that looked correct\n- Fix a hook-ordering bug the right way: with dependency injection, not a delay or a flag\n- Enable and reason about shutdown hooks (`onModuleDestroy`, `beforeApplicationShutdown`, `onApplicationShutdown`) correctly\n\n## Who this is for\n\nYou've written at least one `onModuleInit()` in a NestJS service and injected a provider into another provider's constructor. You don't need any prior lifecycle-hook experience beyond that — this article builds the full model from there.\n\nThis article is written against **NestJS 12.x** (verified against `@nestjs\u002Fcore`'s npm dist-tags and the `nestjs\u002Fnest` GitHub release history: `latest` is `12.2.0`, `v12.0.0` shipped August 27, 2026, and the 11.x line has moved to the `legacy` npm tag at `11.2.7`). The point releases since v12.0.0 (through 12.2.0) fix shutdown-sequence and startup-failure edge cases but don't change the `onModuleInit`\u002F`onApplicationBootstrap` ordering model described below. Everything here is core-container behavior — it works identically whether your `HttpAdapter` is Express or Fastify, since lifecycle hooks fire before Nest ever hands a request to either one.\n\n## Table of contents\n\n- [The problem: a hook order nobody promised](#the-problem-a-hook-order-nobody-promised)\n- [The mental model: constructed order vs. hook order](#the-mental-model-constructed-order-vs-hook-order)\n- [Stage 1: the five lifecycle hooks](#stage-1-the-five-lifecycle-hooks)\n- [Stage 2: the order Nest does guarantee](#stage-2-the-order-nest-does-guarantee)\n- [Stage 3: the order it never guaranteed — and v12 changed](#stage-3-the-order-it-never-guaranteed-and-v12-changed)\n- [Stage 4: the fix is DI, not a delay](#stage-4-the-fix-is-di-not-a-delay)\n- [Stage 5: shutdown hooks and async hooks](#stage-5-shutdown-hooks-and-async-hooks)\n- [Edge cases and gotchas](#edge-cases-and-gotchas)\n- [Best practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat sheet](#cheat-sheet)\n\n## The problem: a hook order nobody promised\n\nHere's the setup, trimmed to the part that matters:\n\n```ts\n\u002F\u002F metrics.module.ts\nlet requestCounterSeed = 0;\n\n@Injectable()\nexport class MetricsService implements OnModuleInit {\n  onModuleInit() {\n    requestCounterSeed = loadSeedFromDisk(); \u002F\u002F populates module-scope state\n  }\n}\n```\n\n```ts\n\u002F\u002F reports.service.ts\n@Injectable()\nexport class ReportsService implements OnModuleInit {\n  private baseline = 0;\n\n  onModuleInit() {\n    this.baseline = requestCounterSeed; \u002F\u002F reads it back — no DI involved\n  }\n}\n```\n\n`ReportsService` never injects `MetricsService`. There's no `constructor(private metrics: MetricsService)` anywhere — the two talk through a module-scope variable instead, and the code \"works\" as long as `MetricsService`'s `onModuleInit` happens to run before `ReportsService`'s. Under Nest 10 and 11, it reliably did — because hook order followed each module's position in `AppModule`'s `imports` array, and `MetricsModule` happened to be listed first.\n\nIt is not a coincidence that this looks fragile. It *is* fragile — it was fragile before the upgrade too. The v12 release note doesn't introduce the bug; it removes the accident that was hiding it. Once hook invocation moved to being ordered \"by component hierarchy level\" instead of import position, `ReportsService.onModuleInit` can run first, and `requestCounterSeed` is still `0` when it reads it.\n\n## The mental model: constructed order vs. hook order\n\n**The mental model:** NestJS gives you exactly one hard guarantee about initialization order, and it comes from the dependency graph, not from where a hook happens to live. If provider `B` is constructor-injected into provider `A`, Nest must fully construct `B` — and run `B`'s own `onModuleInit` — before it can finish constructing `A` and run `A`'s `onModuleInit`. That ordering is real, structural, and the same in every version, because it falls directly out of how constructor injection works: you cannot hand `A` a `B` that doesn't exist yet.\n\nEverything else — the order of two providers that share **no** dependency edge, sitting in sibling modules that never import each other — has never been part of that contract. It happened to be stable for a long time because Nest's internal bootstrap walked the module tree in a specific, observable order. \"Observable\" is not \"guaranteed\": nothing in Nest's public API ever promised that two unrelated providers would always initialize in the same relative order, which is exactly why the v12 release notes can change it and call it a breaking change rather than a bug fix — it was breaking an assumption, not a contract.\n\n**Key concept:** if two providers' initialization order matters to your code, that requirement has to be expressed as a dependency — an injected constructor parameter — not inferred from import position. If there's no edge in the graph, there's no promise about the order.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-lifecycle-hook-order\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Stage 1: the five lifecycle hooks\n\nNest exposes five lifecycle interfaces a provider (or module class) can implement. Each is a plain TypeScript interface with one method, and implementing it is how you opt in:\n\n```ts\nimport {\n  OnModuleInit,\n  OnApplicationBootstrap,\n  OnModuleDestroy,\n  BeforeApplicationShutdown,\n  OnApplicationShutdown,\n} from '@nestjs\u002Fcommon';\n```\n\n- **`onModuleInit()`** — called once the host module's own dependencies have been resolved and constructed. This is the hook for \"set up something that needs this provider's injected dependencies to already exist.\"\n- **`onApplicationBootstrap()`** — called once **every** module in the app has finished `onModuleInit`, but before Nest starts listening for connections. Use it for work that should only start once the whole graph is up, not just this provider's own corner of it.\n- **`onModuleDestroy()`** — the mirror of `onModuleInit`, run during teardown.\n- **`beforeApplicationShutdown(signal?: string)`** — runs after `onModuleDestroy`, with access to the OS signal (`SIGTERM`, `SIGINT`) that triggered shutdown, if there was one.\n- **`onApplicationShutdown(signal?: string)`** — the last hook to run, immediately before the process would otherwise exit.\n\n`onModuleInit` and `onApplicationBootstrap` only fire if you actually call `app.init()` or `app.listen()` — a `TestingModule` you only `compile()` but never `init()` skips both, which is a common source of \"why didn't my hook run in this test\" confusion.\n\n## Stage 2: the order Nest does guarantee\n\nThe one ordering rule worth memorizing: **a provider's `onModuleInit` runs after the `onModuleInit` of every provider it depends on, transitively.** This is a direct consequence of constructor injection being resolved depth-first.\n\n```ts\n@Injectable()\nexport class CacheService implements OnModuleInit {\n  onModuleInit() {\n    console.log('CacheService ready');\n  }\n}\n\n@Injectable()\nexport class OrdersService implements OnModuleInit {\n  constructor(private readonly cache: CacheService) {}\n\n  onModuleInit() {\n    console.log('OrdersService ready'); \u002F\u002F always logs after CacheService\n  }\n}\n```\n\nBecause `OrdersService` injects `CacheService`, `\"CacheService ready\"` is guaranteed to print before `\"OrdersService ready\"` — in Nest 10, 11, 12, and every future version, because changing it would mean handing `OrdersService` a `CacheService` whose own setup hasn't run yet. That's not an implementation detail Nest could casually break; it's the definition of what constructor injection means.\n\n`onApplicationBootstrap` extends the same guarantee one level further: because it only fires after *every* module's `onModuleInit` has completed, anything you do there can safely assume the entire app's `onModuleInit` work — dependency-ordered or not — is already done.\n\n## Stage 3: the order it never guaranteed — and v12 changed\n\nWhat Nest has never promised is the relative order between two providers that share no dependency edge — like `MetricsService` and `ReportsService` above. Before v12, that order was a side effect of how Nest walked the module tree (roughly, each module's `imports` in declaration order, depth-first). It was deterministic in practice, which is exactly what made it easy to depend on by accident.\n\nThe v12.0.0 release notes list the actual change under breaking changes, verbatim: **\"Lifecycle hooks are now invoked by component hierarchy level.\"** The notes don't spell out the full new algorithm step by step — their own guidance is to \"review ordering assumptions between related providers\u002Fmodules in init, teardown, and tests\" rather than to assume your old mental model of \"import order\" still predicts anything. That's the honest, practical takeaway: don't try to reverse-engineer the new implicit order and depend on *that* instead — it's exactly the same mistake with a different answer.\n\n## Stage 4: the fix is DI, not a delay\n\nThe wrong fixes are tempting and all fragile: reordering `imports` in `AppModule` (back to relying on an undocumented detail, just a different one), adding a `setTimeout` before reading the shared state (a race condition with extra steps), or introducing a global \"ready\" flag with a polling loop. All three fix today's symptom and leave the actual defect — an implicit dependency — in place.\n\nThe real fix is to make the dependency explicit, the same way you would for any other cross-service call:\n\n```ts\n@Injectable()\nexport class ReportsService implements OnModuleInit {\n  private baseline = 0;\n\n  constructor(private readonly metrics: MetricsService) {}\n\n  onModuleInit() {\n    this.baseline = this.metrics.getSeed(); \u002F\u002F guaranteed: metrics already initialized\n  }\n}\n```\n\n```ts\n@Injectable()\nexport class MetricsService implements OnModuleInit {\n  private seed = 0;\n\n  onModuleInit() {\n    this.seed = loadSeedFromDisk();\n  }\n\n  getSeed() {\n    return this.seed;\n  }\n}\n```\n\nOnce `ReportsService` constructor-injects `MetricsService`, Stage 2's guarantee applies again: Nest cannot construct `ReportsService` without `MetricsService` already existing, and `MetricsService.onModuleInit` is guaranteed to have already run. The module-scope variable is gone, replaced by a method call on an instance Nest already knows how to order correctly — because now there's an edge in the graph for it to order *by*.\n\n**Key concept:** a lifecycle-ordering bug is almost always a missing edge in the dependency graph wearing a disguise. Add the edge (inject the thing), and the ordering problem disappears on its own — you don't have to solve it, Nest already does.\n\n## Stage 5: shutdown hooks and async hooks\n\nEvery hook in Stage 1 can be `async` — return a `Promise`, and Nest waits for it before moving to the next stage. An `async onModuleInit()` that awaits a database connection genuinely blocks `app.listen()` until it resolves, which is usually exactly what you want (don't accept traffic before you can serve it) but is worth knowing explicitly, since a slow connection now shows up as a slow boot, not a slow first request.\n\nTeardown hooks (`onModuleDestroy`, `beforeApplicationShutdown`, `onApplicationShutdown`) only run on `app.close()` or an OS shutdown signal — **and only if you've opted in**:\n\n```ts\n\u002F\u002F main.ts\nconst app = await NestFactory.create(AppModule);\napp.enableShutdownHooks(); \u002F\u002F without this, SIGTERM\u002FSIGINT skip teardown hooks entirely\nawait app.listen(3000);\n```\n\nForgetting `enableShutdownHooks()` is the most common reason a service that's supposed to flush a queue or close a connection pool on shutdown silently doesn't — the process just exits, and no teardown hook ever runs.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-lifecycle-hook-order\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\n## Edge cases and gotchas\n\n- **Tests that only `compile()`, never `init()`.** `Test.createTestingModule({...}).compile()` builds the module graph but never calls `onModuleInit` or `onApplicationBootstrap`. If a hook-driven side effect doesn't appear to run in a test, check whether the test ever called `.init()` on the result.\n- **Circular dependencies with `forwardRef()`.** Two providers that depend on each other via `forwardRef(() => OtherService)` can't have a clean \"B before A\" order, because there is no clean order — Nest resolves the cycle, but a hook in either one that assumes the other is fully initialized can still race. Prefer breaking the cycle over patching around it.\n- **Global modules aren't a shortcut around ordering.** A module marked `@Global()` is visible everywhere without importing it, but it is still initialized according to the same rules — \"global\" changes visibility, not hook order.\n- **`onApplicationBootstrap` running late in a slow-starting app.** Because it waits for every module's `onModuleInit`, one slow async hook anywhere in the graph delays `onApplicationBootstrap` for the whole app, not just its own module.\n- **Upgrading from 11.x to 12.x with hidden cross-module hook assumptions.** This is the v12-specific case this article opened with. If a test suite starts flaking right after the upgrade, treat it as a real latent bug the version bump exposed, not a Nest regression to work around.\n\n## Best practices\n\n- **Never let a hook in one provider assume another unrelated provider already ran** unless that assumption is backed by a real constructor-injection edge. If it isn't, add the edge.\n- **Use `onApplicationBootstrap` for anything that needs the whole app ready**, not just its own module's dependencies — don't reach for a hack to delay `onModuleInit` work instead.\n- **Call `app.enableShutdownHooks()` in production** if any provider relies on `onModuleDestroy` or `onApplicationShutdown` to flush state or close connections cleanly.\n- **When you upgrade a major Nest version, read the release's breaking-changes list before you read anything else** — a change like this one produces no compile error and no obvious runtime error, only an intermittent one.\n- **If you genuinely need to verify ordering empirically**, log each hook with its provider's name during boot rather than assuming either the \"old\" or \"new\" order from memory — the release notes don't spell out the full new algorithm, so the only way to know your app's actual order is to look at it.\n\n## FAQ\n\n### Does onModuleInit run in constructor order?\n\nOnly where a real dependency edge exists. If `A` injects `B`, `B`'s `onModuleInit` is guaranteed to run first. Between providers with no such edge, there's no guaranteed order, and NestJS 12's release notes confirm that relative order can and did change.\n\n### Why didn't my onModuleDestroy fire when I sent SIGTERM?\n\nAlmost always because `app.enableShutdownHooks()` was never called. Without it, Nest doesn't attach the OS signal listeners that trigger the teardown hooks at all.\n\n### Does an async onModuleInit block app.listen()?\n\nYes. Nest awaits every `onModuleInit` (and then every `onApplicationBootstrap`) before it starts accepting connections, so a slow one delays the whole app's startup, not just the part that's slow.\n\n### Is this different for Fastify vs Express?\n\nNo. Lifecycle hooks fire during module bootstrap, entirely before Nest hands anything to the underlying HTTP adapter, so the behavior is identical regardless of which one you're using.\n\n### Will NestJS 13 reorder hooks again?\n\nThere's no way to know in advance, and that uncertainty is the actual point of this article: code that depends only on guaranteed ordering (real dependency edges) can't be broken by a future reshuffle of the unguaranteed part, because it never relied on it.\n\n## Cheat sheet\n\n| Hook | Fires when | Guaranteed relative to |\n| --- | --- | --- |\n| `onModuleInit()` | Host module's own dependencies constructed | After every injected dependency's `onModuleInit` |\n| `onApplicationBootstrap()` | Every module's `onModuleInit` has finished | After all `onModuleInit` hooks app-wide |\n| `onModuleDestroy()` | `app.close()` or shutdown signal (needs `enableShutdownHooks()`) | Mirrors `onModuleInit`'s dependency order, in reverse |\n| `beforeApplicationShutdown(signal)` | After all `onModuleDestroy` hooks | After every `onModuleDestroy` |\n| `onApplicationShutdown(signal)` | Last hook before process exit | After `beforeApplicationShutdown` |\n| Two providers, no dependency edge | — | **Not guaranteed, by design** — don't rely on it |\n| Opt in to teardown hooks | `app.enableShutdownHooks()` in `main.ts` | Must be called explicitly |\n| Fix an ordering bug | Constructor-inject the provider you actually depend on | Turns an assumption into a real graph edge |\n\n## Key takeaways\n\n- NestJS guarantees hook order along the **dependency graph**: if `A` injects `B`, `B`'s hooks always run first. That rule has never changed and never will, because it's a consequence of constructor injection, not a scheduling choice.\n- It has **never** guaranteed order between providers with no dependency edge — that was always an implementation detail, and NestJS 12's release notes confirm it changed.\n- A hook-ordering bug is a missing dependency edge in disguise. The fix is to inject the thing you depend on, not to chase the new implicit order.\n- Teardown hooks need `app.enableShutdownHooks()` to run at all — a very common silent gap.\n\nThe report service from the top of this article didn't start being wrong the day it was upgraded to Nest 12 — it was wrong from the day someone reached for a module-scope variable instead of a constructor parameter. The version bump just stopped covering for it. Once you're reading \"hook order\" as \"the dependency graph, and nothing else,\" that whole bug class stops being a mystery you debug after an upgrade and starts being something you can rule out on sight.\n\nHas a major-version upgrade ever exposed an ordering assumption you didn't know your code was making? Drop it in the comments.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [NestJS Dependency Injection Explained (with Cheat Sheet)](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-dependency-injection-explained-with-cheat-sheet-4bem)\n- [NestJS Testing Module: Provider Overrides (with Cheat Sheet)](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-testing-module-provider-overrides-with-cheat-sheet-1o79)\n- [NestJS Module Encapsulation Explained (with Cheat Sheet)](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fnestjs-module-encapsulation-explained-with-cheat-sheet-10mo)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":96,"canonical":468,"description":98},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fnestjs-weekly-lifecycle-hook-order","01a1358a-158e-70df-8178-6a99865a9359",{"name":471,"part":472,"total":472,"items":473},"NestJS Deep Dive",7,[474,478,482,487,491,495,499],{"slug":475,"title":476,"publishedAt":477,"readingMinutes":84},"nestjs-weekly-request-lifecycle","NestJS Request Lifecycle Explained (with Cheat Sheet)","2026-08-28T17:01:41.941Z",{"slug":479,"title":480,"publishedAt":481,"readingMinutes":80},"nestjs-weekly-dependency-injection-providers-scopes","NestJS Dependency Injection Explained (with Cheat Sheet)","2026-09-13T08:40:18.434Z",{"slug":483,"title":484,"publishedAt":485,"readingMinutes":486},"nestjs-weekly-module-encapsulation-exports","NestJS Module Encapsulation Explained (with Cheat Sheet)","2026-09-13T08:40:50.070Z",15,{"slug":488,"title":489,"publishedAt":490,"readingMinutes":84},"nestjs-weekly-testing-provider-overrides","NestJS Testing Module: Provider Overrides (with Cheat Sheet)","2026-09-18T17:23:03.842Z",{"slug":492,"title":493,"publishedAt":494,"readingMinutes":80},"nestjs-weekly-guards-canactivate-reflector","NestJS Guards: CanActivate, ExecutionContext & Reflector","2026-09-26T12:25:39.001Z",{"slug":496,"title":497,"publishedAt":498,"readingMinutes":80},"nestjs-weekly-standard-schema-validation","NestJS 12: Standard Schema Validation Without class-validator","2026-10-02T06:11:02.679Z",{"slug":95,"title":96,"publishedAt":100,"readingMinutes":84},{"id":501,"locked":18},"01a1358a-161d-7097-806f-61d28a256644",[503],{"id":504,"slug":95,"title":96,"_count":505},"01a1358a-1747-719b-ad61-e15a0f4619f1",{"questions":506},8,[508],{"locale":13,"slug":95},{"id":504,"slug":95,"title":96,"_count":510,"questionCount":506},{"questions":506},[],{"items":513,"meta":615},[514,525,540,555,570,585,600],{"id":94,"slug":95,"title":96,"subtitle":97,"excerpt":98,"coverUrl":99,"locale":13,"readingMinutes":84,"publishedAt":100,"viewCount":68,"likeCount":19,"commentCount":19,"author":515,"vertical":516,"topic":517,"tags":518,"_count":523,"playground":524,"hasQuiz":17,"hasPlayground":17},{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[519,520,521,522],{"slug":109,"name":110,"color":97},{"slug":70,"name":71,"color":97},{"slug":82,"name":83,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":95},{"id":526,"slug":496,"title":497,"subtitle":97,"excerpt":527,"coverUrl":528,"locale":13,"readingMinutes":80,"publishedAt":498,"viewCount":529,"likeCount":19,"commentCount":19,"author":530,"vertical":531,"topic":532,"tags":533,"_count":538,"playground":539,"hasQuiz":17,"hasPlayground":17},"01a0f0ef-ccc8-73e8-9b53-e6ce4c7fe9ff","NestJS 12 adds Standard Schema validation for @Body(), @Query(), and @Param() — validate with Zod or Valibot, no class-validator DTO. Cheat sheet included.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-standard-schema-validation.png",445,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[534,535,536,537],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":496},{"id":541,"slug":492,"title":493,"subtitle":97,"excerpt":542,"coverUrl":543,"locale":13,"readingMinutes":80,"publishedAt":494,"viewCount":544,"likeCount":19,"commentCount":19,"author":545,"vertical":546,"topic":547,"tags":548,"_count":553,"playground":554,"hasQuiz":17,"hasPlayground":17},"01a0ddae-48ac-73c6-bdee-1420cdfb17c2","A complete guide to NestJS guards: how CanActivate, ExecutionContext, and the Reflector decide who reaches your route handler, with a cheat sheet.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-guards-canactivate-reflector.png",567,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[549,550,551,552],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":492},{"id":556,"slug":488,"title":489,"subtitle":97,"excerpt":557,"coverUrl":558,"locale":13,"readingMinutes":84,"publishedAt":490,"viewCount":559,"likeCount":19,"commentCount":19,"author":560,"vertical":561,"topic":562,"tags":563,"_count":568,"playground":569,"hasQuiz":17,"hasPlayground":17},"01a0a630-c856-72d3-bae5-3339e38a8564","A complete guide to NestJS's testing module: how overrideProvider really wires mocks, why get() fails on scoped providers, and using useMocker.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-testing-provider-overrides.png",604,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[564,565,566,567],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":488},{"id":571,"slug":483,"title":484,"subtitle":97,"excerpt":572,"coverUrl":573,"locale":13,"readingMinutes":486,"publishedAt":485,"viewCount":574,"likeCount":19,"commentCount":19,"author":575,"vertical":576,"topic":577,"tags":578,"_count":583,"playground":584,"hasQuiz":17,"hasPlayground":17},"01a0821b-4733-762d-bf96-b68c7eb140c0","How NestJS module boundaries actually work: what exports really cross, why @Global() isn't a shortcut, and how forwardRef breaks circular module dependencies.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-module-encapsulation-exports.png",537,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[579,580,581,582],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":483},{"id":586,"slug":479,"title":480,"subtitle":97,"excerpt":587,"coverUrl":588,"locale":13,"readingMinutes":80,"publishedAt":481,"viewCount":589,"likeCount":19,"commentCount":19,"author":590,"vertical":591,"topic":592,"tags":593,"_count":598,"playground":599,"hasQuiz":17,"hasPlayground":17},"01a05e06-53b7-7415-9cd3-05504882514b","How the NestJS DI container resolves providers: tokens, module scope, singleton vs request vs transient, and why 'singleton' can still mean two instances.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-dependency-injection-providers-scopes.png",586,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[594,595,596,597],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":479},{"id":601,"slug":475,"title":476,"subtitle":97,"excerpt":602,"coverUrl":603,"locale":13,"readingMinutes":84,"publishedAt":477,"viewCount":604,"likeCount":19,"commentCount":19,"author":605,"vertical":606,"topic":607,"tags":608,"_count":613,"playground":614,"hasQuiz":17,"hasPlayground":17},"01a03cdb-22ef-748a-b555-8071c7a905e6","A complete guide to the NestJS request lifecycle: the exact order middleware, guards, interceptors, pipes, and filters run, and why it matters.","\u002Fmedia\u002Fcovers\u002Fnestjs-weekly-request-lifecycle.png",778,{"id":103,"name":104,"username":105,"avatarUrl":97,"headline":106},{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":109,"name":110},[609,610,611,612],{"slug":109,"name":110,"color":97},{"slug":82,"name":83,"color":97},{"slug":70,"name":71,"color":97},{"slug":58,"name":59,"color":97},{"assessments":117},{"slug":475},{"page":117,"perPage":472,"total":472,"totalPages":117},"\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F metrics.module.ts\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">let\u003C\u002Fspan> requestCounterSeed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n\n@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">MetricsService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    requestCounterSeed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">loadSeedFromDisk\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F populates module-scope state\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F reports.service.ts\n\u003C\u002Fspan>@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ReportsService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">private\u003C\u002Fspan> baseline \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>baseline \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> requestCounterSeed; \u003Cspan class=\"shj-cmnt\">\u002F\u002F reads it back — no DI involved\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-kwd\">import\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-class\">OnApplicationBootstrap\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-class\">OnModuleDestroy\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-class\">BeforeApplicationShutdown\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n  \u003Cspan class=\"shj-class\">OnApplicationShutdown\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">,\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">from\u003C\u002Fspan> \u003Cspan class=\"shj-str\">'@nestjs\u002Fcommon'\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003Cdiv>13\u003C\u002Fdiv>\u003Cdiv>14\u003C\u002Fdiv>\u003Cdiv>15\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">CacheService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'CacheService ready'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OrdersService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">constructor\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">private\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">readonly\u003C\u002Fspan> cache\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">CacheService\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    console\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">log\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-str\">'OrdersService ready'\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F always logs after CacheService\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">ReportsService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">private\u003C\u002Fspan> baseline \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-kwd\">constructor\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">private\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">readonly\u003C\u002Fspan> metrics\u003Cspan class=\"shj-oper\">:\u003C\u002Fspan> \u003Cspan class=\"shj-class\">MetricsService\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>baseline \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>metrics\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">getSeed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F guaranteed: metrics already initialized\n\u003C\u002Fspan>  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003Cdiv>5\u003C\u002Fdiv>\u003Cdiv>6\u003C\u002Fdiv>\u003Cdiv>7\u003C\u002Fdiv>\u003Cdiv>8\u003C\u002Fdiv>\u003Cdiv>9\u003C\u002Fdiv>\u003Cdiv>10\u003C\u002Fdiv>\u003Cdiv>11\u003C\u002Fdiv>\u003Cdiv>12\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">@\u003Cspan class=\"shj-class\">Injectable\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>\n\u003Cspan class=\"shj-kwd\">export\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">class\u003C\u002Fspan> \u003Cspan class=\"shj-class\">MetricsService\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">implements\u003C\u002Fspan> \u003Cspan class=\"shj-class\">OnModuleInit\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n  \u003Cspan class=\"shj-kwd\">private\u003C\u002Fspan> seed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-num\">0\u003C\u002Fspan>;\n\n  \u003Cspan class=\"shj-func\">onModuleInit\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>seed \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-func\">loadSeedFromDisk\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\n  \u003Cspan class=\"shj-func\">getSeed\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan> \u003Cspan class=\"shj-bracket\">{\u003C\u002Fspan>\n    \u003Cspan class=\"shj-kwd\">return\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">this\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>seed;\n  \u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\n\u003Cspan class=\"shj-bracket\">}\u003C\u002Fspan>\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>","\u003Cdiv class=\"shj shj-lang-ts shj-multiline\" data-lang=\"ts\">\u003Cdiv class=\"shj-scroll\">\u003Cdiv class=\"shj-numbers\">\u003Cdiv>1\u003C\u002Fdiv>\u003Cdiv>2\u003C\u002Fdiv>\u003Cdiv>3\u003C\u002Fdiv>\u003Cdiv>4\u003C\u002Fdiv>\u003C\u002Fdiv>\u003Cdiv class=\"shj-code\">\u003Cspan class=\"shj-cmnt\">\u002F\u002F main.ts\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">const\u003C\u002Fspan> app \u003Cspan class=\"shj-oper\">=\u003C\u002Fspan> \u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> \u003Cspan class=\"shj-class\">NestFactory\u003C\u002Fspan>\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">create\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-class\">AppModule\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\napp\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">enableShutdownHooks\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>; \u003Cspan class=\"shj-cmnt\">\u002F\u002F without this, SIGTERM\u002FSIGINT skip teardown hooks entirely\n\u003C\u002Fspan>\u003Cspan class=\"shj-kwd\">await\u003C\u002Fspan> app\u003Cspan class=\"shj-oper\">.\u003C\u002Fspan>\u003Cspan class=\"shj-func\">listen\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">(\u003C\u002Fspan>\u003Cspan class=\"shj-num\">3000\u003C\u002Fspan>\u003Cspan class=\"shj-bracket\">)\u003C\u002Fspan>;\u003C\u002Fdiv>\u003C\u002Fdiv>\u003C\u002Fdiv>",{"locked":18,"total":19,"comments":624},[]]