The Express. js Course - Module 10: Logging and Handling Errors | Shivendra Raghuvanshi | Skillshare
Search

Playback Speed


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

The Express. js Course - Module 10: Logging and Handling Errors

teacher avatar Shivendra Raghuvanshi, Lead Developer and Online Teacher

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

    • 1.

      Class Introduction

      2:28

    • 2.

      Introduction: Logging and Handling Errors

      3:23

    • 3.

      Handling Server Selection Error

      3:24

    • 4.

      Error Middleware in Express

      4:32

    • 5.

      Eliminating Try-Catch Blocks

      6:42

    • 6.

      Using Express Async Errors module

      3:13

    • 7.

      Logging Errors

    • 8.

      Logging to MongoDB

      3:46

    • 9.

      Uncaught Exceptions

      5:03

    • 10.

      Unhandled Rejections

      7:39

    • 11.

      Extracting Routes

      4:40

    • 12.

      Extracting the Logging Logic

      2:08

    • 13.

      Extracting the Database Logic

      4:30

    • 14.

      Extracting the Config Logic

      4:49

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

2

Students

--

Project

About This Class

Module 10: Logging and Handling Errors is all about building reliable and maintainable backend applications with Express.js. You’ll learn to identify, log, and handle errors efficiently, ensuring your application remains robust and secure under any circumstances. This module also covers the essential skill of organizing your codebase by extracting critical logic into separate, reusable modules.

What You’ll Learn

  • Handle server selection errors gracefully.
  • Master the use of error middleware in Express.js.
  • Simplify error handling using Express Async Errors.
  • Log errors to the console, files, and even MongoDB for tracking and debugging.
  • Address uncaught exceptions and unhandled rejections.
  • Extract routes, logging, database, and configuration logic into modular components for a cleaner codebase.

Meet Your Teacher

Teacher Profile Image

Shivendra Raghuvanshi

Lead Developer and Online Teacher

