- Lab
- Core Tech

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.

Path Info
Table of Contents
-
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 variousconsole.log
andconsole.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 inUserService.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.
-
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 itsvalue
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 thetarget
andpropertyKey
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.
-
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.
-
Challenge
Logging Parameters and Returns
Logging Parameters and Return Values
Your
@Log
decorator is working great. Reviewing thegetAllUsers()
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 thegetAllUsers()
method. You have successfully migrated all logging functionality from thegetAllUsers
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 errorUser ID is required
log message.Since this throws an
Error
object, the@Log
decorator's existing error handling will automatically capture it, making the manualconsole.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 theresults
, but theUser 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!
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.