Featured resource
Tech Upskilling Playbook 2025
Tech Upskilling Playbook

Build future-ready tech teams and hit key business milestones with seven proven plays from industry leaders.

Learn more
  • Labs icon Lab
  • Core Tech
Labs

Guided: Apply Decorators to Enhance Class Behavior in TypeScript

Learn to implement TypeScript method decorators for automatic API logging. You'll transform cluttered service methods filled with manual console.log statements into clean, focused code enhanced by reusable @Log decorators that capture method calls, parameters, and return values without touching your business logic.

Labs

Path Info

Level
Clock icon Beginner
Duration
Clock icon 39m
Last updated
Clock icon Aug 29, 2025

Contact sales

By filling out this form and clicking submit, you acknowledge our privacy policy.

Table of Contents

  1. Challenge

    Introduction

    Introduction to the Logging Problem

    Codebases often become cluttered with scattered console.log statements that obscure business logic and create maintenance challenges.

    Examining the src/services/UserService.ts file reveals the core issue: logging code is embedded directly within business methods. You'll find method entry/exit markers, parameter dumps, timestamps, and error traces scattered throughout the codebase. This approach violates the single responsibility principle and makes the business logic difficult to read and maintain.

    The Solution

    Decorators provide a clean approach for cross-cutting concerns like logging, enabling separation of business logic from logging requirements while maintaining code readability and reducing duplication.

    Your Task: Analyze Current Logging

    Examine the UserService.ts file and identify the various console.log and console.error statements present.

    Make note of the different logging patterns you observe:

    • Method entries and exits
    • Parameter dumps
    • Timestamps
    • Error messages
    • Success notifications

    Note which methods contain excessive logging code and identify repetitive patterns.

    Observing the Logging Output

    To observe these logging patterns in action, a development server has been started in the left terminal (you should see "Server is running").

    You can test the User API by running the following command in the right terminal:

    curl http://localhost:3000/users
    

    This triggers the getAllUsers() method in UserService.ts, displaying the current logging statements in the left terminal.

    Use this output as a baseline to verify that the refactoring steps in the following sections produce identical results.

    What You'll Learn

    In the upcoming steps, you'll learn how to create a decorator that encapsulates this repetitive logging code. This approach will provide a cleaner logging solution while keeping your business logic completely separate from logging concerns.

    If you encounter any difficulties during any step, you can reference the /solution/step# directory where the complete solution is available.


    Click the right arrow to start encapsulating these logging patterns into a reusable decorator.

  2. Challenge

    Decorator Fundamentals

    TypeScript Decorator Fundamentals

    Have you ever wondered how those mysterious @ symbols work in modern TypeScript code? You've probably seen them sprinkled throughout frameworks like Angular or NestJS, but decorators might seem like magic at first glance!

    How Method Decorators Work

    A method decorator is simply a function that gets called when TypeScript processes your class definition. It receives three important parameters that give it complete control over the method:

    function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
      // Your decorator magic happens here
    }
    
    • target represents the class prototype.
    • propertyKey is the method name as a string.
    • descriptor holds the method's metadata and configuration, including the actual method function stored in its value property.

    Configuration Requirements

    Before using decorators in TypeScript, you must enable them in your project configuration since they remain experimental features.

    Verify that your tsconfig.json file includes these essential compiler options:

    {
      "compilerOptions": {
        ...,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      }
    }
    

    While decorators are labeled as "experimental," this reflects the ongoing evolution of the ECMAScript standard rather than stability concerns. They're widely used in production applications and frameworks.

    Your Task: Building Your First Decorator

    In the src/decorators/logging.ts file, you'll find a basic @Log decorator that currently logs the class and method names from the target and propertyKey parameters. While simple, this provides enough functionality to start applying the decorator to methods.

    Decorator Call Functionality

    Looking at the server logs in the left terminal, you'll notice the Decorator applied ... message appears before the server even starts. The decorator currently only logs during class initialization but doesn't affect method execution.

    To add runtime behavior, replace the original method with a wrapper function that executes when the method is called.

    This is achieved by accessing the original method through descriptor.value and substituting it with a new function that incorporates your desired functionality.

    Restore Original Method Execution

    However, notice that nothing is returned by the call and checking the server logs reveals that the original method's behavior is completely missing. The current implementation has replaced the original method entirely without preserving its functionality.

    To resolve this, the decorator must invoke the original method within the wrapper function. You now have a working decorator that serves as the foundation for migrating your existing logging code into a reusable decorator pattern.


    Click the right arrow to proceed with migrating existing embedded log messages into your decorator.

  3. Challenge

    Migrate Logging

    Migrating Logging Messages to the Decorator

    Excellent work! You've successfully created a basic @Log decorator that can wrap the method call. Now you can begin migrating your existing logging code into this decorator.

    Enhance Your Basic Decorator

    Reviewing the logging patterns identified in UserService.ts, you can start migrating these over to the decorator starting with the method entry timestamp and method name.

    Your Task: Move Method Flow Logging to the Decorator

    In the following tasks, you'll gradually move the flow-related logging functionality into your @Log decorator.

    In the following step, you'll complete the migration by adding logging for the method's input arguments and output result.

  4. Challenge

    Logging Parameters and Returns

    Logging Parameters and Return Values

    Your @Log decorator is working great. Reviewing the getAllUsers() function, you may notice the last log message that needs to be migrated: method results. Replicating this in the @Log decorator will complete the migration of logging functionality in the getAllUsers() method. You have successfully migrated all logging functionality from the getAllUsers method into the @Log decorator.

    Moving to the getUserById method, you can see similar logging patterns that can now be handled by decorating this method with the @Log decorator, such as the method entry log and timestamp.

    Next, you'll see a specific log message for the passed id parameter. These method parameter logs can be moved to the decorator that will generically log all method parameters, providing the same visibility with better consistency.

    Handling Validation Errors

    After removing the parameter logging from getUserById, you'll notice the next validation error User ID is required log message.

    Since this throws an Error object, the @Log decorator's existing error handling will automatically capture it, making the manual console.error statement redundant.

    While this validation provides good input checking, it's difficult to test via the endpoint since the Express router handles missing parameters differently.

    That said, you can use this same concept to address one of the next set of log messages.

    The User found message is already handled by the @Log decorator when logging the results, but the User not found message is not.

    Error Handling Considerations

    Note: While throwing an error is an effective way to log "user not found" scenarios, it may cause the router to return a 500 Internal Server Error instead of the proper 404 Not Found status. Consider this trade-off between simplified logging and correct HTTP response codes.

    Completing the Migration

    The remaining log messages in getUserById (completion and error messages) are already handled by the @Log decorator and can be removed.

    Note: While the console.error statement is now redundant, keep the try-catch block if you need custom error handling logic. Simply rethrow the error afterward so the @Log decorator can still capture and log it.

    Congratulations

    Excellent work! You've successfully refactored your codebase using TypeScript decorators. You've learned to centralize logging for method calls, parameters, results, and errors, making your code cleaner and more maintainable.

    Your @Log decorator now provides a reusable, consistent way to track method activity across your service. Consider applying this decorator to your remaining methods to reinforce these concepts and maximize the benefits throughout your codebase.

    Well done exploring the power of decorators!

Jeff Hopper is a polyglot solution developer with over 20 years of experience across several business domains. He has enjoyed many of those years focusing on the .Net stack.

What's a lab?

Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.

Provided environment for hands-on practice

We will provide the credentials and environment necessary for you to practice right within your browser.

Guided walkthrough

Follow along with the author’s guided walkthrough and build something new in your provided environment!

Did you know?

On average, you retain 75% more of your learning if you get time for practice.