Teacher
Level: Intermediate

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. Class Introduction: Welcome back to Module ten, logging and handling errors of the Express JS course series. My name is Shawn Ragawnhi, You guide to mastering Express Js. Over the years, I have built scalable and robust applications for various industries, and in this class, I will help you tackle one of the most critical aspects of backend development, logging and handling errors. One of my proudest moments was when I designed a logging system that reduced debugging time for a client by 60%. Today, I will show you how to achieve similar results. In this module, we will focus on identifying and handling errors efficiently in Express s, simplifying error handling with tools like Express async errors, logging errors to multiple destinations like files and MongoDB for better tracking. Next, handling uncaught exceptions and unhandled rejections to ensure application stability. And finally, we will extract and organize key parts of your app that is routes logging and configuration for a clean and maintainable code base. This class is for developers who want to build robust and maintainable Express Jas applications. If you have completed the earlier modules, you are ready to dive in. Basic knowledge of JavaScript no Jas and express is all you need to get started. By mastering error handling and logging, you will significantly enhance the reliability of your applications. You will also save countless hours debugging, making you a more efficient and valuable developer. Finally, for the project, you will build a custom lover to handle logs across multiple transports. That is Console Files and Manga DB. And then you will refactor the Fair Wheels app by extracting joy validation and server logic into separate modules. These hands on tasks will solidify your understanding of error handling and modularization. This module is a game changer for your backend development skills. Let's dive in and make your applications more robust than ever. See you in the first lecture, let's get started. 2. Introduction: Logging and Handling Errors: In our current implementation of Fairwee's app, we have assumed an ideal world where everything works successfully. However, in the real world, there are always unexpected errors. For example, it is possible that our connection to Mongo Deb drops out for whatever reason. So as a best practice, you should count for these unexpected situations and handle them properly, which means you should send a proper error message to the client and log the exception on the server. So later, you can look at the log and see what are some issues that are happening frequently and how you can improve them. So let me demonstrate a real world scenario where our Mongo EV server dies. So here in the terminal, you can see I'm running the application with Nord mod. And here's my other terminal window where I'm running Mongo Damon. It is a background service that is listening on port 27017. And here in Postman, I have a tab open to send a get request api CR. So when we send this request, we get a 200 response. Beautiful. Now, back in Mongo DB terminal, I'm going to stop this process. So let me open a new Power Show window and type top services name MongoDB. And that's it. Our Mongo DV server is now shut down. Let's see what happens when you send this request one more time. So this is hanging in there. After a few seconds, we are going to see an error message in the terminal where we are running the application. Okay, so here's the error message. Mongo server selection error, SRO selection timed out after, then we have a dynamic value server selection, timeout S. And here's the actual error interrupted at shutdown. So by default, when you connect to Mongo DB, if the connection cannot be established, MongoDB driver will attempt to reconnect three times with 1 second interval. If you scroll down to the bottom of the page, C, our app crashed. That means we exited from the process with code one. Now in this particular demonstration, I shut down the MongaiVserver, so it wouldn't really matter if this process is live or not. But let's imagine in a real world scenario, our Manga Divi server is going down for, let's say, 1 minute, and then it's going to come back after 1 minute. With the current implementation, our node process will terminate and will not be able to serve any other clients even after Mongo Divi resta. So this is a problem and a very big one. So we need to properly handle these scenarios, and that's what we are going to learn in this section. 3. Handling Server Selection Error: Now let's take the first step to handle this error properly. So whenever you see server selection error, that means Manga B client is unable to connect to any server in the Mangaib deployment. This happens for various reasons like incorrect connection rings, network issues, server misconfiguration or version incompatibility. Note that this error occurred when we tried to fetch data from the car's API. So let's go to our cars module. This is the handler for getting the list of cars. So here we have a promise that is returned here. We are awaiting that, but nowhere in this code, we have a try cache block to handle rejected promises. This implementation is same as getting a promise, calling then, but not calling cash to handle the errors. So if you are using the promised syntax, we should always call cache to handle exceptions. If you are using a sync and Aviate, you should always have r cache blocks. So here, we need to put this code in a tri block like this and then add the cache block where we get the error. Now here we need to send a proper response to the client. So response that status. We use the error code 500, which means internal server error. So something failed on the server, we don't know what. And then send a message like something failed. Now, technically, here we should also log the exception, but we are going to look at that later. So let's improve this application step by step. Now, let's see how this new implementation works. So back in the terminal, I'm going to start Mangaibi first because if we don't start it and run the application, look, we cannot connect to Mangaib. So initially, I want to be connected to Mangaib and then I want to drop that connection somewhere in the middle. So let's stop this application. Now back to the terminal window. Let's run start service name Mangaib. Now let's run our fair wheels application. Okay, connected to Manga DV. I'm going to start this process. So again, top service name Manga DV. Then here in Post MD, I'm going to send a request for the cars endpoint. This is going to take 30 seconds, so I'm going to pause the recording and come back. Alright. This is what we get. 500 internal server error. And this is our message. Now, if you look at the terminal window for our application, you no longer see that server selection error, which resulted in the termination of this process. 4. Error Middleware in Express: So in the last lecture, we took the first step to handle errors properly. But there is a problem in the current implementation. Let's say tomorrow we decide to change the message that we send to the client. With the current implementation, we have to go through every route handler where we use this Trcche block and modify that message. So in a real world situation, here we are going to log the error. Again, if in the future, we decide to change how we log the error we have to go back to several route handlers and make that change. So we want to move this logic for handling errors to somewhere central. So in the future, we want to make a change in the logic to handle errors. There is a single place we need to modify. So let's go to index dot g. This is where we are registering our middleware functions. In Express, we have a special kind of middleware function called error middleware. We register that middleware function after all the existing middleware functions. So here we call app that US and pass a middleware function with three parameters, request response, and next. But we also add a fourth argument here at the front. That's the exception or error that we catch somewhere else in the application. Now in this function, we add all the logic for handling errors in our application. So back to cars Js. I'm going to cut this logic from here and paste it here. Now, back to cars Js one more time. Here in this cache block, we want to pass control to our error handling middleware function. So we add a new parameter here, next. And as you know, we call next to pass control to the next middleware function in the request processing pipeline. Here in the cache block, we call next and pass this error as an argument. Now, because in index or Js, we registered this function after all the existing middleware functions, when we call next, we will end up here. And the error that we pass will be the first argument to this function. Now with this new implementation, we have a single place to handle errors. So if you want to make changes in the future, we only come back and modify this function. In a real world application, the logic for logging the exception or errors might be several lines long. We don't want to add all the details in index dot Js. So in index or Js, we just want to do orchestration. We want to do a high level arrangement. The details should be encapsulated in a different module. So I'm going to move this function, this middleware function to a separate module. So back here, we have this middleware folder. Let's add a new file here, error dot Js. Coming back to index Js, I'm going to get this function here and in error Js, set module exports to this function. So now we separated the details of error handling in a separate module, and this results in better separation of concerns. In index or Js on the top, we need to load this module. So const error. We set this to require. Then we go one level up to the middleware folder, and unload the error module. And finally, here, we call app dot g and pass our error handling function. Note that I'm not calling this function. I'm just passing a reference to this function. So now our application has a better design. However, in cast or Js, we have this try cache block, and as you can tell, we have to repeat this in every route handler in our application. We have to add this cache error, and here we should call next. This is repetitive. So in the next lecture, I'm going to show you how to improve this implementation. 5. Eliminating Try-Catch Blocks: In this route handler, we have a try cache block to handle errors. The problem is, we will end up repeating this try cache block in every single route handler. This adds a lot of noise to our code and make it harder to focus on the actual logic for each route. It's just distracting. Ideally, we want to move this high level error handling logic somewhere else to a single function that we can reuse across all routes. Let me show you how we can do this. First, we will create a reusable function called async middleware. This function will serve as a template. It will include the tri cache block we need, but we will make it flexible so it can adapt to different route handlers. So the function will take a route handler as an argument like this. Inside the tr block, we will call that handler. If anything goes wrong, the cache block will pass the error next middleware function. This way, the only part that changes for each route is a specific logic in the handler. Everything else stays the same. Alright, let's define this async middleware. So this function will look something like this. It takes a route handler as an argument inside, we wrap the handler in a tr cache block. If the handler throws an error, the cache block calls next with the exception. Now let's use this middleware in our route. So instead of writing that tr cache block directly in the route handler, we pass this function to async middleware. This middleware will take care of the error handling for us. So we don't need this next parameter, the tr block, and the cache block. So our route code becomes much cleaner. Now, look at this ASN function, the anonymous function that we are passing as a route handler. Eventually, we want to pass this ASN function as an argument to this new function. Now, because handler is an ASN function, we should await it. And because we have used await here, we should also mark this function as AS. Now there is a tiny issue we need to address. When calling the handler inside Async Middleware, we need to pass the request response and next parameters. But here's the catch. Those objects aren't defined anywhere in a sync middleware. So how do you get access to these parameters? Well, to solve this, we need to understand how express works. So here, when we define a route router dot get, let's say new route. Here we pass a route handler function which takes three parameters, request response, and next, and it goes to a code plot. So we don't call this function our sales. Instead, we pass a reference to this function, and Express takes care of calling it and passing the required arguments. That is request response and next at runtime. But in our current implementation, we are directly calling Async Middleware. That's not how Express expects it to work. We need to make a small change here. So we will turn this Async middleware function into a factory function. So instead of directly calling it, we'll make it return new function. This returned function will act as the actual route handler that Express can call. So this new returned function will accept request response, and next as parameters. And inside, it will call the original handler, we pass to async Middleware, passing along request response and next. Now, when Express calls this returned function, it will provide the necessary objects. That is request response and next, and everything will work seamlessly. With the setup, we have moved the tricchblock into async middleware. This means our route handlers are now super clean. We are awaiting the call to the handler, so we need to mark the calling function, which is this function as async. And we no longer need to apply this async here anymore because in this function in Async middleware nowhere we are awaiting a promise, we are simply returning an async function. Okay? Finally, let's make Async middleware reusable. So we will move it to a separate module here in our middleware folder. Let's create a new file called async Js. Let me cut the code for Async Middleware from here and paste it here. Finally, we will export it as module thought exports, and we set this to fun engine. Back here in Casta Js, simply load this Async middle ware. So on the top, const async middleware. We set this to require then the middleware folder and async. And that's it. Now you can use this middleware in any route handler across your application. So with this approach, your core becomes cleaner, more focused, and easier to maintain. 6. Using Express Async Errors module: In the last lecture, we define this async middleware function. Now, while this async middleware function solves the problem of repetitive tr cach blocks, the issue we have is that we have to remember to call it every time. And this also makes our cord a little bit noisy. So in this lecture, I'm going to show you a different approach. We are going to use an NPM module, and this module will monkey patch our route handlers at runtime. So when we send a request to this endpoint, that module will wrap our route handler code inside something like this. Let me show you how that works. So open up a terminal and install Express, async errors. Let's install this. Now, let's go to index or Js We need to load this module when the application starts. So we will have a require of express async errors. That's all we need to do. Don't have to get the result and store it in a constant. Now back in cars or JS, we can remove the call to Async Middleware and get back to our original route handler implementation, which is far simpler and cleaner. Similarly, I'm going to remove the second call to Async middleware. And finally, on the top, I'm going to remove the required statement as well. Now let's test this and make sure it is functioning properly. So back in the terminal, I'm going to run our Fairwheels application. Now, in postmen, I'm going to get all the cars. So that endpoint is working. Now I'm going to stop Manga DB. So here in the Manga Di B terminal, let's stop this with top service name Manga DB then send another request to the server to send again. This is going to take a little while. All right, here's the response we were expecting a 500 error with this message. So this verifies that this module that we installed properly moved control from a route handler to our error handling function. So you can see using Express Async errors is very, very easy. And this is my suggested approach for handling async errors in express route handlers. However, if this module doesn't work for your application for whatever reason, then you need to switch back to this other approach and use Async middleware function. 7. Logging Errors: This is our error middleware. Now, as I told you before, in every enterprise application, we need to log the exceptions that are thrown in the application. So later we come back, look at the log and see what are the areas of the application that we can improve. So in this lecture, I'm going to introduce you to a very popular logging library called Winston. So here's the Winston on NPM. The current version is 3.17. And as you can see, there have been over 13 million weekly downloads. It's a very popular and feature rich library. So let's get started. Here in the terminal, NPM install Winston. Beautiful. Now, back to the NPM page. Here you can see the recommended way to use Winston is to create your own logger. And below, we have a sample code to get started. So I will just copy this code and modify it according to our app. Copy all this and paste it here in arrodt Js. On the top, we have our Winston module. Then we have our logger, which is created using this create logger method. It contains an object with few properties, level, format, default meta, and transports. So let me explain how this works. First, we have log level. So level controls which logs are to be processed. That means it filter logs by severity. So severity of all levels is assumed to be numerically ascending from most important to least important. That means if we have error, then it has a severity of zero and have the highest priority. Similarly, we have one, info, STDP and verbs debug and silly with a severity of six, and it has the least priority. So here we have set the default level to info, meaning all messages of info, warn and error will be lagged. And if we set the level to verbos, then all the messages of verbs and above levels will be locked. Next, we have format. So a format is used to customize how logs appear. That is a plain text, a JCN object or timestamped. And there are more formats, these are implemented in log form, a separate module from Winston, and you can have a look at this module to see all the formats available. Here we are going to use the JCNFmat and we don't need this default beta. Now, this logger object has an array of transports. So a transport is essentially a storage device for our logs. So it defines where the logs are sent. Winston comes with a few core transports. They are Console for logging messages on the Console pile and HTTP for calling an SDDB endpoint for logging messages. There are also plugins for Winston. There are other NPM modules for logging messages in Mongo DB and COS DB, which is another popular no SQL database. There's also a plugin for logging messages in Redis and Loge, which is a very popular log analysis and monitoring service for enterprise applications. Now here we are going to use two different transports, one for logging messages into a file and another for logging to the console. So let me change this one to console. And we don't need this file name property here. Our logger is ready to use, and if you just want to have a file of logs in JSON format, this much code is sufficient to do so. However, there is more to Winston. It's also possible to set a custom format and a custom level on each transport separately. And any number of formats may be combined into a single format using format dot combine. So I'm going to use a few formats in our transports and then combine them for a better experience. Let me show you how to do this. First of all, let's remove this default level and format. Now on the console, I want to use colors to log levels based on the custom mapping. For that, we can use colorize format and combine it with simple format. So here, add a property format, and then we call the combined method, the winston dot format dot combine and simply pass the formats that we want to combine. So winston dot format dot ColorIE and winston dot format dot Simple. We also want to give it a level info. So as I mentioned before, all the messages of info one and error levels will be logged to the console. That's it. Similarly, we can customize our file transport. As you can see, custom level is already set to error. Great. Here, we want to make the logs more readable by adding current date and time. It can be done by combining timestamp and JCN formats. So here, one more time, I'm calling combine. And then pass wstin dot forma dot Time STAMP and instin dot fam dot JCN. By doing so, we have added a timestamp to each error that may occur inside the JSN object. Finally, we need to use this logger in our error middleware. So we call logger dot log. Then we pass an object with a property level, which we can set to error or you can simply use the error helper method to log the error directly. Like this. Now, we just need to pass the error message. So we set the message property to err dot message. Now to demonstrate this, let's go to cars dot js. Here in the get cars route, I'm going to throw an error. So throw new error not able to fetch cars. So let's imagine somewhere in the application, an error is thrown with the current implementation. Our error middleware will catch that exception. It will log it using Winston and return the 500 error to the lien. So let's test this. I'm going to run the application, Norman Beautiful. Now, back in postman, let's send a get request to the cars endpoint. Alright. So here's our internal server error. Now, if you look at the console, we have error because we used Winston dot error method, and its color is red. And this is the error message that we have thrown, not able to fetch cars. So this is the console transport which we have customized on the logger. Now in our project, you can see here we have this new file, error dot log. Here we have a JSON object with a few properties level, which is error message is not able to fetch cars and the timestamp. So in the future, you can query the log file and perhaps extract only the errors at a specific date. So this is a big picture. We simply call logger dot error or one of the helper methods. And depending on the transport that we have configured, Winston will log the given message. In the next lecture, I'm going to show you how to log on Mongo DB. 8. Logging to MongoDB: All right. Now let me show you how to log messages to Manga Deb. The logging to Mangaib is made pretty simple with another NPM package, Winston Mangaib. So let's install it. NPM install Winston Mangaib. The version 6.0 0.0. So make sure you have the exact same version. Otherwise, what I'm going to show you will not work in your system. Back in E Js In the last lecture, we added customized file and Console transports. Now we are going to add a new Manga Divi transport. First, we need to go on the top. After we load Winston, we need to load Winston Manga DB require Winston Manga div. And here we don't care about what is exported from this module. We just need to require it. Okay with this, we can come here and call logger dot at new Winston Dot transports Manga B. We pass in options object. There are a few properties here that you can look in the documentation. The one that you need to set is DB. So we set this to the connection string of our database, Manga DB holland 127.0 0.0 0.142 7017 fair wheels. Now in a real world scenario, you may want to separate your log from your operational database. That's a decision that varies from one environment to another. Here, we are going to use the same database for logging our errors. All right, we are done with this. Now, we don't need to make any other changes. So next time there is an error in the application because we have another transport, Winston will automatically store our error in Mongadib. So let's run the application again. And back in Postman, send a request to the cars endpoint. Now, let's take a look at MongaV Compass. So here's our fairwheel database. Let's refresh. We can see we have a new collection log, and this is the message we lacked. So here's the timestamp. The level is set to error. And here's the message, not able to fetch cars. Now in the last lecture, I talked about setting a custom level on each transport separately. So here, maybe you only want to lock the errors in Mongoib. You don't want to store information messages or debug messages. If that's the case, here in the options object, you also set the level property to error. So with this, only error messages will be logged. Now, as we discussed before, if you said this to info, because info level has a priority of two and is third in the logging level, only the error warning, and info messages will be logged. Nothing beyond info will be logged in Manga Divi. 9. Uncaught Exceptions: Now, this error middleware that we have added here only catches errors that happen as a part of request processing pipeline. So this is particular to Express. If an error is thrown outside the context of Express, this middleware will not be car. Let me show you. At the bottom of this file, after the export, I'm going to throw a new error. So throw new error, something failed during startup. So this error is thrown outside the context of processing a request. It's outside the context of Express. So now, when I run this application, you will see that this error will crash the process, and Winston will not be able to store it in the log. To verify this, let me go to our log file, delete everything here, save now back in the terminal. Let's run our app. Norman, Okay, so you can see our app crashed, and here's our error. Something failed during startup. And if you look at the log file, you can see there is nothing here. So if you deploy this application to production, your application won't work, and there is no way for you to know what went wrong unless you have access to the console on the server. That's where Winston comes in. It allows you to catch and log uncut exceptions effortlessly. So in this lecture, I'm going to show you how to properly handle uncaught exceptions in a knot process. This is at a higher level. It's not tied to express. Now, back in EOD or Js, here, when creating your logger, add an exception handler's property and pass an array with a transport instance to it. So exception handlers. We set it to an array where we pass a transport instance, new Winston dot transports dot file. It takes an object with a property file name. And here we want to log the uncut exceptions in a separate file. So let me call this exceptions dot log. And that's it. Easy, right? Now, what if you have already set up a logger and want to enable exception handling later? That's no problem. You can use exceptions dot handle method. Let me show you. So I will commend this out and below, after we define our logger, we call logger dot exceptions dot handle and pass the transport instance as we did before. So new transports dot file. File Name exceptions dot lag. Now this approach is great for flexibility as your application evolves. Finally, we don't want our app to crash after logging the uncut exception. By default, Winston exits the process after logging uncaught exception. You can disable this behavior by setting exit on error property to falls when creating your logger. So here, after exception handlers, we set exit on error to falls, or you can assign it dynamically like this. Logger dot on error is false. And let me comment out this one. Now, back in the terminal, let's run this one more time. Note that this time the process did not terminate because we caught the exception here. So the process terminates if you don't catch an exception. Okay? Now, let's take a look at our exceptions log file. You can see our error message, something failed during startup. With Winston, you can easily manage uncut exceptions and control our applications behavior during unexpected errors. In the next lecture, we're going to look at unhandled promise rejections. 10. Unhandled Rejections: In the last lecture, we talked about handling uncaught exceptions. So if there is an exception in your application and you have not caught that exception using a cache block, you can use this exception handlers property of the logger to log it in a file using Winston. Just like with uncaught exceptions, Winston makes it easy to cache and log unhandled rejections. With the current setup, an unhandled rejection will also be logged into our exceptions dot Log file without crashing the app. Let me show it to you. So here in error dot Js, we are throwing an exception. Let's replace this with a rejected promise. So constant promise. We set this to promise dot, reject and pass an error object with a message, promise rejected. So imagine this process represents the result of an asynchronous operation like a call to a database or a remote TDP service, and so on. So we have a rejected promise. And as I told you before, with promises, we either call then, and then we should call catch to make sure to handle rejections, or if we are using the Async and await syntax, we await the promise, but we should put this in a try cachblock to case the exceptions or rejections. In this code, we have a promise, and I'm going to call then pass a simple callback console dot log D. But I'm not going to call cache. So we will have an unhandled rejection. Now if you run the app Nomon our app is working fine. And if you check the exceptions log file, here's our unhandled rejection promise rejected. So Winston automatically caught this as uncaught exception and logged it on exceptions dot log IL however, with Winston, you have a property rejection handlers to handle unhandled rejections separately. Let me show you how to do it. Here you can enable rejection handling when creating your logger. So just like we add exception handlers, we can add rejection handlers and pass an array of transport incense to it. So rejection handlers, set it to an array. Then we pass new Winston dot transports dot pile. It takes an object with file name. Now I want to log the rejections separately. So I'm going to call it rejections dot log, and that's it. This setup writes all your unhandled rejections dedicated rejections dot log file. Now, what if you have already set up your logger and want to handle promise rejections later? Just like exception handling, Winston provides the rejections handle method. So let me comment this out below. Just after we call logger dot exceptions, we call logger dot rejections dot handle and simply pass a transport instance new Winston dot ransport dot file filename reactions dot lag. This approach lets you add a transport specifically for rejections even after your logger is initialized. So let's test it back in the terminal node index dot JS. All right. Look at this warning, unhandled rejection. And here's our error. Now, if you look at the log, we have a new file rejections dot log. Look, here's our unhandled promise rejection, and this time, it is not logged in exceptions dot log file. Now with the current version of node, this unhandled promise rejection should terminate the nod process. But as you can see, this process is still running. We are connected to Mongo DB. This happened because of the exit on error property in our logger, which we set to falls. So whether you are dealing with an uncaught exception or an unhandled reaction, as a best practice, you should terminate the node process. So you should exit here because at this point, your process can be in an unclean state. So as a best practice, we should terminate the process and restart it to make sure we start with a clean state. Now, you might ask, if we terminate the process, how are we going to restart it in production? Well, there are tools for that, which we call process managers. And in the future, we are going to look at one of those. So I'm going to modify this code, and we simply remove the exit on error property because by default, Winston will exit after logging an uncaught exception or an unhandled rejection. One question you might have is whether you should log messages to a file or to a database like Manga Div. There are different opinions about this, but I personally believe you should use both transports because each transport has strengths and weaknesses. Manga Di B or other databases is good for quering data. So if you want to create a client application for querying your log, it's much easier to query the data in Mongo DB as opposed to a flat file like this. However, it is possible that your Mongo DB server goes down or you cannot connect to it for whatever reason. In that case, it's better use the file system because file system is always available. In a production environment, unhandled promise rejections can go unnoticed without proper handling. So this way, we created an audit trail of what went wrong and why. And that's how you handle unhandled rejections with Winston. Whether it's uncut exceptions or promise rejections, Winston helps you to maintain visibility and reliability in your application. 11. Extracting Routes: Alright, so here's the code in index or Js. The main issue we have here is lack of separation of concerns. There are so many things happening here, and that's why we have a large number of required statements on the top of this module. Below that, you can see we have some configuration code. After that, we have got something completely different, which is all about connecting to Mongo Di Ba database. Then we move on to setting up our routes in various middleware. These are different concerns. They should not be mixed together in one file or one module. In this module, we should only orchestrate these concerns. So the details of them should be moved to different modules. For example, the details of setting up routes or the details of connecting to Mongo DB database, they should be separated. So in this lecture, we are going to focus on extracting routes into a separate module. So let's create a new folder called initialize. Here, I'm going to add a new file routes dot JS, and here we should export a function. So module dot Exports. We set this to a function. Now, in this function, I'm going to add all the code for setting up our routes and other middleware. So back in index or Js, I'm going to cut all the code and move it here. So look at the dependencies here. We have a dependency to app object to express, all these routers, like companies, customers, and so on. So back in index or Js on the top on line 14, this is how we create the app object. We should have a single instance of that in the entire application. In other words, we don't want to load Express and then call it to create an app object in our new module. So we want to send a reference to this app to this new module. So this function should take app as an argument. Okay? Now, back in index or Js, here we have the app object. We can load our new module that is initialized routes. This returns a function, so we call it and pass the app object. That's it. It's all we have to do. Now let's clean up this module. So all these routers that we have imported here, like companies, customers, and so on, all these should be moved to our new module because we have not referenced them anywhere else in the index module. So cut back here, paste them on the top. We have added most of the dependencies. We also need Express and the error middleware. So we can load the Express on the top, const Express. Require Express. Now for the error middleware, I'm going to take it out of index or Js because this is the only place you're referencing this middleware function. Here's our error middleware. Cut back in routes or Js, and let's add that here. Now, back to index or Js, you can see the core in this module is already much shorter. We don't have so many required statements anymore, and also the implementation is a little bit cleaner. Now one last thing back to routes module, we need to change the paths to these routers because the routes folder is not inside the initialized folder. So anywhere we have peri slash, I'm going to replace that with period period slash. So here I have selected these two characters. In VS code, we can enable multi cursor editing. I'm holding down Control and D on Windows. If you're using Mac, the shortcut is probably Command D. So see, I'm selecting multiple instances and then we can replace them all in one go. So period period. Done. 12. Extracting the Logging Logic: Here's our error Js middleware. In this lecture, we are going to move all the code for setting up logging with different module. That is anything that is related to Winston and handling rejected promises and uncaught exceptions. So in the initialized folder, let's add a new file, logging dot js, and then back in error dot js. Take all this code for setting up Winston. Get it and move it to logging dot s right here. Now, we must export this logger, so module dot Exports. And we want to name it Logger. So logger, we set this to Logger. Now back to index dot js. I would also like to move this require statement for handling asynchronous errors in Express. I would rather put this in our logging module, which is all about handling and logging errors. So let's cut it from here and paste it in the logging dot js module. Now finally, we need to go back to index dot js and load the logging module. Require initializes Logging. Note that I put this first. So just in case we get an error in loading other modules, to make sure to lock that error and terminate the process. So we are done with this refactoring as an exercise. I want you to move all the code for dealing with database initialization to a separate module called dbdt Js. You will have my solution in the next lecture. 13. Extracting the Database Logic: Here in index dot JS, this is the only code we have for database initialization. So here in the initialized folder, let's add a new file, dw dot JS. And here we are going to export a function. So module dot Exports. We set this to a function. And then move all the database initialization code right here. Now, I'm going to make a few changes here. First of all, when we connect, I don't want to do Consult at log. I would rather log this as an informative message using Winston. So on the top, let's load our logger to require Logging. And we need to destructure logger, so Canst Cebrass logger. So why are we restructuring it? If you remember, in the logging dot js, we have assigned the logger object as a property of module dot exports. So to access the logger property, you need to destructure it. I did this on purpose for a quick refresher. Now back to db dot js. As we have our logger now, we can replace this console that log with logger dot info. Also we should remove this cache method because if we can't connect to Mongo Di B, we want to log that exception and terminate the process. With the current implementation, we are handling this rejected promise right here, and all we are doing is displaying this message on the console. So we are not logging this. We are not terminating the process. I use this here specifically for demonstration purposes, but with the new implementation, we don't need this. So let's delete this and finally, we need to import mongoose on the top. I'm going to take this out of indexed or Js module. So back in indexed or Js on the top, I'm going to remove this line for importing mongoose and then put it right here. So here's our database module. You can see the code is very clean, very short. We have a single responsibility. We don't have too many things mixed up together. Finally, we need to load this module in index or Js. So here, I'm going to call require initialize DV. Here we get a function, so we call it. Now let's verify that with our current implementation. If we can't connect to the database during the application initialization, that exception will be logged and the process will be terminated. So open a new terminal. I'm going to stop the Mongo DB process with stop service name Mongo DV. And then back to our terminal window, node, indexed or Js So here's our unhandled rejection. The process is terminated. And if you look at rejections dot log, we can see the rejection is logged here. Beautiful. So that's why I told you we should remove the cache method here and let our global error handler deal with that rejected promise. Here's your next exercise. I want you to go back to index or Js and extract all the code for dealing with configuration to a separate module. So more specifically, I'm talking about these few lines here where we look for essential configuration settings during the application initialization. You will see my solution next. 14. Extracting the Config Logic: All right. Let's start by adding a new file in the initialized folder. So config dot js. Again, we export a function. Now, back in index or Js, we take all the code for logging the configuration settings into this new module. So here we have a dependency to this config module. So I'm going to get this from index dot js and load it on the top of this module. Okay, so if we don't have this configuration setting, we don't want to log something on the console. Rather, we want to store this as a fatal error in our log. So instead of doing a console dot error and process dot exit, it's better to throw an exception, and then our current infrastructure will catch that exception, log it, and terminate the process. So we throw a new error and use the error message. Also, as a best practice, always throw error objects instead of strings, even though you can do that in JavaScript. Because when you throw an error object, this stack trace will be available for you to see later. If you throw a string with the error message, you will not have the stack trace. Okay, so that's another best practice for you to know. Now finally, let's go back to index or Js and load this new module. So require initialize, flash config. It's a function, so we call it. Now, let's test it. Before that, I need to start the Manga DV service. So back in the terminal, let's run start service name Manga DV. Okay, now we have to start afresh. So close all the terminals and open a new terminal like this and run nod index dot JS. All right, so the process is terminated, but nothing is logged on the console. Although if you look at this file, you can see why our application crashed. Patel error, JWD secret key is not defined. This is good for a production environment. But if you give this application to a new developer and they run it, they have no idea what's going on. So in our current implementation, we are using only a file transport for uncaught exceptions. So we should add a console transport as well to display uncaught exceptions on the console. So new Winston dot transports console. And that's it. Let's go back to the terminal and run the application one more time. You can see the reason our application failed is because we have not defined JWT private key. Now I want to show you something. So go back to index dot js. Look at the index dot JS file. We have only 13 lines of code here. Remember what we had before? I think we had 30 to 40 lines of code with really poor separation of concerns. With this new refactoring, we're doing only one thing, setting up the application. The details of logging, the details of routes, the details of database, and other aspects are delegated to other modules. This is a single responsibility principle in practice. Although there is still scope for refactoring, we can move this configuration of joy and this code here, which is responsible for handling the server setup and starting it. So I will leave it for you as an assignment of this section.