<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Bhargava Chary  on Medium]]></title>
        <description><![CDATA[Stories by Bhargava Chary  on Medium]]></description>
        <link>https://medium.com/@bhargavacharyb?source=rss-b22d801ae376------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*InkKLzkl4BsEOblENuQa_g.jpeg</url>
            <title>Stories by Bhargava Chary  on Medium</title>
            <link>https://medium.com/@bhargavacharyb?source=rss-b22d801ae376------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 15 Nov 2026 09:01:12 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@bhargavacharyb/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[NestJS Dependency Injection: The “Ghost Instance” Trap and How to Master Tokens]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-dependency-injection-the-ghost-instance-trap-and-how-to-master-tokens-f349fd0bbcb7?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/f349fd0bbcb7</guid>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[useclass-vs-useexisting]]></category>
            <category><![CDATA[provider]]></category>
            <category><![CDATA[dependency-injection]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Sun, 20 Sep 2026 02:31:01 GMT</pubDate>
            <atom:updated>2026-09-20T02:31:01.582Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*62TRYPiKpt1QiE_qylX83g.png" /></figure><p>NestJS dependency injection feels like sheer magic — until your application’s state quietly desynchronizes in production.</p><p>When you start learning the framework, DI looks deceptively simple. You write a standard constructor, and NestJS somehow wires up your services automatically:</p><pre>constructor(<br>  private readonly emailService: EmailService,<br>) {}</pre><p>For class-based dependencies, NestJS uses runtime metadata emitted by TypeScript to resolve the class provider. Simple.</p><p>Then, you open an enterprise codebase and encounter something like this:</p><pre>constructor(<br>  @Inject(&#39;EMAIL_SERVICE&#39;)<br>  private readonly emailService: EmailService,<br>) {}</pre><p>Suddenly, the illusion breaks. Why does @Inject() exist if TypeScript already declared the type? Why would someone use a string token? And most dangerously: why does switching between useClass and useExisting mean the difference between a unified singleton and two competing instances in memory?</p><p>Misunderstanding this mapping can lead to subtle bugs: duplicated stateful services, multiple database pools, inconsistent caches, or mocks that fail to resolve during tests.</p><p>Here is the exact mental model you need to master types, tokens, and providers in NestJS.</p><h3>Act I: Type vs. Token vs. Provider</h3><p>To understand advanced NestJS patterns, you must separate what TypeScript does from what NestJS does. Beginners tend to merge three distinct concepts into one because they often share the exact same name:</p><ul><li><strong>The Type:</strong> What TypeScript uses for compile-time type checking.</li><li><strong>The Token:</strong> The runtime identifier NestJS uses to locate a provider.</li><li><strong>The Provider:</strong> The registration that tells NestJS how to resolve that token — through a class, existing provider, value, or factory.</li></ul><p>When you use standard class-based injection, the class can serve both as the TypeScript type and as the NestJS runtime token:</p><pre>CustomLoggerService<br>       ↓<br>  NestJS Token<br>       +<br> TypeScript Type</pre><p>But when you use a custom string token, those responsibilities split. This is the single most important visual to remember:</p><pre>constructor(<br>  @Inject(&#39;logger&#39;)<br>  private readonly logger: CustomLoggerService,<br>)</pre><pre>@Inject(&#39;logger&#39;)<br>       ↓<br>  NestJS Token (Finds the provider in memory)<br><br>: CustomLoggerService<br>       ↓<br>  TypeScript Type (Gives you IDE autocomplete)</pre><p>The token finds the box; the type tells you what is inside it.</p><h3>Act II: Why TypeScript Interfaces Disappear</h3><p>The most common question senior engineers hear from NestJS newcomers is: <em>“I want to decouple my code using an interface. Why can’t I just inject </em>IMailService<em>?&quot;</em></p><p>You try this:</p><pre>// Won&#39;t work at runtime<br>constructor(<br>  private readonly mailService: IMailService,<br>) {}</pre><p>NestJS crashes on startup with: Nest can&#39;t resolve dependencies of the UserService (?).</p><p>Here is the secret: <strong>TypeScript interfaces do not exist in JavaScript.</strong></p><p>When the TypeScript compiler (tsc) compiles your files into raw JavaScript, every single interface and pure type annotation is erased. Because NestJS relies on runtime metadata to figure out what to inject, interfaces leave no trace for NestJS to follow.</p><p>Classes, however, are real JavaScript constructor functions that survive compilation.</p><pre>class CustomLoggerService<br>        ↓<br>exists at runtime<br>        ↓<br>can be used as a DI token<br><br>interface IMailer<br>        ↓<br>erased during compilation<br>        ↓<br>cannot be used as a runtime DI token</pre><p>When you program against abstractions (interfaces), NestJS has nothing to inspect at runtime. You <strong>must</strong> provide an explicit runtime token using @Inject(&#39;MAIL_SERVICE&#39;).</p><h3>Act III: The “Ghost Instance” Trap (useClass vs. useExisting)</h3><p>Once you start using custom tokens, you need to tell NestJS how to resolve them in your modules. This is where developers accidentally introduce critical bugs.</p><p>Consider this innocent-looking module configuration:</p><pre>@Module({<br>  providers: [<br>    CustomLoggerService,<br>    {<br>      provide: &#39;APP_LOGGER&#39;,<br>      useClass: CustomLoggerService, // The trap<br>    },<br>  ],<br>})<br>export class CommonModule {}</pre><p>It looks like you simply gave CustomLoggerService an alternative name (&#39;APP_LOGGER&#39;). But for the sake of this article, I will call this the <strong>Ghost Instance Trap</strong>.</p><p>By registering the same class under two different tokens, you create two separate provider registrations, which resolve to two independent singleton instances under the default scope:</p><ul><li><strong>Token 1 (</strong>CustomLoggerService<strong>):</strong> NestJS runs new CustomLoggerService() — → <strong>Instance A</strong></li><li><strong>Token 2 (</strong>&#39;APP_LOGGER&#39;<strong>):</strong> NestJS runs new CustomLoggerService() — → <strong>Instance B</strong></li></ul><p>If your logger buffers logs in an internal array, maintains a queue, or tracks rate limits, those two tokens will never share state.</p><h3>The Fix: The Nametag Alias (useExisting)</h3><p>If your goal is to provide a clean alias for an already registered provider, you must use useExisting. This tells NestJS to bypass instantiation and simply point the new token to an existing singleton.</p><pre>@Module({<br>  providers: [<br>    CustomLoggerService,<br>    {<br>      provide: &#39;APP_LOGGER&#39;,<br>      useExisting: CustomLoggerService, // Points to the existing singleton<br>    },<br>  ],<br>})<br>export class CommonModule {}</pre><p>Think of DI tokens as nametags on a box.</p><ul><li>useClass<strong> (Two Boxes):</strong> Creates Box A for the class token, and Box B for the string token.</li><li>useExisting<strong> (One Box, Two Nametags):</strong> Creates Box A, and slaps both nametags onto it.</li></ul><p>Now, whether a consumer injects via the class token or the string token, logger1 === logger2 remains strictly true.</p><h3>Act IV: Clean Architectural Abstraction</h3><p>Custom tokens might feel like boilerplate for simple CRUD apps, but they are the backbone of scalable architecture.</p><p>Imagine an application that relies heavily on a logging service. Instead of coupling 50 controllers directly to WinstonLoggerService, consumers depend only on an abstraction token:</p><pre>constructor(<br>  @Inject(&#39;LOGGER&#39;) private readonly logger: LoggerInterface<br>) {}</pre><p>In your module, you map the token to the specific implementation:</p><pre>{<br>  provide: &#39;LOGGER&#39;,<br>  useClass: WinstonLoggerService,<br>}</pre><p>If you ever decide to migrate to Google Cloud Logging or AWS CloudWatch, you don’t touch your 50 controllers. You simply swap the implementation in the module:</p><pre>{<br>  provide: &#39;LOGGER&#39;,<br>  useClass: CloudLoggerService, <br>}</pre><h3>The Ultimate Provider Cheat Sheet</h3><p>To keep your codebases predictable, here is a quick reference table of the provider patterns you will encounter, and exactly when to use them:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*jzmP1VUSxswjoMd0DE-o4g.png" /></figure><h3>The Most Important Takeaway</h3><p>Finally, never forget the exports array. A common beginner hurdle is configuring the perfect token in a CommonModule, only to get dependency errors in a UserModule.</p><p>A providers array dictates what a module registers internally. The exports array dictates what a module exposes to the rest of the application. If you create a custom token like &#39;LOGGER&#39;, you must put &#39;LOGGER&#39; in the exports array for other modules to use it.</p><p>When you sit down to read or write NestJS code, always ask yourself three questions:</p><ul><li><em>What is the token?</em></li><li><em>What is the implementation?</em></li><li><em>Is this a separate provider registration (</em><em>useClass), or an alias to an existing provider (</em><em>useExisting)?</em></li></ul><p>Once those questions become second nature, the magic of NestJS disappears, leaving behind a highly predictable, fiercely powerful architecture.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f349fd0bbcb7" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Avoiding Fat Services in NestJS: A Practical Guide to Lean Architecture]]></title>
            <link>https://bhargavacharyb.medium.com/avoiding-fat-services-in-nestjs-a-practical-guide-to-lean-architecture-d7a0ec6fcdd5?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/d7a0ec6fcdd5</guid>
            <category><![CDATA[nestjs-design-patterns]]></category>
            <category><![CDATA[best-practices]]></category>
            <category><![CDATA[fat-services-nestjs]]></category>
            <category><![CDATA[nestjs]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Mon, 09 Feb 2026 14:54:15 GMT</pubDate>
            <atom:updated>2026-02-09T14:54:15.408Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>As your NestJS application grows, it’s easy for service classes to become bloated.</blockquote><p>What starts as a neat UserService handling a couple of database queries slowly turns into a class that does everything: database access, business rules, integrations, caching, authorization checks, and even request validation.</p><p>These “fat services” are one of the most common architectural anti-patterns in NestJS applications. They make code harder to read, harder to test, and harder to evolve. Over time, they introduce hidden coupling that turns even small changes into risky refactors.</p><p>A simple self-check helps here:</p><p>Ask yourself: “<strong>What is this service’s <em>primary</em> job?</strong>” If the answer includes “<strong>and</strong>,” it’s time to refactor.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*eff9zZeu_FUP7bU-DDBBVQ.png" /></figure><p>This article explains how to recognize fat services, why they are problematic, and — most importantly — how to structure your NestJS code to avoid them in the first place.</p><h3>What Do We Mean by a Fat Service?</h3><p>A fat service is a class that handles too many responsibilities.</p><p>Typical warning signs include:</p><ul><li>Business logic mixed with database queries or HTTP calls</li><li>Complex conditional flows inside service methods</li><li>Authorization checks embedded directly in business logic</li><li>Validation logic scattered throughout services</li><li>Hard-coded integration details</li><li>Large methods that are difficult to reason about or test</li></ul><p>Fat services usually don’t appear overnight. They grow incrementally as new features are added, deadlines approach, and “just one more condition” sneaks in. Eventually, a single class becomes the center of gravity for unrelated concerns.</p><h3>Why Fat Services Are a Problem</h3><p>Fat services create a number of issues as your application grows:</p><p><strong>Hard to Test: </strong>Unit tests become difficult because the class does many different things. You end up mocking everything, and tests become fragile or ineffective.</p><p><strong>Tight Coupling:</strong> When one class is responsible for data access, business logic, and integration logic, any change in one area can break unrelated features.</p><p><strong>Poor Readability:</strong> New developers (or you six months later) have to wade through lots of unrelated logic to understand what the class is supposed to do.</p><p><strong>Difficult refactoring:</strong> When a class has many reasons to change, modifying one behavior risks breaking others.</p><h3>The Golden Rule: Single Responsibility Principle (SRP)</h3><p>The Single Responsibility Principle states that a class should have one reason to change.</p><p>In NestJS terms, this means:</p><ul><li>Services should coordinate work, not do everything</li><li>Business rules should live in focused, testable units</li><li>Infrastructure concerns should be isolated</li></ul><p>If a service feels hard to explain in one sentence, it probably has too many responsibilities.</p><h3>Separating Concerns: Practical Tips</h3><p>Here are practical guidelines you can apply in your NestJS projects.</p><h4>1. Keep Database Access in Repositories</h4><p><strong>The Problem</strong>: Your service is littered with raw SQL or ORM-specific queries, tightly coupling business logic to your database framework.</p><pre>@Injectable()<br>export class UserOrderService {<br>  constructor(<br>    @InjectRepository(User) <br>    private readonly userRepo: Repository&lt;User&gt;,<br>    <br>    @InjectRepository(Order)<br>    private readonly orderRepo: Repository&lt;Order&gt;<br>  ) {}<br><br>  async findUserAndOrders(userId: string) {<br>    const user = await this.userRepo.findOne(userId);<br>    const orders = await this.orderRepo.find({ userId });<br>    return { user, orders };<br>  }<br>}</pre><p><strong>The Solution</strong>: Extract data access into a dedicated repository layer. This creates a clean abstraction, making your service easier to test and your database technology easier to swap.</p><pre>// Repositories<br>export interface UserRepository {<br>  findById(id: string): Promise&lt;User | null&gt;;<br>  findByEmail(email: string): Promise&lt;User | null&gt;;<br>  save(user: User): Promise&lt;User&gt;;<br>}<br><br>export interface OrderRepository {<br>  findByUserId(userId: string): Promise&lt;Order[]&gt;;<br>  save(order: Order): Promise&lt;Order&gt;;<br>}<br><br></pre><pre>// Adaptors for Repositories<br>@Injectable()<br>export class TypeOrmUserRepository implements UserRepository {<br>  constructor(<br>    @InjectRepository(User) <br>    private readonly repo: Repository&lt;User&gt;<br>  ) {}<br><br>  findById(id: string): Promise&lt;User | null&gt; {<br>    return this.repo.findOneBy({ id });<br>  }<br>  <br>}<br><br>@Injectable()<br>export class TypeOrmOrderRepository implements OrderRepository {<br>  constructor(<br>    @InjectRepository(Order)<br>    private readonly repo: Repository&lt;Order&gt;<br>  ) {}<br><br>  findByUserId(userId: string): Promise&lt;Order[]&gt; {<br>    return this.repo.find({ where: { userId } });<br>  }<br>}</pre><pre>// Service<br>@Injectable()<br>export class UserOrderService {<br>  constructor(<br>    private readonly userRepository: UserRepository,<br>    private readonly orderRepository: OrderRepository<br>  ) {}<br><br>  async findUserAndOrders(userId: string) {<br>    const [user, orders] = await Promise.all([<br>      this.userRepository.findById(userId),<br>      this.orderRepository.findByUserId(userId)<br>    ]);<br>    <br>    if (!user) throw new NotFoundException(&#39;User not found&#39;);<br>    <br>    return { user, orders };<br>  }<br>}</pre><pre>// Module<br>@Module({<br>  imports: [<br>    TypeOrmModule.forFeature([User, Order])<br>  ],<br>  providers: [<br>    // Bind interfaces to implementations<br>    {<br>      provide: UserRepository,<br>      useClass: TypeOrmUserRepository<br>    },<br>    {<br>      provide: OrderRepository,<br>      useClass: TypeOrmOrderRepository<br>    },<br>    UserOrderService<br>  ],<br>  exports: [UserOrderService]<br>})<br>export class UserOrderModule {}</pre><p><strong>Benefit</strong>: Your OrderService is now immune to changes in your ORM syntax. Testing is trivial—just mock the OrderRepository interface.</p><h4>2. Offload Complex Business Logic to Domain Services</h4><p><strong>The Problem</strong>: Complex calculation or validation logic is embedded directly in your main service, obscuring its primary flow.</p><pre>@Injectable()<br>export class OrderService {<br>  async calculateOrderTotal(order: Order) {<br>    let total = 0;<br>    for (const item of order.items) {<br>      total += item.unitPrice * item.quantity;<br>      if (item.isTaxable) {<br>        total += item.unitPrice * item.quantity * 0.08; // Tax logic<br>      }<br>    }<br>    if (order.customer.isVIP) { // Discount logic<br>      total *= 0.9;<br>    }<br>    return total;<br>  }<br>}</pre><p><strong>The Solution</strong>: Extract intricate business rules into stateless, pure Domain Service classes. These are simple TypeScript classes (not NestJS @Injectable() services) that hold core business logic.</p><pre>export class OrderCalculationService {<br>  calculateSubtotal(items: OrderItem[]): number {<br>    return items.reduce((sum, item) =&gt; sum + (item.unitPrice * item.quantity), 0);<br>  }<br><br>  calculateTax(subtotal: number, taxableItems: OrderItem[]): number {<br>    const taxableAmount = taxableItems.reduce((sum, item) =&gt; sum + (item.unitPrice * item.quantity), 0);<br>    return taxableAmount * 0.08;<br>  }<br><br>  applyDiscount(total: number, customer: Customer): number {<br>    return customer.isVIP ? total * 0.9 : total;<br>  }<br>}<br><br>@Injectable()<br>export class OrderService {<br>  constructor(private calculationService: OrderCalculationService) {}<br><br>  async calculateOrderTotal(order: Order) {<br>    const subtotal = this.calculationService.calculateSubtotal(order.items);<br>    const tax = this.calculationService.calculateTax(subtotal, order.items.filter(i =&gt; i.isTaxable));<br>    const total = subtotal + tax;<br>    return this.calculationService.applyDiscount(total, order.customer);<br>  }<br>}</pre><p><strong>Benefit</strong>: The business rules are centralized, unit-tested in complete isolation, and completely independent of NestJS, making them portable and crystal clear.</p><h4>3. Secure Endpoints with Guards, Not Inline Checks</h4><p><strong>The Problem</strong>: Authorization logic like if (user.role !== &#39;admin&#39;) pollutes the beginning of every service method.</p><pre>@Injectable()<br>export class ProjectService {<br>  async deleteProject(projectId: string, user: User) {<br>    if (user.role !== &#39;admin&#39;) {<br>      throw new ForbiddenException(&#39;Only admins can delete projects&#39;);<br>    }<br>    // ... your business logic<br>  }<br>}</pre><p><strong>The Solution</strong>: Use NestJS Guards. They run before a request reaches your service, handling authentication and authorization declaratively.</p><pre>import { Injectable, CanActivate, ExecutionContext } from &#39;@nestjs/common&#39;;<br><br>@Injectable()<br>export class RolesGuard implements CanActivate {<br>  constructor(private reflector: Reflector) {}<br><br>  canActivate(context: ExecutionContext): boolean {<br>    const requiredRoles = this.reflector.get&lt;string[]&gt;(&#39;roles&#39;, context.getHandler());<br>    if (!requiredRoles) return true;<br><br>    const request = context.switchToHttp().getRequest();<br>    const user = request.user;<br>    return requiredRoles.some((role) =&gt; user.roles?.includes(role));<br>  }<br>}<br><br>@Injectable()<br>export class ProjectService {<br>  async deleteProject(projectId: string) {<br>    // your business logic<br>  }<br>}<br><br>@Controller(&#39;projects&#39;)<br>@UseGuards(RolesGuard)<br>export class ProjectController {<br>  @Delete(&#39;:id&#39;)<br>  @SetMetadata(&#39;roles&#39;, [&#39;admin&#39;]) <br>  deleteProject(@Param(&#39;id&#39;) projectId: string) {<br>    return this.projectService.deleteProject(projectId);<br>  }<br>}</pre><p><strong>Benefit</strong>: Security logic is centralized, reusable, and your service methods become pure business logic.</p><h4>4. Enforce Validation with Pipes, Not Service Logic</h4><p><strong>The Problem</strong>: Service methods are cluttered with validation boilerplate for checking string formats, number ranges, or business rules.</p><pre>@Injectable()<br>export class UserService {<br>  async createUser(createUserDto: any) {<br>    if (!createUserDto.email.includes(&#39;@&#39;)) {<br>      throw new BadRequestException(&#39;Invalid email&#39;);<br>    }<br>    if (createUserDto.password.length &lt; 8) {<br>      throw new BadRequestException(&#39;Password too short&#39;);<br>    }<br>    // ... Business logic<br>  }<br>}</pre><p><strong>The Solution</strong>: Use NestJS Pipes. Use the built-in ValidationPipe with class-validator for DTOs, and create custom pipes for complex business validations.</p><pre>import { IsEmail, IsString, MinLength } from &#39;class-validator&#39;;<br><br>export class CreateUserDto {<br>  @IsEmail()<br>  email: string;<br><br>  @IsString()<br>  @MinLength(8)<br>  password: string;<br>}<br><br>// The service<br>@Injectable()<br>export class UserService {<br>  async createUser(createUserDto: CreateUserDto) { // Type-safe and pre-validated<br>    // business logic here<br>  }<br>}<br><br>@Post()<br>@UsePipes(new ValidationPipe())<br>createUser(@Body(ValidationPipe) createUserDto: CreateUserDto) {<br>  return this.userService.createUser(createUserDto);<br>}</pre><p><strong>Benefit</strong>: Validation is consistent, reusable, and keeps your services clean and focused on their core purpose.</p><h3>5. Manage Cross-Cutting Concerns with Interceptors</h3><p><strong>The Problem</strong>: Repetitive tasks like logging, standardizing responses, or error handling are duplicated across services.</p><p><strong>The Solution</strong>: Use NestJS Interceptors to wrap request/response cycles elegantly.</p><pre>import { CallHandler, ExecutionContext, NestInterceptor } from &#39;@nestjs/common&#39;;<br>import { map } from &#39;rxjs/operators&#39;;<br><br>export class TransformInterceptor implements NestInterceptor {<br>  intercept(context: ExecutionContext, next: CallHandler) {<br>    const start = Date.now();<br>    const req = context.switchToHttp().getRequest();<br><br>    console.log(`Incoming ${req.method} request to ${req.url}`);<br><br>    return next.handle().pipe(<br>      map(data =&gt; ({<br>        success: true,<br>        duration: `${Date.now() - start}ms`,<br>        data,<br>      })),<br>    );<br>  }<br>}</pre><h3>6. Offload Heavy Work to Background Jobs</h3><p>Services should not:</p><ul><li>Send emails</li><li>Process large files</li><li>Call slow external APIs</li></ul><p>Use background jobs (Bull or BullMQ) to keep HTTP requests fast and services focused on orchestration.</p><p>This allows services to describe <em>what should happen</em> instead of <em>how and when it happens</em>.</p><h3>Conclusion</h3><p>Avoiding fat services is not just about splitting code — it’s about organizing your application in a way that:</p><ul><li>Makes responsibilities explicit</li><li>Encourages reuse</li><li>Simplifies testing</li><li>Improves maintainability</li><li>Reduces coupling</li></ul><p>By thinking in terms of <strong>domain logic</strong>, <strong>infrastructure</strong>, and <strong>cross-cutting concerns</strong>, you’ll build NestJS applications that feel clean even as requirements grow.</p><p>If you found this article useful, please consider leaving a clap (👏) and a comment.</p><p>Happy coding! 🎉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d7a0ec6fcdd5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[NestJS Clean Architecture Explained: A Beginner-Friendly Guide to Scalable Backends]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-clean-architecture-explained-a-beginner-friendly-guide-to-scalable-backends-91084187b3f5?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/91084187b3f5</guid>
            <category><![CDATA[domain-driven-design]]></category>
            <category><![CDATA[clean-architecture]]></category>
            <category><![CDATA[architecture-design]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[software-architecture]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Mon, 09 Feb 2026 04:01:00 GMT</pubDate>
            <atom:updated>2026-02-09T04:01:00.952Z</atom:updated>
            <content:encoded><![CDATA[<p>As NestJS applications grow, many teams run into the same problem.</p><p>At the beginning, everything feels simple. Controllers call services, services talk to repositories, and new features are easy to add. But as time goes on, requirements change. Business rules become more complex. New integrations are added. Refactoring starts to feel risky.</p><p>At that point, the issue is usually <strong>not NestJS</strong>. The real problem is <strong>architecture</strong>.</p><p>This article shows you Clean Architecture in simple, practical terms. No heavy theory. Just clear structure to keep your code clean, testable, and flexible as it grows.</p><h3>What Is Clean Architecture?</h3><p>Clean Architecture is about dependency direction. Instead of organizing code by technical layers (controllers, services, repositories), you organize by business purpose.</p><p>Think of your application as circles:</p><ul><li><strong>Innermost circle</strong>: Core business rules (“an order can only be paid if created”)</li><li><strong>Middle circles</strong>: Application logic (steps to complete tasks)</li><li><strong>Outermost circles</strong>: Technical details (databases, APIs, web frameworks)</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*UjxjMQku_0RfVl4f.jpeg" /></figure><blockquote>Dependencies always point inward.</blockquote><p>Your business logic doesn’t depend on NestJS, databases, or external services.</p><p>NestJS already gives us a great structure. Clean Architecture helps define <strong>how responsibilities should flow inside that structure</strong>.</p><h3>The Four Layers: NestJS-Friendly Version</h3><h4>1. The Domain Layer (The Heart)</h4><p>This is the innermost layer. It contains your Entities and Domain Business Rules. These are pure TypeScript classes with no dependencies on any framework, library, or outer layer.</p><pre>export class Order {<br>  constructor(<br>    public readonly id: string,<br>    private items: OrderItem[],<br>    private status: &#39;CREATED&#39; | &#39;PAID&#39; | &#39;SHIPPED&#39;<br>  ) {}<br><br>  markAsPaid() {<br>    if (this.status !== &#39;CREATED&#39;) {<br>      throw new Error(&#39;Only new orders can be paid&#39;);<br>    }<br>    this.status = &#39;PAID&#39;;<br>  }<br>}</pre><h4>2. The Application Layer (TheOrchestrator)</h4><p>This layer contains Use Cases (or Application Services). Each use case orchestrates a single application-specific task, coordinating the domain and infrastructure layers to fulfill a user’s intent. This layer depends on the Domain Layer.</p><pre>export class PayOrderUseCase {<br>  constructor(<br>    private readonly orderRepository: OrderRepository,<br>    private readonly paymentGateway: PaymentGateway<br>  ) {}<br><br>  async execute(orderId: string) {<br>    const order = await this.orderRepository.findById(orderId);<br>    if (!order) {<br>      throw new Error(&#39;Order not found&#39;);<br>    }<br><br>    await this.paymentGateway.charge(order);<br>    order.markAsPaid();<br><br>    await this.orderRepository.save(order);<br>  }<br>}</pre><h4>3. The Infrastructure Layer (The Adapters)</h4><p>This layer <em>implements</em> the abstractions defined by the inner layers. It contains all the framework-specific and I/O specific code. It depends inward on the Application/Domain layers.</p><pre>@Injectable()<br>export class TypeOrmOrderRepository implements OrderRepository {<br>  constructor(<br>    @InjectRepository(OrderEntity)<br>    private readonly repo: Repository&lt;OrderEntity&gt;<br>  ) {}<br><br>  async findById(id: string): Promise&lt;Order | null&gt; {<br>    const entity = await this.repo.findOneBy({ id });<br>    return entity ? mapToDomain(entity) : null;<br>  }<br><br>  async save(order: Order): Promise&lt;void&gt; {<br>    await this.repo.save(mapToEntity(order));<br>  }<br>}</pre><h4>4. The Presentation Layer (The Entry Point)</h4><p>In NestJS, this is often fused with the Infrastructure layer’s controllers. Its primary job is to handle HTTP requests, perform input validation (using pipes like ValidationPipe and class-validator), and pass clean data to the Application Layer.</p><pre>@Controller(&#39;orders&#39;)<br>export class OrderController {<br>  constructor(private readonly payOrder: PayOrderUseCase) {}<br><br>  @Post(&#39;:id/pay&#39;)<br>  async pay(@Param(&#39;id&#39;) orderId: string) {<br>    await this.payOrder.execute(orderId);<br>    return { success: true };<br>  }<br>}</pre><h3>Dependency Injection &amp; Module Configuration</h3><p>The magic that wires this all together is NestJS’s DI system.</p><pre>@Module({<br>  imports: [TypeOrmModule.forFeature([OrderEntity])],<br>  controllers: [OrderController],<br>  providers: [<br>    PayOrderUseCase,<br>    { provide: OrderRepository, useClass: TypeOrmOrderRepository },<br>  ]<br>})<br>export class OrderModule {}</pre><h3>Dependency Direction</h3><p>Dependencies should flow like this:</p><pre>HTTP Request<br>   ↓<br>Controller<br>   ↓<br>Use Case<br>   ↓<br>Domain Entity</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/798/1*29ITxiELGi1MZDmHqjINvA.png" /></figure><blockquote>Inner layers don’t know about outer layers</blockquote><h3>Getting Started: Practical First Steps</h3><p>If you’re new to this, don’t try to refactor your entire application at once. Start small:</p><ol><li>Pick one important entity in your app (like User, or Product)</li><li>Extract its business rules into a plain TypeScript class (domain entity)</li><li>Create one use case for a key operation</li><li>Build an adapter for your database</li><li>Update your controller to use the new use case</li></ol><p>Once it clicks, apply the pattern to other modules (like Order).</p><h3>Conclusion</h3><p>If you design your system so that business logic lives at the center, everything else becomes replaceable. That’s what makes software scale beyond features.</p><blockquote>Start Simple, Evolve Gradually</blockquote><p>If you found this article useful, please consider leaving a clap (👏) and a comment. Happy coding! 🎉</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=91084187b3f5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-21) Advanced Bull Patterns in NestJS: Global Configs, Job Tuning & Cron Workflows]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-21-advanced-bull-patterns-in-nestjs-global-configs-job-tuning-cron-workflows-effdf5a0040b?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/effdf5a0040b</guid>
            <category><![CDATA[scalability]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[background-jobs]]></category>
            <category><![CDATA[bulls]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Mon, 13 Oct 2025 14:47:22 GMT</pubDate>
            <atom:updated>2025-10-13T14:48:04.344Z</atom:updated>
            <content:encoded><![CDATA[<p>In the last article, we explored the <strong>basics of Bull queues in NestJS</strong> — how to enqueue jobs, process them, and handle background tasks.</p><p>But here’s the truth: once you take your application into <strong>production</strong>, you’ll quickly realize that <strong>Bull isn’t just about background jobs</strong>.</p><p>Background jobs are no longer a “nice-to-have.” They’re the heartbeat of scalable backends — powering email notifications, payments, reporting, and more.</p><p>If you’ve already built simple queues with Bull in NestJS (enqueue, process, done ), you’ve only scratched the surface. Once your app hits production, queues demand <strong>discipline and patterns</strong>. Think: retries, scheduling, workflows, and monitoring.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*-XD-uo-uyoykmwld" /></figure><p>This article is your upgrade path — from <strong>basic jobs</strong> to <strong>production-grade Bull mastery</strong>.</p><h3>Why Advanced Patterns Matter</h3><p>In real-world apps like <strong>payments, notifications, or email systems</strong>, queues can’t follow a one-size-fits-all rule.</p><ul><li>Some jobs (like <strong>payment processing</strong>) need strict retry rules.</li><li>Others (like <strong>logging events</strong>) don’t deserve retries at all.</li><li>Scheduled jobs (like <strong>monthly billing</strong>) must run like clockwork.</li><li>Complex workflows (like <strong>order → payment → inventory → email</strong>) require <strong>job chaining</strong>.</li></ul><p>If you don’t define these patterns clearly, your system risks <strong>inconsistency, resource waste, and even data corruption.</strong></p><h4>1. Global Queue Configuration</h4><p>Bull allows you to define <strong>default job options</strong> at the queue level. Perfect for consistent retry and cleanup strategies.</p><p>In our app, we configure this in <strong>AppModule</strong>:</p><pre>BullModule.forRoot({<br>  redis: {<br>    host: process.env.REDIS_HOST ?? &#39;localhost&#39;,<br>    port: parseInt(process.env.REDIS_PORT ?? &#39;6379&#39;),<br>  },<br>  defaultJobOptions: {<br>    attempts: 3,<br>    backoff: { type: &#39;exponential&#39;, delay: 2000 }, // 2s → 4s → 8s<br>    removeOnComplete: true,<br>    removeOnFail: false,<br>  },<br>}),</pre><p><strong>Benefits:</strong></p><ul><li>No repeated boilerplate configs.</li><li>Consistent retry rules across the board.</li><li>Automatic cleanup = no Redis bloat.</li></ul><h4>More About BullModule Options</h4><p>The `BullModule.forRoot()` and `BullModule.registerQueue()` methods come with several powerful options that help tailor queues to your scaling strategy.</p><p>Here are some of the most useful ones:</p><ul><li><strong>prefix</strong>: Adds a prefix to all Redis keys. Handy when multiple apps share the same Redis instance.</li><li><strong>limiter</strong>: Controls how many jobs can run per time frame, preventing API flooding.</li><li><strong>defaultJobOptions</strong>: Defines retry, backoff, and cleanup defaults globally.</li><li><strong>createClient</strong>: Lets you customize the Redis connection (e.g., TLS, sentinel, or shared clients).</li><li><strong>sharedConnection</strong>: Allows multiple queues to reuse the same Redis connection for efficiency.</li></ul><p><strong>Why this matters:</strong><br>When you scale your NestJS app to multiple instances (like in Kubernetes or microservices), using these options ensures <strong>resource efficiency, namespace isolation, and predictable throughput</strong>.</p><h4>2. Per-Job Configuration with priority</h4><p>Global configs are great, but sometimes you need <strong>surgical precision</strong>.</p><p>Example from our <strong>OrderService</strong>:</p><pre>await this.orderQueue.add(<br>  &#39;send-confirmation&#39;,<br>  { orderId: order.order_id, userEmail: user.email },<br>  {<br>    attempts: 2, // override global 3<br>    priority: 5, // lower numbers = higher priority in Bull<br>  },<br>);</pre><p>Use <strong>global configs</strong> for consistency.<br>Use <strong>per-job configs</strong> for critical overrides</p><h4>3. Cron &amp; Delayed Jobs</h4><p>Bull makes <strong>scheduling jobs</strong> a breeze.</p><p><strong>Example 1: Cron Jobs (Scheduled Jobs)</strong></p><p>Use cases:</p><ul><li>Generate <strong>monthly sales reports</strong></li><li>Clear <strong>abandoned carts</strong> every 6 hours</li></ul><pre>await this.orderQueue.add(<br>  &#39;generate-monthly-report&#39;, {},<br>  { repeat: { cron: &#39;0 0 1 * *&#39; } }, // run every 1st of the month<br>);</pre><p>Or directly inside a processor:</p><pre>@Processor(&#39;reports&#39;)<br>export class ReportProcessor {<br>  @Cron(&#39;0 0 * * *&#39;) // Every day at midnight<br>  async generateDailyReport() {<br>    console.log(&#39;Generating daily sales report...&#39;);<br>    // Fetch orders and generate report PDF<br>  }<br>}</pre><p><strong>Why:</strong><br>Automates business routines like billing, analytics, and cleanup without blocking APIs.</p><p><strong>Example 2: Delayed Jobs (Time-Based Automation)</strong></p><p>Use cases:</p><ul><li>Send “Order Shipped” email <strong>2 hours</strong> after dispatch</li><li>Offer a discount if user doesn’t complete payment in <strong>24 hours</strong></li></ul><pre>await this.orderQueue.add(&#39;sendFollowUp&#39;, orderDetails, {<br>  delay: 24 * 60 * 60 * 1000, // 24 hours<br>});</pre><p><strong>Why:</strong><br> Delayed jobs make your system proactive — perfect for user re-engagement and follow-ups</p><h4>4. Advanced Retry Strategies</h4><p>Not all retries are equal — Bull lets you fine-tune retry intervals per job.</p><pre>await this.orderQueue.add(<br>    &#39;update-inventory&#39;,<br>    { productIds: createOrderDto.productIds },<br>    { attempts: 5, <br>      backoff: { type: &#39;fixed&#39;, delay: 10000 }  // fixed 10s delay for retries<br>    }<br>);</pre><p>Prevents spamming external APIs while still ensuring retries happen.</p><h4>5. Concurrency: Scaling Your Workers</h4><p>One worker process might not keep up with high-volume workloads.<br>Bull supports <strong>concurrency</strong> directly within your worker.</p><pre>@Processor(&#39;order&#39;)<br>export class OrderProcessor {<br>  @Process({ name: &#39;send-confirmation&#39;, concurrency: 5 })<br>  async handleOrderConfirmation(job: Job&lt;{ orderId: string; userEmail: string }&gt;) {<br>    console.log(`📧 Processing confirmation for order ${job.data.orderId}`);<br>    await new Promise((res) =&gt; setTimeout(res, 2000));<br>    console.log(`✅ Confirmation sent`);<br>  }<br>}</pre><p>Here, up to <strong>5 confirmation emails</strong> are processed in parallel.</p><h4>6. Monitoring with Bull Board</h4><p>Debugging queues in production is a nightmare without visibility. That’s why we use <strong>Bull Board</strong>:</p><p>Install:</p><pre>npm i @bull-board/api @bull-board/express</pre><pre>// main.ts<br>import { ExpressAdapter } from &#39;@bull-board/express&#39;;<br>import { Queue } from &#39;bull&#39;;<br>import { createBullBoard } from &#39;@bull-board/api&#39;;<br>import { BullAdapter } from &#39;@bull-board/api/bullAdapter&#39;;<br>import { getQueueToken } from &#39;@nestjs/bull&#39;;<br><br>const serverAdapter = new ExpressAdapter()<br>serverAdapter.setBasePath(&#39;/bull-board&#39;)<br>const orderQueue = app.get&lt;Queue&gt;(getQueueToken(&#39;order&#39;));<br><br>createBullBoard({<br>    queues: [new BullAdapter(orderQueue)],<br>    serverAdapter,<br>  })<br><br>app.use(&#39;/bull-board&#39;, serverAdapter.getRouter())</pre><p>Access at <a href="http://localhost:3000/bull-board">http://localhost:3000/bull-board</a></p><p><strong>Features:</strong></p><ul><li>Live job tracking</li><li>Retry or cleanup failed jobs</li><li>View performance metrics</li></ul><h4>Real-World Walkthrough</h4><p><strong>Customer places an order (</strong><strong>/order POST)</strong></p><ul><li>API immediately responds: “Order placed successfully!”</li></ul><p><strong>Bull Queue:</strong></p><ul><li>Worker 1 sends email confirmation.</li><li>Worker 2 updates product inventory.</li><li>Worker 3 (future) could handle payments or shipping.</li></ul><p><strong>Customer Experience:</strong> Instant<br> <strong>System Experience:</strong> Reliable, retryable jobs → if email fails, it retries.</p><h4>Best Practices Recap</h4><ul><li>Use global defaults for consistency.</li><li>Override configs for critical jobs.</li><li>Always clean up completed jobs (removeOnComplete).</li><li>Use cron jobs + nested flows for automation.</li><li>Monitor actively with Bull Board.</li></ul><h3>Conclusion</h3><p>Bull is more than just a background job queue. With the <strong>advanced patterns</strong> above, you can turn it into a <strong>resilient backbone</strong> for your application’s workflows.</p><p>By combining <strong>global configs, per-job precision, cron jobs, nested flows, retries, and monitoring</strong>, you’ll have a queue system that’s:</p><blockquote>Fast, Reliable, Scalable.</blockquote><h4>Next in the Series</h4><p><strong>(NestJS-22): “NestJS + BullMQ: The Modern Successor with Streams &amp; Improved Scaling.”</strong></p><h4>GitHub Repository</h4><p>Find the complete code here 👉 <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/9b7bdd455d3468f47180a8ed2152076e6e70d8e9">bhargavachary123</a></p><p>Did this guide help you? Smash that 👏, drop a comment, and follow for more NestJS deep dives!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=effdf5a0040b" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-20) NestJS & Bull: Building Resilient Background Job Queues]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-20-nestjs-bull-building-resilient-background-job-queues-539c44243867?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/539c44243867</guid>
            <category><![CDATA[scalable-architecture]]></category>
            <category><![CDATA[background-jobs]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[bulls]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Wed, 10 Sep 2025 14:27:47 GMT</pubDate>
            <atom:updated>2025-09-14T08:16:06.625Z</atom:updated>
            <content:encoded><![CDATA[<h3>(NestJS-20) NestJS &amp; Bull: Building Scalable Background Job Queues with Redis</h3><p>Modern applications often need to handle background tasks — like sending emails, processing payments, generating reports, or syncing data — that shouldn’t block user-facing requests.</p><p>Handling these reliably at scale requires a job queue system. This is where <strong>Bull</strong>, a Node.js-based, Redis-powered queue library, shines.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/686/0*u2_9A4-YoNrmNzbn" /></figure><p>In this guide, we’ll explore how to integrate <strong>Bull with NestJS</strong> to build resilient and fault-tolerant background job queues for an <strong>e-commerce use case</strong>.</p><h3>Real-World Problem</h3><p>Imagine you order shoes online. The website spins for <strong>3–5 seconds</strong> while:</p><ul><li>Inventory is checked</li><li>Payment is confirmed</li><li>Confirmation email is sent</li></ul><p>😤Annoying, right?</p><blockquote>Now imagine <strong>Amazon</strong> doing this for millions of orders daily. That’s why background job queues exist.</blockquote><h3>🔥 Why Job Queues Matter</h3><p>Imagine an <strong>e-commerce</strong> application:</p><ul><li>A user places an order.</li><li>Instead of making them wait for inventory checks, payment confirmation, and email notifications, you confirm the order <strong>immediately</strong>.</li><li>Heavy tasks are offloaded into background jobs.</li></ul><h4>Benefits:</h4><ul><li><strong>Offload heavy tasks</strong> → Keep APIs fast and responsive.</li><li><strong>Retry failed tasks</strong> → Avoid losing critical jobs due to transient errors.</li><li><strong>Scale horizontally</strong> → Workers can run on multiple instances.</li><li><strong>Prioritize jobs</strong> → Ensure high-priority tasks run first.</li></ul><h3>⚡ Why Bull + Redis?</h3><ul><li><strong>Redis-backed</strong> → Durable, persistent queues.</li><li><strong>Retries &amp; Delays</strong> → Automatic retries, scheduled jobs, delayed tasks.</li><li><strong>Concurrency</strong> → Process multiple jobs in parallel.</li><li><strong>Event-based</strong> → Monitor job lifecycle (completed, failed, stalled).</li><li><strong>Battle-tested</strong> → Widely used in production systems.</li></ul><h3>🛠 Setting Up Bull in NestJS</h3><blockquote>We’ll use an <strong>E-Commerce Order</strong> example.</blockquote><h4>1. Installing Dependencies</h4><pre>npm install --save @nestjs/bull bull</pre><h4>2. Register Bull Module</h4><pre>// app.module.ts<br>import { Module } from &#39;@nestjs/common&#39;;<br>import { BullModule } from &#39;@nestjs/bull&#39;;<br><br>@Module({<br>  imports: [<br>    BullModule.forRoot({<br>      redis: {<br>        host: process.env.REDIS_HOST ?? &#39;localhost&#39;,<br>        port: +process.env.REDIS_PORT ?? 6379,<br>      }<br>    }),<br>  ],<br>})<br>export class AppModule {}</pre><p>We configure Bull globally so <strong>all feature modules</strong> can register their own queues with <strong>default job options</strong> (applies to all jobs unless overridden).<br> Redis is used for persistence → jobs survive server crashes.</p><h4>2. Create a Queue</h4><pre>// order.module.ts<br>@Module({<br>  imports: [<br>    TypeOrmModule.forFeature([Order]),<br>    BullModule.registerQueue({<br>      name: &#39;order&#39;, // 👈 order queue<br>    }),<br>  ],<br>  controllers: [OrderController],<br>  providers: [OrderService],<br>})<br>export class OrderModule {}</pre><blockquote>Here we define a queue called &quot;order&quot;. Any background job related to <strong>orders</strong> (emails, stock, payments) goes into this queue.</blockquote><blockquote>Think of it as a <strong>separate conveyor belt</strong> for order-related tasks.</blockquote><h4>3. Define a Processor (Worker)</h4><pre>// order.processor.ts<br>import { Processor, Process } from &#39;@nestjs/bull&#39;;<br>import { Job } from &#39;bull&#39;;<br><br>@Processor(&#39;order&#39;)<br>export class OrderProcessor {<br>  @Process(&#39;send-confirmation&#39;)<br>  async handleOrderConfirmation(job: Job&lt;{ orderId: string; userEmail: string }&gt;) {<br>    console.log(`📧 Sending confirmation email for order ${job.data.orderId} to ${job.data.userEmail}`);<br>    await new Promise((res) =&gt; setTimeout(res, 2000)); // simulate delay<br>    console.log(`✅ Confirmation email sent to ${job.data.userEmail}`);<br>  }<br><br>  @Process(&#39;update-inventory&#39;)<br>  async handleInventoryUpdate(job: Job&lt;{ productIds: string[] }&gt;) {<br>    console.log(`📦 Updating inventory for products: ${job.data.productIds.join(&#39;, &#39;)}`);<br>    await new Promise((res) =&gt; setTimeout(res, 1000));<br>    console.log(`✅ Inventory updated`);<br>  }<br>}</pre><blockquote>@Processor(&#39;order&#39;) → Listens to the <strong>order queue</strong>. Each @Process = a specific <strong>job type</strong>.</blockquote><blockquote>Think of it as <strong>warehouse workers</strong> picking jobs off the belt.</blockquote><h4>4. Adding Jobs to the Queue</h4><pre>// order.service.ts<br>import { Injectable } from &#39;@nestjs/common&#39;;<br>import { InjectRepository } from &#39;@nestjs/typeorm&#39;;<br>import { Repository } from &#39;typeorm&#39;;<br>import { Order } from &#39;./entities/order.entity&#39;;<br>import { UserService } from &#39;src/user/user.service&#39;;<br>import { ProductService } from &#39;src/product/product.service&#39;;<br>import { CreateOrderDto } from &#39;./dto/create-order.dto&#39;;<br>import { UpdateOrderDto } from &#39;./dto/update-order.dto&#39;;<br>import { Queue } from &#39;bull&#39;;<br>import { InjectQueue } from &#39;@nestjs/bull&#39;;<br><br>@Injectable()<br>export class OrderService {<br>  constructor(<br>    @InjectRepository(Order)<br>    private readonly orderRepo: Repository&lt;Order&gt;,<br>    private readonly userService: UserService,<br>    private readonly productService: ProductService,<br>    @InjectQueue(&#39;order&#39;) private orderQueue: Queue, // 👈 Inject queue<br>  ) {}<br><br>  async create(createOrderDto: CreateOrderDto): Promise&lt;string&gt; {<br>    // Fetch the user to associat with the order<br>    const user = await this.userService.findOne(createOrderDto.userId);<br>    if (!user) {<br>      return &#39;User not found&#39;;<br>    }<br><br>    const order = new Order();  // Create a new order instance<br>    order.user = user;<br>    let total: number = 0;<br>    // Fetch and process each product<br>    const products = await Promise.all(createOrderDto.productIds.map(async (productId) =&gt; {<br>      const product = await this.productService.findOne(productId);<br>      if (product) {<br>        // Accumulate total price<br>        total += parseInt(product.price + &quot;&quot;);<br>        return product;<br>      }<br>    }));<br>    order.products = products;   // set products to order<br>    order.total = total;<br>    order.ordered_on = new Date();<br>    await this.orderRepo.save(order); // Save the new order to the repository<br><br>    await this.orderQueue.add(<br>      &#39;send-confirmation&#39;,<br>      {<br>        orderId: order.order_id,<br>        userEmail: user.email,<br>      },<br>    );<br><br>    await this.orderQueue.add(<br>      &#39;update-inventory&#39;,<br>      { productIds: createOrderDto.productIds }<br>    );<br><br>    return &#39;Ordered successfully (processing in background)&#39;;<br>  }<br><br>  async update(updateOrderDto: UpdateOrderDto): Promise&lt;string&gt; {<br>    const order = await this.orderRepo.findOne({ where: { order_id: updateOrderDto.orderId }, relations: { products: true } })<br>    if (!order) return &quot;Order not exists!&quot;<br><br>    let total: number = 0;<br>    const new_products = await Promise.all(updateOrderDto.productIds.map(async (productId) =&gt; {<br>      const product = await this.productService.findOne(productId);<br>      if (product) {<br>        // Accumulate total price<br>        total += parseInt(product.price + &quot;&quot;);<br>        return product;<br>      }<br>    }));<br><br>    order.products = new_products;<br>    order.total = total<br>    await this.orderRepo.save(order)<br><br>    await this.orderQueue.add(&#39;update-inventory&#39;, {<br>      productIds: updateOrderDto.productIds,<br>    });<br><br>    return &#39;Order placed successfully! (processing in background)&#39;;<br>  }<br>  // ... other methods.<br>}</pre><blockquote>We save the order <strong>synchronously</strong> → User gets instant confirmation. Heavy tasks (email, inventory) → queued for workers.</blockquote><h4>5. Register the Processor</h4><pre>// order.module.ts<br>import { Module } from &#39;@nestjs/common&#39;;<br>import { BullModule } from &#39;@nestjs/bull&#39;;<br>import { TypeOrmModule } from &#39;@nestjs/typeorm&#39;;<br>import { Order } from &#39;./entities/order.entity&#39;;<br>import { OrderService } from &#39;./order.service&#39;;<br>import { OrderController } from &#39;./order.controller&#39;;<br>import { OrderProcessor } from &#39;./order.processor&#39;;<br>import { UserModule } from &#39;src/user/user.module&#39;;<br><br>@Module({<br>  imports: [<br>    TypeOrmModule.forFeature([Order]),<br>    BullModule.registerQueue({ name: &#39;order&#39; }),<br>    UserModule,<br>  ],<br>  controllers: [OrderController],<br>  providers: [OrderService, OrderProcessor],<br>})<br>export class OrderModule {}</pre><h3>Real-World Walkthrough</h3><ol><li>Customer places an order (/order POST).</li><li>API responds instantly: <strong>Order placed successfully!</strong></li><li>Bull starts processing in background:</li></ol><ul><li>Worker 1 → sends confirmation email.</li><li>Worker 2 → updates product inventory.</li><li>Worker 3 (future) → handle payments/shipping.</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sQ9B4bMI4Zf3fbBn" /></figure><ul><li><strong>Customer Experience:</strong> Feels instant ⚡</li></ul><h3>Background Email Jobs</h3><ul><li><strong>Without Bull:</strong> User signup waits 2–3s for email → bad UX.</li><li><strong>With Bull:</strong> Signup enqueues job instantly. Worker sends email asynchronously.</li></ul><blockquote><strong>API latency:</strong> &lt; 100ms. <strong>Result: Happy users + scalable system</strong>.</blockquote><h3>Advanced Features of Bull in NestJS</h3><ul><li><strong>Delayed Jobs</strong> → Schedule tasks for later (reminders, emails).</li><li><strong>Concurrency</strong> → Run multiple workers at once.</li><li><strong>Priorities</strong> → Ensure critical jobs (like payments) are handled first.</li><li><strong>Rate Limiting</strong> → Prevent spamming APIs/external services.</li><li><strong>Queues Dashboard</strong> → Use tools like <a href="https://github.com/felixmosh/bull-board?utm_source=chatgpt.com">bull-board</a> to monitor jobs visually.</li></ul><h3>Real-World Use Cases</h3><ul><li><strong>E-Commerce</strong> → Order confirmations, inventory sync, payment retries.</li><li><strong>SaaS Apps</strong> → Report generation, CSV/PDF exports, notifications.</li><li><strong>APIs/Microservices</strong> → Webhooks, data sync, video/image processing.</li></ul><h3>Takeaway</h3><p><strong>Controller</strong> → Handles requests.<br> <strong>Service</strong> → Saves order + enqueues jobs.<br> <strong>Queue (Bull)</strong> → Stores jobs in Redis.<br> <strong>Processor (Worker)</strong> → Executes jobs asynchronously.</p><p>This is how Amazon, Flipkart, Shopify handle <strong>millions of background jobs</strong> daily.</p><h3>Conclusion</h3><p><strong>Bull + NestJS + Redis = Resilient Background Jobs 💪</strong></p><p>With this stack, you get:</p><ul><li>Distributed scalability</li><li>Faster APIs by offloading heavy tasks</li><li>Fault-tolerant, production-ready job processing</li></ul><h3>🔮 Next Up</h3><p>In the <strong>next article Advanced Bull Patterns in NestJS</strong>, we’ll take things further with:</p><ul><li><strong>Retries &amp; Backoff</strong> → Automatically retry failed jobs with exponential delays.</li><li><strong>Delayed &amp; Scheduled Jobs</strong> → Perfect for reminders, subscription renewals, and cleanups.</li><li><strong>Job Priorities</strong> → Ensure payments outrank promo emails.</li><li><strong>Concurrency &amp; Rate Limiting</strong> → Control job throughput.</li><li><strong>Repeatable &amp; Cron Jobs</strong> → Schedule recurring background tasks.</li><li><strong>Scaling Across Instances</strong> → Run multiple workers in distributed environments.</li></ul><p>Think of it as <strong>Part 2 of the Bull Series</strong> — a deeper dive into the advanced patterns that power production-ready systems.</p><h4>GitHub Repository</h4><p>Find the complete code here 👉 <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/6956777356c07d46710785081045bc0f708f7f88">bhargavachary123</a></p><p>💡 Did this guide help you? Smash that 👏, drop a comment, and follow for more NestJS deep dives!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=539c44243867" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Beyond Basic Caching: Advanced Redis Superpowers for Your NestJS App]]></title>
            <link>https://bhargavacharyb.medium.com/beyond-basic-caching-advanced-redis-superpowers-for-your-nestjs-app-402ec65229fc?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/402ec65229fc</guid>
            <category><![CDATA[distributed-systems]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[scalability]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Thu, 04 Sep 2025 17:15:53 GMT</pubDate>
            <atom:updated>2025-09-04T17:15:53.544Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>Cache Busting, Auto-Refreshing Keys, and Pattern-Based Invalidation</blockquote><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*mu7b24dP-Gb9q5KK" /></figure><p>In the last article, we set up Redis as a distributed cache in a NestJS app. That solved the basics — <strong>consistency, durability, and scalability</strong>. But real-world systems demand more: <strong>keeping data fresh, invalidating caches efficiently, and coordinating updates across services</strong>.</p><p>This is where advanced Redis patterns come into play. In this guide, we’ll cover:</p><ul><li>🔄 <strong>Cache Busting with Pub/Sub</strong> → Keep data synced across multiple app instances</li><li>🕒 <strong>Auto-Refreshing Stale Keys</strong> → Preload hot keys before they expire</li><li>🎯 <strong>Pattern-Based Invalidation</strong> → Wipe entire sets of related keys without manual cleanup</li></ul><p>Let’s break them down one by one.</p><h3>Cache Busting with Redis Pub/Sub</h3><p>When one service updates data, <strong>other instances may still serve outdated results</strong> from their cache.</p><p>Redis <strong>Pub/Sub</strong> solves this by broadcasting <strong>invalidation messages</strong> across services.</p><p><strong>Example: Pub/Sub in NestJS</strong></p><pre>import { Injectable } from &#39;@nestjs/common&#39;;<br>import Redis from &#39;ioredis&#39;;<br><br>@Injectable()<br>export class RedisService {<br>  private publisher = new Redis();<br>  private subscriber = new Redis();<br><br>  constructor() {<br>    this.subscriber.subscribe(&#39;cache-invalidate&#39;);<br>    this.subscriber.on(&#39;message&#39;, (channel, message) =&gt; {<br>      if (channel === &#39;cache-invalidate&#39;) {<br>        console.log(`Invalidating key: ${message}`);<br>        // Add cache deletion logic here<br>      }<br>    });<br>  }<br><br>  publishInvalidation(key: string) {<br>    this.publisher.publish(&#39;cache-invalidate&#39;, key);<br>  }<br>}</pre><p>Whenever a product is updated:</p><pre>await this.redisService.publishInvalidation(`product:${productId}`);</pre><blockquote>Every service listening on the channel will invalidate the key instantly.</blockquote><h3>Auto-Refreshing Stale Keys</h3><p>Imagine a <strong>hot API endpoint</strong> that expires every 5 minutes. The first unlucky user after expiration waits for the database fetch. Instead of punishing them, we can <strong>refresh the cache automatically</strong> in the background.</p><h4>Lazy Reload vs Proactive Refresh</h4><ul><li><strong>Lazy Reload</strong> → Expired → Fetch → Cache again → Return (already covered in Part 2)</li><li><strong>Proactive Refresh</strong> → Refresh <strong>before expiry</strong>, so no user ever faces a delay</li></ul><p><strong>Example: Proactive Refresh with Cron</strong></p><pre>import { Cron, CronExpression } from &#39;@nestjs/schedule&#39;;<br><br>@Injectable()<br>export class CacheWarmupService {<br>  constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}<br><br>  @Cron(CronExpression.EVERY_5_MINUTES)<br>  async refreshHotKeys() {<br>    const hotKeys = [&#39;products&#39;, &#39;top-articles&#39;];<br>    for (const key of hotKeys) {<br>      const freshData = await this.fetchFromDB(key);<br>      await this.cache.set(key, freshData, { ttl: 300 });<br>      console.log(`Refreshed key: ${key}`);<br>    }<br>  }<br>}</pre><h3>Pattern-Based Invalidation</h3><p>Sometimes you update a <strong>whole category</strong> of items, not just one. Instead of invalidating keys one by one, use <strong>pattern-based invalidation</strong>.</p><h4>Unsafe Method (not recommended at scale):</h4><pre>const keys = await redis.keys(&#39;product:*&#39;);<br>await redis.del(...keys);</pre><h4>Scalable Method with SCAN</h4><pre>async function invalidatePattern(pattern: string) {<br>  let cursor = &#39;0&#39;;<br>  do {<br>    const [newCursor, keys] = await redis.scan(cursor, &#39;MATCH&#39;, pattern, &#39;COUNT&#39;, 100);<br>    if (keys.length) {<br>      await redis.del(...keys);<br>    }<br>    cursor = newCursor;<br>  } while (cursor !== &#39;0&#39;);<br>}</pre><p>This approach is <strong>non-blocking</strong> and safe for large datasets.</p><h3>⚡ Quick Recap</h3><ul><li><strong>Pub/Sub</strong> → Keeps caches consistent across app instances</li><li><strong>Auto-Refresh</strong> → Hot keys stay fresh, users never wait</li><li><strong>Pattern Invalidation</strong> → Efficient cleanup after bulk updates</li></ul><p>With these patterns, Redis evolves from a simple cache into a <strong>smart, distributed, self-healing memory layer</strong> for your NestJS app.</p><h3>Conclusion</h3><p>Redis is more than just “fast storage.”<br> When paired with NestJS, it becomes a <strong>powerful orchestration tool for distributed state management</strong>.</p><p>By combining <strong>Pub/Sub, proactive refresh, and pattern-based invalidation</strong>, you can:</p><ul><li>Keep data always fresh</li><li>Avoid stale cache problems</li><li>Scale your system without cache chaos</li></ul><p>💡 <strong>Your Turn</strong></p><p>How are you handling caching in your production apps?<br> Drop a comment below — let’s compare strategies!</p><p>👉 If this helped, leave a clap (👏), share it with your team, and follow for the more like this artical.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=402ec65229fc" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-19) Supercharge Your NestJS App with Redis Caching (Part 2) — Distributed Caching Made Easy]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-19-supercharge-your-nestjs-app-with-redis-caching-part-2-distributed-caching-made-easy-d9a689262097?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/d9a689262097</guid>
            <category><![CDATA[nestjs-tutorial]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[redis]]></category>
            <category><![CDATA[nestjs-redis]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Fri, 22 Aug 2025 11:02:55 GMT</pubDate>
            <atom:updated>2025-08-30T10:21:50.497Z</atom:updated>
            <content:encoded><![CDATA[<h3>(NestJS-19) Supercharge Your NestJS App with Redis Caching (Part 2) — Distributed Caching Made Easy</h3><p>In <a href="https://medium.com/@bhargavacharyb/mastering-nestjs-in-memory-caching-for-lightning-fast-apis-d35851736abb"><strong>Part 1</strong></a> of this series, we explored in-memory caching in NestJS using the built-in CacheModule. While useful for small applications, it falls short for production environments:</p><p><strong>Limitations of In-Memory Caching</strong></p><ul><li><strong>No persistence</strong> → Cache vanishes if the server restarts.</li><li><strong>No multi-instance sharing</strong> → Each server maintains its own cache (problem in load-balanced systems).</li><li><strong>Limited scalability</strong> → Memory tied to one process.</li><li><strong>Basic eviction</strong> → Lacks advanced cache management strategies.</li></ul><p>This is where <strong>Redis</strong> comes in.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*zLU_-eknrpy8rVTq.jpeg" /></figure><h3>Why Redis Matters</h3><p>Let’s explore why Redis is such a natural upgrade over in-memory caching. <strong>Redis</strong> is a <strong>lightning-fast, distributed, in-memory datastore</strong> that works perfectly as a caching layer.</p><ul><li><strong>Persistence</strong> → Keeps snapshots or logs for durability.</li><li><strong>Multi-instance consistency</strong> → Works across all backend servers.</li><li><strong>Advanced eviction</strong> → LRU, LFU, TTL, etc.</li><li><strong>Pub/Sub</strong> → Real-time cache invalidation across services.</li><li><strong>Performance</strong> → Sub-millisecond reads/writes.</li></ul><p><strong>Example Impact in Real-World Systems</strong></p><ul><li><strong>E-commerce</strong>: Faster product page loads → higher conversion rates.</li><li><strong>SaaS dashboards</strong>: Reduced database strain → happier customers.</li><li><strong>APIs</strong>: Handle 10x more requests without adding servers.</li></ul><h3>Redis + NestJS = High Performance</h3><p>NestJS provides a modular architecture that integrates seamlessly with Redis. Together, they allow developers to:</p><ul><li>Reduce <strong>database load</strong>.</li><li>Improve <strong>response times</strong>.</li><li>Scale <strong>horizontally</strong> with cache consistency across instances.</li></ul><h3>Implementation</h3><h4>1. Install Dependencies</h4><pre>npm install @keyv/redis cache-manager keyv @types/cache-manager</pre><ul><li>@keyv/redis: Redis adapter for Keyv.</li><li>cache-manager: Abstract caching layer used by NestJS.</li><li>keyv: Simple key-value store for managing data.</li></ul><h4>2. Configure Redis in app.module.ts</h4><p>Here we replace in-memory caching with a <strong>Redis-powered cache store</strong>:</p><pre>import { CacheModule, Module } from &#39;@nestjs/common&#39;;<br>import { createKeyv } from &#39;@keyv/redis&#39;;<br><br>@Module({<br>  imports: [<br>    // other modules...<br>    CacheModule.registerAsync({<br>      isGlobal: true,<br>      useFactory: async () =&gt; {<br>        return {<br>          stores: [<br>            createKeyv(<br>              `redis://${process.env.REDIS_HOST ?? &#39;localhost&#39;}:${process.env.REDIS_PORT ?? 6379}`,<br>            ),<br>          ],<br>          ttl: 600000, // default 10 minutes<br>        };<br>      },<br>    }),<br>  ], <br>})<br>export class AppModule {}</pre><h3>3. Setup Environment Variables</h3><p>Add in .env file:</p><pre>REDIS_HOST=localhost<br>REDIS_PORT=6379</pre><h4>4. Service Example with Redis Caching</h4><pre>import { CACHE_MANAGER, Inject, Injectable } from &#39;@nestjs/common&#39;;<br>import { Cache } from &#39;cache-manager&#39;;<br>import { Product } from &#39;./product.entity&#39;;<br>import { Repository } from &#39;typeorm&#39;;<br>import { InjectRepository } from &#39;@nestjs/typeorm&#39;;<br><br>@Injectable()<br>export class ProductService {<br>  constructor(<br>    @Inject(CACHE_MANAGER) private cacheManager: Cache,<br>    @InjectRepository(Product) private productRepo: Repository&lt;Product&gt;,<br>  ) {}<br><br>async findAll(): Promise&lt;Product[]&gt; {<br>    // Check cache first<br>    const cached = await this.cacheManager.get&lt;Product[]&gt;(&#39;products&#39;);<br>    if (cached) {<br>      console.log(&#39;Retrieved from Redis cache&#39;);<br>      return cached;<br>    }<br>    // Fetch from DB<br>    const products = await this.productRepo.find();<br>    // Store in cache with default TTL<br>    await this.cacheManager.set(&#39;products&#39;, products);<br>    // Or with explicit TTL: await this.cacheManager.set(&#39;products&#39;, products, 300000);<br>    return products;<br>  }<br>}</pre><h3>Real-World Use Cases</h3><h4><strong>E-Commerce Product Catalogs</strong></h4><ul><li>Reduced page load times from 2s → 200ms</li><li>Database handled 70% fewer reads during peak load</li><li>Improved conversion rates and revenue</li></ul><h4><strong>SaaS Analytics Dashboards</strong></h4><ul><li>Response times improved 5x</li><li>Reduced database strain and infra costs</li><li>Increased customer satisfaction and retention</li></ul><blockquote>How are you currently handling cache in production? Comment below 👇</blockquote><h3>Conclusion</h3><p>Redis caching is essential for <strong>scalable, high-performance applications</strong>. It provides:</p><ul><li><strong>Distributed consistency</strong> across multiple app instances.</li><li><strong>Durability &amp; persistence</strong> beyond in-memory storage.</li><li><strong>Advanced eviction &amp; scalability</strong> for high-traffic APIs.</li><li><strong>Sub-millisecond response times</strong></li></ul><p>By combining <strong>Redis + NestJS</strong>, you can deliver applications that are <strong>faster, more reliable, and ready to scale</strong>.</p><h3>Next Up</h3><p>Stay tuned for <strong>“NestJS &amp; Bull: Building Resilient Background Job Queues”</strong> We’ll explore how Redis + Bull + NestJS help build <strong>retryable, fault-tolerant background jobs</strong>.</p><h4>GitHub Repository</h4><p>Find the complete code on GitHub: <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/dcbc6ba809132a83c2e160b7b5c20480b7170d26">bhargavachary123</a></p><p><strong>Did this guide help you?</strong> Leave a clap (👏), Share it, drop a comment with your results, and follow for more NestJS power-ups!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d9a689262097" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-18) Mastering NestJS In-Memory Caching for Lightning-Fast APIs]]></title>
            <link>https://bhargavacharyb.medium.com/mastering-nestjs-in-memory-caching-for-lightning-fast-apis-d35851736abb?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/d35851736abb</guid>
            <category><![CDATA[caching]]></category>
            <category><![CDATA[in-memory-cache]]></category>
            <category><![CDATA[nestjs-tutorial]]></category>
            <category><![CDATA[backend-optimization]]></category>
            <category><![CDATA[cachemodule]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Tue, 08 Jul 2025 15:35:51 GMT</pubDate>
            <atom:updated>2025-07-12T01:59:27.627Z</atom:updated>
            <content:encoded><![CDATA[<h3>(NestJS-18) <strong>NestJS In-Memory Caching (Part 1): Speed Up Your API</strong></h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/686/0*hbh7VR6qOR_kWD6j" /></figure><p>Performance and scalability are critical in modern backend development. Every millisecond matters, especially when users expect instant responses from your APIs. If your backend hits the database repeatedly for the same data, you’re not just wasting resources — you’re falling behind.</p><p><strong>NestJS in-memory cachin</strong>g is the secret sauce for speeding up API responses, delighting users, and reducing your infrastructure costs. This guide takes you step by step through everything you need to know.</p><h3>What is Caching and Why Does It Matter?</h3><p>Caching is the process of storing frequently accessed data in a fast-access memory (RAM), so future requests can be served instantly — without hitting the database or repeating expensive computations.</p><ul><li><strong>Blazing-fast API responses</strong> (lower latency)</li><li><strong>Reduced database load</strong></li><li><strong>Better scalability</strong></li><li><strong>Lower infrastructure costs</strong></li></ul><p>Without caching, your backend may execute the same queries or computations hundreds or thousands of times per minute, slowing down your API and increasing costs.</p><h3>Why Use Internal (In-Memory) Caching?</h3><p>Internal caching is ideal when:</p><ul><li><strong>Faster API Responses:</strong> Serve frequently accessed data from RAM instead of re-querying the database.</li><li><strong>Reduced Database Load:</strong> Offload read-heavy operations, freeing up resources for critical writes.</li><li><strong>Quick Setup:</strong> No external dependencies — get started with built-in CacheModule.</li><li><strong>Ideal for Prototyping:</strong> Validate caching strategies before scaling to distributed systems.</li></ul><p>NestJS provides a simple built-in @Cacheable() mechanism using an <strong>in-memory store</strong>.</p><h3>Setting It Up</h3><p>NestJS includes an internal memory cache provider out of the box. You just need to enable it:</p><h4>Step 1: Install and Import CacheModule</h4><p>NestJS ships with a built-in @nestjs/cache-manager package.</p><pre>npm install @nestjs/cache-manager cache-manager</pre><p>In your module file (e.g., app.module.ts):</p><pre>// app.module.ts<br>import { CacheModule, Module } from &#39;@nestjs/common&#39;;<br><br>@Module({<br>  imports: [<br>    CacheModule.register({<br>    CacheModule.register({ <br>      isGlobal: true,     // Making the cache module global<br>      ttl: 5 * 60 * 1000, // Default cache time to live in milliseconds<br>     }),<br>    // ...other modules<br>  ],<br>})<br>export class AppModule {}<br></pre><ul><li>cache-manager v4&gt; (used by older NestJS versions) expects TTL in seconds</li><li>cache-manager v5&lt; (used by current NestJS) expects TTL in milliseconds</li></ul><blockquote><strong>Tip:</strong> Use short TTL for rapidly changing data and longer TTL for static config or dropdown values.</blockquote><h4>Step 2: Apply CacheInterceptor at Controller or Route Level</h4><p>NestJS’s CacheInterceptor automatically caches GET responses by <strong>URL</strong> and <strong>method</strong>.</p><pre>import { Controller, Get, UseInterceptors, CacheInterceptor } from &#39;@nestjs/common&#39;;<br><br>@Controller(&#39;products&#39;)<br>@UseInterceptors(CacheInterceptor)  // Apply to all routes in this controller<br>export class ProductsController {<br>  @Get()<br>  @CacheKey(&#39;product&#39;)<br>  @CacheTTL(60 * 100) // custom milliseconds<br>  async findAll() {<br>    return this.productService.findAll();<br>  }<br>}</pre><p><strong>Route-level caching:</strong></p><pre> @UseInterceptors(CacheInterceptor)<br> @Get()<br>  async findAll() {<br>    return await this.productService.findAll();<br>  }</pre><h3>Custom Cache Keys &amp; Manual Control</h3><p>When you need fine-grained control, use the CACHE_MANAGER token to set, get, or delete cache entries manually.</p><p><strong>Example: Manual Set/Get from Cache</strong></p><pre>import { Inject, Injectable } from &#39;@nestjs/common&#39;;<br>import { InjectRepository } from &#39;@nestjs/typeorm&#39;;<br>import { Product } from &#39;./entities/product.entity&#39;;<br>import { Repository } from &#39;typeorm&#39;;<br>import { Cache } from &#39;cache-manager&#39;;<br>import { CACHE_MANAGER } from &#39;@nestjs/cache-manager&#39;;<br><br>@Injectable()<br>export class ProductService {<br>  constructor(<br>    @InjectRepository(Product)<br>    private readonly productRepo: Repository&lt;Product&gt;,<br>    @Inject(CACHE_MANAGER)<br>    private readonly cacheManager: Cache,<br>  ) { }<br><br>  async findAll(): Promise&lt;Product[]&gt; {<br>    const value = await this.cacheManager.get&lt;Product[]&gt;(&#39;products&#39;); // Check if the value is already cached<br>    if (value) {<br>      return value; // Return cached value if it exists<br>    }<br>    const products = await this.productRepo.find(); //returning all the products<br>    await this.cacheManager.set(&#39;products&#39;, products); // Set the value in the cache<br>    return products; // Return an empty array if products is null or undefined<br>  }<br>}</pre><p>You can also set custom TTL when your setting a new pair, use .del() to delete keys and .reset() to clear everything.</p><blockquote><strong>Don’t cache</strong> POST/PUT/DELETE routes, user-specific endpoints</blockquote><h3>Smart Caching Strategy</h3><ul><li><strong>Cache Only What Matters:</strong> Focus on read-heavy, expensive operations (e.g., list endpoints, metadata).</li><li><strong>Invalidate Proactively:</strong> Always clear or update the cache when underlying data changes.</li><li><strong>Monitor Memory Usage:</strong> In-memory cache uses your app’s RAM. Keep an eye on memory to avoid any errors.</li><li><strong>Use Versioning in Keys:</strong> Append a version number (v1, v2) to cache keys when API changes.</li></ul><h3>Where Should You Use This?</h3><p>Use internal caching when:</p><ul><li>Caching <strong>read-heavy</strong> endpoints (like /products, /categories, /dashboard)</li><li>Avoiding duplicate <strong>external API</strong> calls</li><li>Caching <strong>authentication metadata</strong>, roles, and permissions</li><li>Storing <strong>static config</strong> that rarely changes</li></ul><h3>Disadvantages of In-Memory Caching</h3><p>While internal caching is simple and fast, it comes with some limitations:</p><ol><li><strong>Memory Limits: </strong>Stored in the app process, so memory is shared with everything else. You can run out quickly.</li><li><strong>No Persistence: </strong>When your app restarts, the cache is wiped.</li><li><strong>Single Instance Only: </strong>If you scale horizontally (e.g., load-balanced servers), each instance has its own isolated cache.</li><li><strong>No Distributed Coordination: </strong>You can’t share cache across services or between workers.</li></ol><h3>When to Upgrade to Distributed Caching?</h3><p>Scaling up? Internal cache is great — until you deploy multiple app instances or need durability. Redis like external cache manager’s steps in when:</p><ul><li><strong>Multi-Instance Support:</strong> Share cache across load-balanced servers.</li><li><strong>Persistence &amp; Backup:</strong> Data survives restarts and can be snapshot.</li><li><strong>Advanced Eviction Policies:</strong> Better control over memory and eviction mechanisms.</li><li><strong>Pub/Sub &amp; Invalidation:</strong> Real-time cache invalidation across processes.</li></ul><p>We’ll explore that in <strong>Part 2: Caching in NestJS Using Redis</strong> — where we’ll dive deep into distributed cache strategies, eviction control, and real-time cache invalidation using the power of Redis.</p><h3>Conclusion</h3><p>In this guide you saw how to wire up Nest’s in-memory cache, pick TTLs, use interceptors and manual cache calls, and understand when to upgrade.</p><h4>Next Up</h4><blockquote><strong>Part 2:</strong> <strong>Supercharge Your NestJS App with Redis Caching</strong><em>— Distributed Caching Made Easy</em></blockquote><p>We’ll integrate Redis as an external cache manager, implement TTL-based strategies, cache invalidation, and more.</p><h4>GitHub Repository</h4><p>Find the complete code on GitHub: <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/40617c3f743e96c8b13d43e46683c998cd9422e3">bhargavachary123</a></p><p><strong>Did this guide help you?</strong> Leave a clap (👏), Share it, drop a comment with your results, and follow for more NestJS power-ups!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d35851736abb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-17)  Persistent Dynamic Jobs — Part 2: Cron, Interval, Timeout with MySQL & TypeORM]]></title>
            <link>https://bhargavacharyb.medium.com/nestjs-17-persistent-dynamic-jobs-part-2-cron-interval-timeout-with-mysql-typeorm-02dcae509580?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/02dcae509580</guid>
            <category><![CDATA[job-scheduling]]></category>
            <category><![CDATA[nestjs]]></category>
            <category><![CDATA[persistent-dynamic-jobs]]></category>
            <category><![CDATA[nestjs-tutorial]]></category>
            <category><![CDATA[dynamic-job]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Wed, 02 Jul 2025 14:05:24 GMT</pubDate>
            <atom:updated>2025-07-02T14:07:33.934Z</atom:updated>
            <content:encoded><![CDATA[<h3>(NestJS-17) Persistent Dynamic Jobs — Part 2: Cron, Interval, Timeout with MySQL &amp; TypeORM</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/820/0*f_6wa_kj1AADEldD.png" /></figure><p>Dynamic background jobs are incredibly powerful — they allow applications to respond to real-time user input by scheduling tasks like email reminders, database backups, or temporary notifications. However, a major limitation of traditional dynamic jobs is that they vanish when your server restarts. To address this, we’ll implement job persistence using a database (TypeORM + MySQL) in NestJS.</p><p>In this article, you’ll learn how to:</p><ol><li><strong>Model</strong> scheduled jobs with TypeORM</li><li><strong>Persist</strong> job metadata in a MySQL database</li><li><strong>Restore</strong> and rehydrate jobs on application startup</li><li><strong>Ensure </strong>observability and reliability for background processes</li></ol><h3>Why Persistence Matters</h3><ul><li><strong>Survive Restarts &amp; Crashes</strong><br> In-memory schedules vanish on reboot. Persisting jobs ensures they automatically resume.</li><li><strong>Admin Visibility &amp; Control</strong><br> Stored job metadata lets admins view or delete jobs via a dashboard or API.</li><li><strong>Audit &amp; Compliance</strong><br> A historical record of job creation and execution supports troubleshooting, security audits, and analytics.</li><li><strong>Hard to manage from UI</strong><br> Impossible to inspect or modify job metadata without persistence</li></ul><h3>Architecture Overview</h3><ol><li><strong>Tasks </strong>Entity (TypeORM) stores job definitions.</li><li><strong>TasksService </strong>manages job lifecycle (create, delete, restore).</li><li><strong>TasksController</strong> exposes REST endpoints for job management.</li><li><strong>CALLBACKS</strong> map callback names to concrete actions (e.g., backup, notify).</li><li>Jobs are automatically reloaded from the DB using the <strong>onModuleInit()</strong> lifecycle hook</li></ol><h4>1. Define the Tasks Entity</h4><p>Define the Tasks entity:</p><pre>import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from &#39;typeorm&#39;;<br><br>export type JobType = &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;;<br>@Entity(&#39;tasks&#39;)<br>export class Tasks {<br>  @PrimaryGeneratedColumn()<br>  id: number;<br><br>  @Column({ unique: true })<br>  name: string;<br><br>  @Column()<br>  type: JobType;<br><br>  @Column()<br>  schedule: string; // cron expression or milliseconds as string<br><br>  @Column()<br>  callbackType: string;<br><br>  @Column({ default: true })<br>  active: boolean;<br><br>  @CreateDateColumn()<br>  createdAt: Date;<br><br>  @UpdateDateColumn()<br>  updatedAt: Date;<br>}</pre><h4>2. Validate Input with DTOs</h4><p>Separate DTOs for clarity and validation:</p><pre>// src/tasks/dto/create-cron.dto.ts<br>import { IsString, IsNotEmpty, Matches } from &#39;class-validator&#39;;<br><br>export class CreateCronDto {<br>  @IsString() <br>  @IsNotEmpty()<br>  name: string;<br><br>  @IsString() <br>  @IsNotEmpty()<br>  cronTime: string;<br><br>  @IsString() <br>  @IsNotEmpty()<br>  callbackType: string;<br>}<br><br>// src/tasks/dto/create-interval.dto.ts<br>import { IsString, IsNotEmpty, IsNumber, Min } from &#39;class-validator&#39;;<br><br>export class CreateIntervalDto {<br>  @IsString() <br>  @IsNotEmpty()<br>  name: string;<br><br>  @IsNumber() <br>  @Min(1)<br>  milliseconds: number;<br><br>  @IsString() <br>  @IsNotEmpty()<br>  callbackType: string;<br>}<br><br>// src/tasks/dto/create-timeout.dto.ts<br>import { IsString, IsNotEmpty, IsNumber, Min } from &#39;class-validator&#39;;<br><br>export class CreateTimeoutDto {<br>  @IsString() <br>  @IsNotEmpty()<br>  name: string;<br><br>  @IsNumber() <br>  @Min(1)<br>  milliseconds: number;<br><br>  @IsString() <br>  @IsNotEmpty()<br>  callbackType: string;<br>}</pre><h4>3. Define Callback Mappings</h4><p>Secure, auditable task behavior:</p><pre>export const CALLBACKS: Record&lt;string, (jobName: string) =&gt; void&gt; = {<br>  logTime: (name) =&gt; console.log(`[${name}] ${new Date().toISOString()} ran.`),<br>  notifyAdmin: (name) =&gt; console.log(`[${name}] Admin notified.`),<br>  backupDatabase: (name) =&gt; console.log(`[${name}] DB Backup started.`),<br>  clearTempCache: (jobName) =&gt; console.log(`${jobName} Clearing temp cache...`),<br>  // Add more callbacks as needed<br>};</pre><h4>Why This Approach is Superior</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ukwUYcFW6jHjxaluK0Kc2Q.png" /></figure><h4>4A. Job CRUD Operations (TasksService — Part 1)</h4><p>This service section is responsible for:</p><ul><li>Creating new jobs</li><li>Deleting jobs</li><li>Checking existence</li><li>Starting/stopping jobs</li></ul><p>CRUD logic is focused only on actions exposed via API or admin dashboards.</p><pre>// tasks.service.ts<br>import { Injectable, Logger } from &#39;@nestjs/common&#39;;<br>import { SchedulerRegistry } from &#39;@nestjs/schedule&#39;;<br>import { CronJob } from &#39;cron&#39;;<br>import { InjectRepository } from &#39;@nestjs/typeorm&#39;;<br>import { Repository } from &#39;typeorm&#39;;<br>import { JobType, Tasks } from &#39;./entities/tasks.entity&#39;;<br><br>export const CALLBACKS: Record&lt;string, (jobName: string) =&gt; void&gt; = {<br>  logTime: (jobName) =&gt; console.log(`${jobName} ${new Date().toISOString()} Job ran!`),<br>  notifyAdmin: (jobName) =&gt; console.log(`${jobName} Notify admin: job executed!`),<br>  backupDatabase: (jobName) =&gt; console.log(`${jobName} Triggering database backup...`),<br>  clearTempCache: (jobName) =&gt; console.log(`${jobName} Clearing temp cache...`),<br>  // Add more callbacks as needed<br>};<br><br>@Injectable()<br>export class TasksService {<br>  private readonly logger = new Logger(TasksService.name);<br><br>  constructor(<br>    @InjectRepository(Tasks)<br>    private readonly tasksRepo: Repository&lt;Tasks&gt;,<br>    private readonly schedulerRegistry: SchedulerRegistry,<br>  ) {}<br><br>  private getCallback(name:string, type: string): (() =&gt; void) | undefined {<br>    return () =&gt; CALLBACKS[type](name);<br>  }<br><br>  async createCronJob(<br>    name: string,<br>    cronTime: string,<br>    callbackType: string,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    if (await this.tasksRepo.findOne({ where: { name } })) {<br>      return { success: false, message: `Job &quot;${name}&quot; already exists` };<br>    }<br>    const callback = this.getCallback(name, callbackType);<br>    if (!callback) {<br>      return { success: false, message: `Invalid callbackType: ${callbackType}` };<br>    }<br>    try {<br>      const cronJob = new CronJob(cronTime, () =&gt; this.handleJobExecution(name));<br>      console.log(`Creating cron job &quot;${name}&quot; with schedule: ${cronTime}`);<br>      this.schedulerRegistry.addCronJob(name, cronJob);<br>      cronJob.start();<br><br><br>      const newJob = this.tasksRepo.create({<br>        name,<br>        type: &#39;cron&#39;,<br>        schedule: cronTime,<br>        callbackType,<br>        active: true,<br>      });<br>      await this.tasksRepo.save(newJob);<br>      this.logger.log(`Created cron job &quot;${name}&quot;`);<br>      return { success: true, message: `Cron job &quot;${name}&quot; created` };<br>    } catch (err) {<br>      this.logger.error(`Create cron job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br><br>  async createIntervalJob(<br>    name: string,<br>    milliseconds: number,<br>    callbackType: string,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    if (await this.tasksRepo.findOne({ where: { name } })) {<br>      return { success: false, message: `Job &quot;${name}&quot; already exists` };<br>    }<br>    const callback = this.getCallback(name, callbackType);<br>    if (!callback) {<br>      return { success: false, message: `Invalid callbackType: ${callbackType}` };<br>    }<br>    try {<br>      const intervalId = setInterval(() =&gt; this.handleJobExecution(name), milliseconds);<br>      this.schedulerRegistry.addInterval(name, intervalId);<br><br>      const newJob = this.tasksRepo.create({<br>        name,<br>        type: &#39;interval&#39;,<br>        schedule: milliseconds.toString(),<br>        callbackType,<br>        active: true,<br>      });<br>      await this.tasksRepo.save(newJob);<br>      this.logger.log(`Created interval job &quot;${name}&quot;`);<br>      return { success: true, message: `Interval job &quot;${name}&quot; created` };<br>    } catch (err) {<br>      this.logger.error(`Create interval job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br><br>  async createTimeoutJob(<br>    name: string,<br>    milliseconds: number,<br>    callbackType: string,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    if (await this.tasksRepo.findOne({ where: { name } })) {<br>      return { success: false, message: `Job &quot;${name}&quot; already exists` };<br>    }<br>    const callback = this.getCallback(name, callbackType);<br>    if (!callback) {<br>      return { success: false, message: `Invalid callbackType: ${callbackType}` };<br>    }<br>    try {<br>      const timeoutId = setTimeout(() =&gt; this.handleJobExecution(name), milliseconds);<br>      this.schedulerRegistry.addTimeout(name, timeoutId);<br><br>      const newJob = this.tasksRepo.create({<br>        name,<br>        type: &#39;timeout&#39;,<br>        schedule: milliseconds.toString(),<br>        callbackType,<br>        active: true,<br>      });<br>      await this.tasksRepo.save(newJob);<br>      this.logger.log(`Created timeout job &quot;${name}&quot;`);<br>      return { success: true, message: `Timeout job &quot;${name}&quot; created` };<br>    } catch (err) {<br>      this.logger.error(`Create timeout job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br><br>  async deleteJob(<br>    name: string,<br>    type: JobType,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    try {<br>      switch (type) {<br>        case &#39;cron&#39;:<br>          this.schedulerRegistry.deleteCronJob(name);<br>          break;<br>        case &#39;interval&#39;:<br>          this.schedulerRegistry.deleteInterval(name);<br>          break;<br>        case &#39;timeout&#39;:<br>          this.schedulerRegistry.deleteTimeout(name);<br>          break;<br>      }<br>      await this.tasksRepo.delete({ name });<br>      this.logger.log(`Deleted job &quot;${name}&quot; (${type})`);<br>      return { success: true, message: `Deleted` };<br>    } catch (err) {<br>      this.logger.error(`Delete job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br><br>  async getAllJobs() {<br>    return {<br>      cron: this.schedulerRegistry.getCronJobs(),<br>      interval: this.schedulerRegistry.getIntervals(),<br>      timeout: this.schedulerRegistry.getTimeouts(),<br>    };<br>  }<br><br>  async jobExists(<br>    name: string,<br>    type: JobType,<br>  ): Promise&lt;boolean&gt; {<br>    return this.schedulerRegistry.doesExist(type, name);<br>  }<br><br>  async stopJob(<br>    name: string,<br>    type: JobType,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    try {<br>      if (type === &#39;cron&#39;) {<br>        this.schedulerRegistry.getCronJob(name).stop();<br>      } else if (type === &#39;interval&#39;) {<br>        clearInterval(this.schedulerRegistry.getInterval(name));<br>      } else if (type === &#39;timeout&#39;) {<br>        clearTimeout(this.schedulerRegistry.getTimeout(name));<br>      }<br>      this.logger.log(`Stopped job &quot;${name}&quot; (${type})`);<br>      return { success: true, message: `Stopped` };<br>    } catch (err) {<br>      this.logger.error(`Stop job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br><br>  async startJob(<br>    name: string,<br>    type: JobType,<br>  ): Promise&lt;{ success: boolean; message: string }&gt; {<br>    try {<br>      if (type === &#39;cron&#39;) {<br>        this.schedulerRegistry.getCronJob(name).start();<br>        this.logger.log(`Started cron job &quot;${name}&quot;`);<br>        return { success: true, message: `Started` };<br>      }<br>      return { success: false, message: `${type} jobs cannot be restarted` };<br>    } catch (err) {<br>      this.logger.error(`Start job failed: ${err.message}`);<br>      return { success: false, message: err.message };<br>    }<br>  }<br>}</pre><h4>4B. Job Restoration on Startup (TasksService — Part 2)</h4><p>This logic ensures persisted jobs are rehydrated and scheduled at runtime:</p><pre>async onModuleInit() {<br>    await this.restoreJobs();<br>  }<br><br>  private async restoreJobs() {<br>    const jobs = await this.tasksRepo.find({ where: { active: true } });<br>    for (const job of jobs) {<br>      if (job.type === &#39;cron&#39;) {<br>        const cronJob = new CronJob(job.schedule, () =&gt; this.handleJobExecution(job.name));<br>        this.schedulerRegistry.addCronJob(job.name, cronJob);<br>        cronJob.start();<br>      } else if (job.type === &#39;interval&#39;) {<br>        const intervalId = setInterval(() =&gt; this.handleJobExecution(job.name), +job.schedule);<br>        this.schedulerRegistry.addInterval(job.name, intervalId);<br>      } else if (job.type === &#39;timeout&#39;) {<br>        const timeoutId = setTimeout(() =&gt; this.handleJobExecution(job.name), +job.schedule);<br>        this.schedulerRegistry.addTimeout(job.name, timeoutId);<br>      }<br>      this.logger.log(`Restored job &quot;${job.name}&quot; (${job.type})`);<br>    }<br>  }<br><br>  private async handleJobExecution(name: string) {<br>    const job = await this.tasksRepo.findOne({ where: { name } });<br>    if (!job) {<br>      this.logger.warn(`Unknown job execution requested: ${name}`);<br>      return;<br>    }<br>    const callback = this.getCallback(name,  job.callbackType);<br>    if (!callback) {<br>      this.logger.warn(`No callback found for job: ${job.callbackType}`);<br>      return;<br>    }<br>    try {<br>      callback();<br>    } catch (err) {<br>      this.logger.error(`Job &quot;${name}&quot; failed: ${err.message}`);<br>    }<br>  }</pre><p>This is crucial for production environments where uptime and task continuity matter.</p><h4>5. REST API Endpoints for Job Management</h4><pre>import {<br>  Controller, Post, Delete, Get, Body, Param, Logger,<br>} from &#39;@nestjs/common&#39;;<br>import { TasksService } from &#39;./tasks.service&#39;;<br>import { CreateCronDto } from &#39;./dto/create-cron.dto&#39;;<br>import { CreateIntervalDto } from &#39;./dto/create-interval.dto&#39;;<br>import { CreateTimeoutDto } from &#39;./dto/create-timeout.dto&#39;;<br>import { JobType } from &#39;./entities/tasks.entity&#39;;<br><br>@Controller(&#39;tasks&#39;)<br>export class TasksController {<br>  private readonly logger = new Logger(TasksController.name);<br><br>  constructor(private readonly tasksService: TasksService) { }<br><br>  @Post(&#39;cron&#39;)<br>  async createCron(@Body() dto: CreateCronDto) {<br>    return this.tasksService.createCronJob(dto.name, dto.cronTime, dto.callbackType);<br>  }<br><br>  @Post(&#39;interval&#39;)<br>  async createInterval(@Body() dto: CreateIntervalDto) {<br>    return this.tasksService.createIntervalJob(dto.name, dto.milliseconds, dto.callbackType);<br>  }<br><br>  @Post(&#39;timeout&#39;)<br>  async createTimeout(@Body() dto: CreateTimeoutDto) {<br>    return this.tasksService.createTimeoutJob(dto.name, dto.milliseconds, dto.callbackType);<br>  }<br><br>  @Delete(&#39;:type/:name&#39;)<br>  async deleteJob(<br>    @Param(&#39;type&#39;) type: JobType,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.deleteJob(name, type);<br>  }<br><br>  @Get()<br>  async getAllJobs() {<br>    return this.tasksService.getAllJobs();<br>  }<br><br>  @Get(&#39;:type/:name&#39;)<br>  async checkJobExists(<br>    @Param(&#39;type&#39;) type: JobType,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.jobExists(name, type);<br>  }<br><br>  @Post(&#39;:type/:name/stop&#39;)<br>  async stopJob(<br>    @Param(&#39;type&#39;) type: JobType,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.stopJob(name, type);<br>  }<br><br>  @Post(&#39;:type/:name/start&#39;)<br>  async startJob(<br>    @Param(&#39;type&#39;) type: JobType,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.startJob(name, type);<br>  }<br>}</pre><p>These enable dynamic creation and management of background jobs through any UI or CLI integration.</p><h3>Conclusion</h3><p>By persisting dynamic jobs in a database and restoring them on startup, you’ll gain:</p><h4>Benefits of the New Architecture</h4><ul><li>Store job metadata in a DB</li><li>Restore them on app boot</li><li>Manage them via REST API</li><li>Jobs survive reboots</li><li>Controlled execution via predefined callbacks</li><li>Future extension: audit logs, execution stats, failure alerts</li></ul><p>This pattern ensures your NestJS background jobs are both flexible and production-ready.</p><h3>Next Steps</h3><p>In our next article, we will explore <strong>Caching in NestJS</strong>.</p><h4>GitHub Repository</h4><p>Find the complete code on GitHub: <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/1483399489b8b5a384753f9492fab85915cb9ad3"><strong>bhargavachary123</strong></a></p><p>If you found this article useful, please consider leaving a clap (👏) and a comment.</p><p><strong>Happy coding! 🎉</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=02dcae509580" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[(NestJS-16) NestJS Dynamic Jobs (Part 1) — Create Cron, Interval, and Timeout Tasks at Runtime]]></title>
            <link>https://bhargavacharyb.medium.com/mastering-background-cron-jobs-in-nestjs-the-complete-guide-cd0f41bb6b31?source=rss-b22d801ae376------2</link>
            <guid isPermaLink="false">https://medium.com/p/cd0f41bb6b31</guid>
            <category><![CDATA[task-management]]></category>
            <category><![CDATA[nestjs-tutorial]]></category>
            <category><![CDATA[scheduling]]></category>
            <category><![CDATA[backend-development]]></category>
            <category><![CDATA[cronjob]]></category>
            <dc:creator><![CDATA[Bhargava Chary ]]></dc:creator>
            <pubDate>Fri, 30 May 2025 08:04:15 GMT</pubDate>
            <atom:updated>2025-06-30T17:24:11.408Z</atom:updated>
            <content:encoded><![CDATA[<h3>(NestJS-16) NestJS Dynamic Jobs (Part 1) — Create Cron, Interval, and Timeout Tasks at Runtime</h3><p>Background jobs are essential in any serious backend application — whether it’s sending reminder emails, cleaning up old data, generating scheduled reports, or integrating with third-party APIs. With NestJS, implementing scheduled background processes is both powerful and easy, thanks to its robust built-in scheduling module.</p><p>This guide walks you through:</p><ul><li>Installing and configuring the NestJS scheduling module</li><li>Defining static scheduled jobs using decorators (@Cron, @Interval, @Timeout )</li><li>Dynamically creating, managing, and deleting scheduled jobs at runtime with secure callbacks</li></ul><p><strong>Note:</strong> This guide does not cover automated testing strategies for scheduled jobs or persistent job storage, which will be addressed in upcoming articles.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/984/1*jYrnHCXFq526NDZGuVzRuQ.png" /></figure><h3>1. Installation and Configuration</h3><p>Before you can schedule jobs, you need to add the scheduling package:</p><pre>npm install --save @nestjs/schedule</pre><p>Now that we have installed the scheduling package, let’s implement a scheduled task module. I’ll create a new module called tasks that will contain our scheduled jobs.</p><pre>nest g module tasks<br>nest g controller tasks<br>nest g service tasks</pre><p>Then, set up the scheduler in your Tasks module:</p><pre>import { Module } from &#39;@nestjs/common&#39;;<br>import { ScheduleModule } from &#39;@nestjs/schedule&#39;;<br>import { TasksService } from &#39;./tasks.service&#39;;<br>import { TasksController } from &#39;./tasks.controller&#39;;<br><br>@Module({<br>  imports: [ScheduleModule.forRoot()],<br>  controllers: [TasksController],<br>  providers: [TasksService],<br>})<br>export class TasksModule {}</pre><h3>2. Defining Jobs: Cron, Interval, Timeout</h3><p>INestJS supports three core scheduling decorators:</p><ul><li>@Cron for cron-based tasks (e.g., “run every day at 5am”)</li><li>@Interval for fixed interval tasks (e.g., “every 10 seconds”)</li><li>@Timeout for one-off delayed tasks</li></ul><pre>// tasks.controller.ts<br>import { Controller, Logger } from &#39;@nestjs/common&#39;;<br>import {<br>    Cron,<br>    CronExpression,<br>    Interval,<br>    Timeout,<br>} from &#39;@nestjs/schedule&#39;;<br><br>@Controller(&#39;tasks&#39;)<br>export class TasksController{<br>  private readonly logger = new Logger(TasksController.name);<br><br>  // ─── Cron Jobs ─────────────────────────────────────────────────────────────<br><br>  /**  Runs every minute */<br>  @Cron(CronExpression.EVERY_MINUTE)<br>  handleEveryMinute() {<br>    this.logger.debug(&#39;Called every minute&#39;);<br>  }<br><br>  /**  Runs daily at 5:00 AM  */<br>  @Cron(&#39;0 5 * * *&#39;)<br>  handleDailyAtFive() {<br>    this.logger.log(&#39;Daily job at 5 AM&#39;);<br>  }<br><br>  // ─── Interval &amp; Timeout ────────────────────────────────────────────────────<br><br>  /**  Runs every 10 seconds  */<br>  @Interval(10_000)<br>  handleInterval() {<br>    this.logger.debug(&#39;Called every 10 seconds&#39;);<br>  }<br><br>  /**  Runs once, 20 seconds after app start  */<br>  @Timeout(20_000)<br>  handleTimeout() {<br>    this.logger.debug(&#39;Called once after 20 seconds&#39;);<br>  }<br><br>  // ─── Dynamicschedule&#39;s ────────────────────────────────────────────────────<br>}</pre><p><strong>Tip:</strong> Use built-in cron aliases like CronExpression.EVERY_MINUTE or custom cron strings like 0 5 * * *.</p><h3>3. Task Management: Dynamic Scheduling at Runtime</h3><p>For maximum flexibility, you may want to create, delete, stop, or start jobs <strong>dynamically at runtime</strong>. This is especially useful for admin panels, user-driven scheduling, or advanced system workflows.</p><h4><strong>Safe Dynamic Callbacks</strong></h4><p><strong>Security first:</strong> Rather than allowing arbitrary JavaScript execution (which is dangerous), we use a <strong>predefined map of safe callbacks</strong>. Only these callbacks can be scheduled via API. You can expand this map to suit your app’s needs.</p><pre>import { Injectable, Logger } from &#39;@nestjs/common&#39;;<br>import { SchedulerRegistry } from &#39;@nestjs/schedule&#39;;<br>import { CronJob } from &#39;cron&#39;;<br><br>function capitalize(str: string): string {<br>  return str.charAt(0).toUpperCase() + str.slice(1);<br>}<br><br>@Injectable()<br>export class TasksService {<br>  private readonly logger = new Logger(TasksService.name);<br>  constructor(private readonly schedulerRegistry: SchedulerRegistry) {}<br><br>  createCronJob(name: string, cronTime: string, callback: () =&gt; void): { success: boolean; message: string } {<br>    try {<br>      const job = new CronJob(cronTime, callback);<br>      this.schedulerRegistry.addCronJob(name, job);<br>      job.start();<br>      this.logger.log(`Cron job &quot;${name}&quot; created and started`);<br>      return { success: true, message: `Cron job &quot;${name}&quot; created and started` };<br>    } catch (error) {<br>      this.logger.error(`Failed to create cron job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br><br>  createIntervalJob(name: string, milliseconds: number, callback: () =&gt; void): { success: boolean; message: string } {<br>    try {<br>      const interval = setInterval(callback, milliseconds);<br>      this.schedulerRegistry.addInterval(name, interval);<br>      this.logger.log(`Interval job &quot;${name}&quot; created and started`);<br>      return { success: true, message: `Interval job &quot;${name}&quot; created and started` };<br>    } catch (error) {<br>      this.logger.error(`Failed to create interval job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br><br>  createTimeoutJob(name: string, milliseconds: number, callback: () =&gt; void): { success: boolean; message: string } {<br>    try {<br>      const timeout = setTimeout(callback, milliseconds);<br>      this.schedulerRegistry.addTimeout(name, timeout);<br>      this.logger.log(`Timeout job &quot;${name}&quot; created and started`);<br>      return { success: true, message: `Timeout job &quot;${name}&quot; created and started` };<br>    } catch (error) {<br>      this.logger.error(`Failed to create timeout job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br><br>  deleteJob(name: string, type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;): { success: boolean; message: string } {<br>    try {<br>      switch (type) {<br>        case &#39;cron&#39;:<br>          this.schedulerRegistry.deleteCronJob(name);<br>          break;<br>        case &#39;interval&#39;:<br>          this.schedulerRegistry.deleteInterval(name);<br>          break;<br>        case &#39;timeout&#39;:<br>          this.schedulerRegistry.deleteTimeout(name);<br>          break;<br>      }<br>      this.logger.log(`Job &quot;${name}&quot; of type &quot;${type}&quot; deleted successfully`);<br>      return { success: true, message: `Job &quot;${name}&quot; of type &quot;${type}&quot; deleted successfully` };<br>    } catch (error) {<br>      this.logger.error(`Failed to delete job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br><br>  getAllJobs() {<br>    return {<br>      cron: this.schedulerRegistry.getCronJobs(),<br>      interval: this.schedulerRegistry.getIntervals(),<br>      timeout: this.schedulerRegistry.getTimeouts(),<br>    };<br>  }<br><br>  jobExists(name: string, type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;): boolean {<br>    try {<br>      return this.schedulerRegistry.doesExist(type, name);<br>    } catch (error) {<br>      this.logger.error(`Error checking job existence: ${error.message}`);<br>      return false;<br>    }<br>  }<br><br>  stopJob(name: string, type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;): { success: boolean; message: string } {<br>    try {<br>      switch (type) {<br>        case &#39;cron&#39;:<br>          this.schedulerRegistry.getCronJob(name).stop();<br>          break;<br>        case &#39;interval&#39;:<br>          clearInterval(this.schedulerRegistry.getInterval(name));<br>          break;<br>        case &#39;timeout&#39;:<br>          clearTimeout(this.schedulerRegistry.getTimeout(name));<br>          break;<br>      }<br>      this.logger.log(`Job &quot;${name}&quot; of type &quot;${type}&quot; stopped successfully`);<br>      return { success: true, message: `Job &quot;${name}&quot; of type &quot;${type}&quot; stopped successfully` };<br>    } catch (error) {<br>      this.logger.error(`Failed to stop job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br><br>  startJob(name: string, type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;): { success: boolean; message: string } {<br>    try {<br>      if (type === &#39;cron&#39;) {<br>        this.schedulerRegistry.getCronJob(name).start();<br>        this.logger.log(`Job &quot;${name}&quot; of type &quot;cron&quot; started successfully`);<br>        return { success: true, message: `Job &quot;${name}&quot; of type &quot;cron&quot; started successfully` };<br>      } else {<br>        const msg = `${capitalize(type)} jobs cannot be restarted. Please recreate the job.`;<br>        this.logger.warn(msg);<br>        return { success: false, message: msg };<br>      }<br>    } catch (error) {<br>      this.logger.error(`Failed to start job &quot;${name}&quot;: ${error.message}`);<br>      return { success: false, message: error.message };<br>    }<br>  }<br>}</pre><p><strong>Example endpoints:</strong></p><pre>import { <br>  Controller, <br>  Post, <br>  Delete, <br>  Get, <br>  Body, <br>  Param, <br>  Logger, <br>  BadRequestException <br>} from &#39;@nestjs/common&#39;;<br>import { TasksService } from &#39;./tasks.service&#39;;<br><br>const CALLBACKS: Record&lt;string, () =&gt; void&gt; = {<br>  logTime: () =&gt; console.log(`[${new Date().toISOString()}] Job ran!`),<br>  notifyAdmin: () =&gt; console.log(&#39;Notify admin: job executed!&#39;),<br>  // Add more as needed<br>};<br><br>@Controller(&#39;tasks&#39;)<br>export class TasksController {<br>  private readonly logger = new Logger(TasksController.name);<br><br>  constructor(private readonly tasksService: TasksService) {}<br>  // ─── Cron Jobs ─────────────────────────────────────────────────────────────<br>  ...<br>  // ─── Interval &amp; Timeout ────────────────────────────────────────────────────<br>  ...<br>  // ─── Dynamicschedule&#39;s ────────────────────────────────────────────────────<br>  @Post(&#39;cron&#39;)<br>  createCronJob(<br>    @Body() body: { name: string; cronTime: string; callbackType: string },<br>  ) {<br>    const callback = CALLBACKS[body.callbackType];<br>    if (!callback) {<br>      throw new BadRequestException(&#39;Invalid or unsupported callbackType&#39;);<br>    }<br>    return this.tasksService.createCronJob(body.name, body.cronTime, callback);<br>  }<br><br>  @Post(&#39;interval&#39;)<br>  createIntervalJob(<br>    @Body() body: { name: string; milliseconds: number; callbackType: string },<br>  ) {<br>    const callback = CALLBACKS[body.callbackType];<br>    if (!callback) {<br>      throw new BadRequestException(&#39;Invalid or unsupported callbackType&#39;);<br>    }<br>    return this.tasksService.createIntervalJob(body.name, body.milliseconds, callback);<br>  }<br><br>  @Post(&#39;timeout&#39;)<br>  createTimeoutJob(<br>    @Body() body: { name: string; milliseconds: number; callbackType: string },<br>  ) {<br>    const callback = CALLBACKS[body.callbackType];<br>    if (!callback) {<br>      throw new BadRequestException(&#39;Invalid or unsupported callbackType&#39;);<br>    }<br>    return this.tasksService.createTimeoutJob(body.name, body.milliseconds, callback);<br>  }<br><br>  @Delete(&#39;:type/:name&#39;)<br>  deleteJob(<br>    @Param(&#39;type&#39;) type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.deleteJob(name, type);<br>  }<br><br>  @Get()<br>  getAllJobs() {<br>    return this.tasksService.getAllJobs();<br>  }<br><br>  @Get(&#39;:type/:name&#39;)<br>  checkJobExists(<br>    @Param(&#39;type&#39;) type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.jobExists(name, type);<br>  }<br><br>  @Post(&#39;:type/:name/stop&#39;)<br>  stopJob(<br>    @Param(&#39;type&#39;) type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.stopJob(name, type);<br>  }<br><br>  @Post(&#39;:type/:name/start&#39;)<br>  startJob(<br>    @Param(&#39;type&#39;) type: &#39;cron&#39; | &#39;interval&#39; | &#39;timeout&#39;,<br>    @Param(&#39;name&#39;) name: string,<br>  ) {<br>    return this.tasksService.startJob(name, type);<br>  }<br>}</pre><h4>I. What is the use of callback in dynamic job scheduling?</h4><p>A <strong>callback</strong> is simply a function (or a block of code) that is executed when the job’s scheduled time is reached.</p><ul><li>In static jobs (using @Cron, etc.), NestJS runs the method you decorate.</li><li>In <strong>dynamic jobs</strong>, you programmatically define what happens when the job fires — by passing a callback function to the scheduler.</li></ul><h4>II. Why Use a Callback?</h4><p><strong>Callbacks</strong> make dynamic scheduling possible, because they let you:</p><ul><li>Decide <strong>what action the job performs</strong> at runtime.</li><li>Associate <strong>different behaviors</strong> with different scheduled jobs.</li><li>Pass extra data or context (such as which users to email, what message to send, etc.).</li></ul><h4>III. Why do we use a map of callbacks?</h4><p><strong>For safety and security!</strong><br> If you allowed <em>any</em> arbitrary code to be executed as a job, it would be a huge security risk (someone could inject dangerous code).</p><ul><li>So, you maintain a list or map of <strong>allowed callbacks</strong> (safe actions you define in advance).</li><li>When a dynamic job is created, it can only select from these pre-approved actions.</li></ul><h3>Error Handling &amp; Best Practices</h3><ul><li><strong>Use try/catch blocks</strong> in all job handlers to prevent uncaught exceptions.</li><li><strong>Monitor</strong> job failures and executions using integrated logging or third-party tools (e.g., Sentry, Datadog).</li><li><strong>Use environment variables</strong> for job schedules in production.</li><li><strong>Avoid long-running tasks</strong> in cron handlers; use a job queue (e.g., Bull) if needed.</li><li><strong>Keep jobs idempotent</strong> — if a job runs twice, it should not corrupt data.</li></ul><h3>Conclusion</h3><p>NestJS’s scheduling module lets you build production-ready background workflows using clean decorators and robust runtime management. By following this guide, you’ll be able to:</p><ul><li>Configure and define all types of background jobs</li><li>Dynamically manage jobs with a secure and extensible API</li><li>Monitor, stop, and start jobs as needed</li><li>Adhere to best practices for reliability and security</li></ul><p><strong>Current Drawbacks</strong></p><ul><li><strong>In-Memory Only &amp; Lack of Persistence:</strong> Dynamically created jobs <strong>are lost when the server restarts</strong></li><li><strong>Limited Callback Logic: </strong>For security, you’re limited to predefined callbacks only. True dynamic logic is not supported for safety.</li><li><strong>No Audit/History: </strong>No automatic logging of job creation, deletion, or execution history unless implemented manually.</li><li><strong>No Built-in Job Recovery: </strong>If a job fails or is interrupted (e.g., due to a crash), it is not automatically recovered or retried.</li><li><strong>No Delayed Persistence or Replaying: </strong>Missed jobs due to downtime are not replayed when the server is back up.</li></ul><h3>Next Steps</h3><p><strong>In the next article,</strong>we’ll dive into <strong>Persistent Dynamic Background Jobs </strong>in NestJS using a database, so you never lose schedules after a server restart — and can audit every job for compliance and reliability.</p><h4>GitHub Repository</h4><p>Find the complete code on GitHub: <a href="https://github.com/bhargavachary123/zero-to-nestjs/tree/efd8b6df60a3018115191b3798ea22d64880a018"><strong>bhargavachary123</strong></a></p><p>If you found this article helpful, leave a clap (👏) and a comment! 🚀</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cd0f41bb6b31" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>