You developed your shiny web application with Node.js, but the work doesn’t stop there. App maintenance can be just as complicated as the coding process. Problems and malfunctions can lead to discouraging clients from using your app. However, if you handle all the issues properly, there is nothing to worry about.
Improper maintenance of an application can result in issues related to stability or flexibility, often leading to the app’s failure. If the code is not well-written or if developers use outdated tools, the performance can suffer, and users might experience more bugs and app crashes. On top of that, poor-quality code can hamper the app’s scaling capacity and the further development of the application. In the worst case scenario, it might become impossible to introduce new features without rewriting the codebase from scratch. In business terms, you will have to put more resources into technology and prepare for a much longer development process.
Why to Use Node.js: Pros and Cons of Choosing Node.js for Back-end DevelopmentOn the other hand, efficient maintenance can make your application stable and flexible, so you can easily build new functionalities and improve the existing ones. It also translates into lower costs and faster development.
Node.js doesn’t provide any specific convention for developing the application. Frameworks that use Node.js are mostly unopinionated, meaning that they don’t give you any specific guidelines for the way code should be written. That’s why each application requires an individual approach and, as a result, more experienced programmers who have worked out good processes for developing and maintaining code internally.
Many problems with Node.js stem from the low quality of packages and modules. The open-source community actively supports the Node.js environment, adding new libraries and updating the existing ones. However, the ecosystem is still relatively immature. Many modules might be abandoned and include some unfixed bugs. Applications using such packages might accumulate technical debt, which makes it difficult to maintain them.
Node.js is a single-threaded process, which makes scaling a little bit more complicated. Developing more complex applications with CPU/MEM-heavy computations might require dividing it into smaller microservices that handle different operations. Yet, many product owners keep their Node.js applications in a monolithic structure, which can result in worse performance.
Documentation is crucial to every IT project. It gives you an idea of how the app works. It tells developers what the main components are, how they relate to each other, and what the main purpose of the application is. It provides explanations as to why certain solutions, especially the less obvious ones, were applied. Poor documentation will very likely extend the development time, and make the whole process more difficult. It can expose the application to problems with performance and inhibit the implementation of new features.
The first thing you need to do when you notice that something is not working properly in the application is to run a code review, regardless of the stack you use. Code review will tell you a lot about the quality of the code and the stack, and about the application in general. It should always be the first step in the process of discovering problems and potential solutions. Having conducted a successful code review, you can narrow down the real problem and see whether it lies in the performance, scalability, architecture, or flexibility.
When fixing the issues with your application, you should also specify the expected outcome and your resources: time and money. Once you have a clear idea of what you want to achieve, and how much you can spend on it, it will be much easier for your team to choose the optimal solution.
If the actual problem lies in a monolithic structure of your app, you should extract microservices out of the app so that they can work separately. Each mini-application should serve its own purpose, for example: API, communicating with the database, fetching data from external services, etc. It will significantly streamline scalability, because you will be able to move services around different machines. If one of them needs extra computing power (e.g. video transcoding), you can delegate this as a service to a more powerful machine.
Microservices also improve flexibility. If your app connects to an external API to fetch some data from there, and you’ve decided to change the data provider, you just need to make a few changes to the microservice instead of reworking the whole app. It’s faster and more efficient. You don’t have to dig into the whole application to modify this, neither do you have to deploy the whole app – you only work on the part you’ve modified. It’s especially useful when you hire a new team to implement a change in one microservice. Your dev team doesn’t need to go over the whole structure, just the part that needs an update.
Microservices have some cons that you will need to consider, though. Depending on the type of the application, using microservices might be more problematic than in the case of a monolithic app. It will require more time for devs to get to know the app. They will need to dive into all the repositories, which takes more time when you hire a new team. Microservices also bring more complexity when it comes to deployment and testing.
If the application works decently, but the overall code quality seems to be low or inconsistent, you should first introduce some conventions. Clean up the file structure by dividing it into logical units. It’s good to implement static code analysis tools (e.g. Codebeat). Make sure that the deployment process is driven by a properly configured Continuous Integration. Put some linters into the pipeline to make sure you don’t push poor or inconsistent code to the production.
If you need to implement or rewrite some functionalities, make sure your app is tested before you start your work. All crucial parts of the application should be properly tested in advance. Imagine that you need to rewrite a part of user management logic in your application. It will be much easier to implement it properly, if you are certain that this part has been fully tested. In this case, if something stops working after your changes, you’ll know about it immediately.
Good documentation for your application is crucial for the efficient development and good communication with the team. That’s why you should ensure that the documentation is always updated and covers all the general information about the app and the key facts. Good documentation needs to be easily understood and should contain accurate information that helps users get the most out of your software. It should be easily accessible to anyone in the team, and it should explain procedures and problems. If your documentation lacks some important information make sure you update it as soon as possible.
Upgrading Node.js to the latest version is pretty straightforward. It can be updated by devops, unless something is not working well – then you might need the help of some developers. Keeping the Node.js version up-to-date can boost your app’s performance (but doesn’t necessarily have to). It also gives the developers the ability to work with modern native solutions, such as async/await and other ES6/7 features, which can make the development process faster. There will be no need to setup transpilers, you will use fewer third-party libraries, and have a more bulletproof stack.
That said, updating Node.js version is not what you should worry about. It rarely generates backward compatibility problems. Most issues result from using outdated open-source modules. The application can use libraries that are not supported or maintained anymore, which, in Node.js world, will accrue technological debt pretty fast. If you have found any outdated libraries, first of all, check if they’re still actively maintained. If so, prepare a plan for an update. You have to be sure that it won’t break anything. Check whether the app has been tested through and through – a test coverage tool will be helpful here. But what if the library is not maintained at all? Look for alternatives, and if there aren’t any, make sure that your tests have you covered, and you’ll be the first one to know if something stops working.
If the app is not performing very well, you need to check the roots of the problem. See whether it’s an architecture problem, for instance, the machine is too weak, or autoscaling is not implemented, or whether the problem lies in the code itself or maybe just a part of it. In each case, you need to narrow down the problem first. What you will have to do will depend on the results of your investigation – it may be implementing autoscaling, changing the architecture, or rewriting some parts of the code. A single universal cure doesn’t exist. But there’s one thing you should always have in your belt: a full-featured monitoring service such as NewRelic. Nothing will help you better in getting to the heart of the problem than logging the application’s behaviour.
In one of our commercial projects, we started from one, monolithic B2C application which extensively used Salesforce API, crons, websockets, and a lot of external service integrations. It was a perfect fit for that time. However, a little bit later, the client decided to focus mainly on B2B. Such shift meant that the business logic of the application became completely different. Still, some of the functionalities such as querying the Salesforce API, DB operations, crons, or event handling remained the same. Therefore, we’ve decided to split the monolithic B2C application into several reusable microservices (e.g. a Salesforce API service, DB service, synchronisation service) that could be leveraged in both B2B and B2C apps.
This gave us much more flexibility when writing the B2B variant of the application. On top of that, we avoided a lot of code duplication, thanks to the sharing of microservices. Right now, any change we make is immediately reflected in both apps. Another advantage is that when a service needs more power, we can scale only that particular services, avoiding incurring unnecessary costs.
Node.js is a great fit for many solutions, but as any other technology, it may cause problems if it isn’t maintained properly. Whenever you encounter a problem, dig into the code, verify the documentation, and discuss the application with an experienced team. The vast majority of issues with Node.js applications can be detected with a code review and solved with a right approach.