Welcome back! In the previous lesson, you learned how to protect user data by enforcing ownership controls. Now, let’s talk about another important part of building a reliable API: logging.
Logging means keeping a record of what happens in your application. For example, you might want to know which endpoints are being called, how long requests take, and whether there are any errors. Good logging helps you monitor your API, find bugs, and understand how users interact with your service.
In this lesson, you will learn how to use interceptors in NestJS to create a centralized logging system. This means you can automatically log every request and response in one place, without having to add logging code to every controller or service.
An interceptor in NestJS is like a security camera at the entrance of a building. It can watch every request that comes in and every response that goes out. Interceptors can be used for many things, such as logging, transforming data, or handling errors.
When a request comes to your API, the interceptor can:
- See the request before it reaches your controller.
- See the response before it goes back to the client.
- Measure how long the request took.
This makes interceptors perfect for logging because you can capture all the important details in one place.
Let’s build a logging interceptor step by step. Here’s the code for our LoggingInterceptor:
What’s happening here?
- The interceptor is a class with an
interceptmethod. - It gets the HTTP request and response objects.
- It records the HTTP method (like GET or POST), the URL, and the start time.
- When the request is finished, it logs the method, URL, status code, how long it took, and the current time.
- The log is sent to a service called
LogsService.
Here we use
tapfromRxJS, which allows us to perform side effects (like logging) once the response has been handled. It does not alter the response sent to the client — it simply “taps into” the stream so we can measure timing and record the log. This distinction is important: if you accidentally used an operator that transformed the stream (e.g.,map), you might unintentionally alter the response data.
Now, let’s see how logs are stored and how you can access them.
Here’s the LogsService:
Explanation:
- The service keeps an in-memory list (buffer) of the most recent 100 logs.
- When a new log is added, it goes to the end of the list. If there are more than 100 logs, the oldest one is removed.
- The
listmethod returns the most recent logs, up to the limit you request (the default is 20).
Note that this log storage is in-memory only. This means logs will disappear if the application restarts, and they are not persisted to a database or external service. In production systems, you’d typically send logs to a persistent log management system (e.g., Elastic Stack, Datadog, or a database). For now, the in-memory buffer is useful for development and learning.
To access the logs, we add an endpoint in the admin controller:
What does this do?
- Adds a
/admin/logsendpoint.
Logs can contain sensitive information, so it’s important to protect them. In our example, we use two guards and a role decorator:
JwtAuthGuardchecks whether the user is authenticated.RolesGuardchecks whether the user has the right role.@Roles('admin')ensures only admins can access the logs.
This means only users who are logged in and have the admin role can see the logs. This helps keep your application secure and prevents regular users from seeing information they shouldn’t.
This is where the importance of combining guards and interceptors becomes clear. For example, in the previous unit we implemented
OwnerOrAdminGuardto ensure regular users could only modify their own reading progress. Here, logging is a cross-cutting concern that affects every request — but we protect access to the logs themselves with guards. This separation of concerns ensures that logs are collected universally, while only admins can view the full history.
To verify that your logging and guard setup works, try the following curl commands:
Admin logs endpoint (enveloped success)
Verify error envelope for missing token
In the first case, you should see the recent logs returned in JSON format if you are authenticated as an admin. In the second case, the server should return an error response with an appropriate HTTP status code (e.g., 401 Unauthorized).
If you log into the provided single-page frontend application with
admin/admincredentials, you will also notice a new Admin page in the navigation bar. This page lets you query the logs directly from the UI instead of using curl, which is a useful way to visualize how logging and guards tie together in a real app.
In this lesson, you learned how to:
- Use a
NestJSinterceptor to log every request and response. - Store logs in memory with a service.
- Expose logs through a protected admin endpoint.
- Secure sensitive log data using guards and roles.
Next, you’ll get to practice these concepts by writing and testing your own logging interceptor and admin log endpoint. This will help you get comfortable with centralized logging and security in NestJS. Good luck!
