📜 ⬆️ ⬇️

Surrealism on javascript. NodeJS Development Tips

Hi, Habra!

Half a year ago, I thought, “Can a book be written?”, And I did write it.


')
All documents are drawn up, pages are laid out, and circulation is printed. I will not beg for money from you on a kickstarter or offer to buy something, but instead try to intrigue with tips on developing at NodeJS for the purpose of public relations and drawing attention to the book.

Tip 1. SQL queries are best kept formatted.


SQL queries are best kept formatted, because the code in this case is much easier to read and edit. Since SQL queries are usually quite long, it is better to split them into several lines, and lines in JavaScript look best in an array.

Before:
var query = "SELECT g.id, g.title, g.description, g.link, g.icon, t.id as tag_id, c.title as comment_title FROM games AS g LEFT JOIN tags AS t ON t.game_id = g.id LEFT JOIN comments AS c ON c.game_id = g.id WHERE g.id = $1"; 

After:
 var query = [ "SELECT ", " g.id, g.title, g.description, g.link, g.icon, ", " t.id as tag_id, c.title as comment_title ", " FROM games AS g ", " LEFT JOIN tags AS t ON t.game_id = g.id ", " LEFT JOIN comments AS c ON c.game_id = g.id ", " WHERE ", " g.id = $1" ]; 

Agree that in the second case the request is much clearer for the reader. In addition, if you before the request in the database output the request to the console, the array, prokinut in console.dir (), again much clearer than the string, prokinut in console.log ().



Tip 2. Life becomes easier when the API of the various components takes as input any format.


Suppose we have created a client to the database and want to execute a certain SQL query. At the entrance we expect a string and parameters. Since Using the previous advice, we decided to store long queries in arrays, we would like to forget about its conversion to a string on each query.

Before:
 dataBase(query.join(""), parameters, callback); 

After:
 dataBase(query, parameters, callback); 

The code becomes simpler when the database query function (in this case, dataBase) itself checks in what form the request is sent to it, and if it is an array, join () itself does it.

Tip 3. Paint the console and format the output.


Debugging programs on NodeJS is difficult, because there is a very lack of a standard developer console with all the chips, such as “breakpoints”. All data is written to the console, and I want to make it more understandable. If your node is spinning somewhere on the server, and even in several instances, and even several different services hang on each instance, and you only have access via SSH, then the console can really make you suffer.

If the NodeJS output to the console a string of the form “Hello world!” With control ANSI characters, it will be colored in different colors. An example of using ANSI control characters:



In order not to remember such hacks, you can connect the colors module and use the following syntax:

 console.log("Error! Parameter ID not found.".red); 

The string will be displayed in red. When developing with this module, you can color the messages in the console in different colors:

The console before the color selection (with fast viewing information is perceived with difficulty):



The console after the color selection (with quick viewing information is perceived quite quickly):



Thanks to the colored console, it will be much easier for you to monitor the status of the server. In addition, if you have several services that work with different processes on one instance, you should also write separate methods in the modules for output to the console. For example:

 var book = { console: function(message, color) { console.log(("Book API: " + (message || ""))[(color || white")]); } } 


If all messages in the console are signed by the modules that send them, you can not only select a specific module, but also instantly find the source of the bug in a critical situation. Text formatting also simplifies the perception of information:



Thus, all the information in the console will be signed, and you can easily understand what events are happening in various modules. Again, color highlighting helps in stressful situations, when this or that system failed and an urgent need to be corrected, and there is no time to read the logs (I do not call you to debug in production, just anything happens). In my projects I decided to rewrite the console module in order to be able to color not only strings, but also arrays and objects, as well as automatically sign all instances. Therefore, when connecting a module, I give it the name of the package with which to sign messages. An example of using a new console module:

 var console = require("./my_console")("Scoring SDK"); console.green("Request for DataBase."); console.grey([ "SELECT *", " FROM \"ScoringSDK__game_list\"", " WHERE key = $1;" ]); 

Example of data output to the console:



Tip 4. Wrap all APIs in try / catch


Our work uses the express framework. What was my surprise when I found out that there is no try / catch wrapper in the standard router object. For example:
  1. You wrote a curve module
  2. Someone tugged at his URL
  3. You have a server down
  4. WTF !?


Therefore, always wrap the external modules API in try / catch. If something goes wrong, your curve module, at least, will not fill up the entire system. The same situation on the client with the template "mediator" (it is also called "listeners and publishers"). For example:


It is much better to do a bust in try / catch and if module B really falls with an error, then at least it will not kill the system and module B will do its job after hearing the event.

Since when writing API modules, I had to separate private and public methods again and again, and after wrapping all public methods in try / catch, I decided to automate this business and wrote a small module for auto-generating APIs. For example, we throw in it an object of the form:

 var a = { _b: function() { ... }, _c: function() { ... }, d: function() { ... } } 


From the naming of methods it is clear that the first two are private, and the last is public. The module will create a wrapper to call the latter, of the form:

 var api = { d: function() { try { return ad(); } catch(e) { return false; } } }; 

Thus, I began to generate a wrapper for the API of all modules, which in the event of an error did not pass it further. This made the code more stable, because the error of an individual developer, merged into production, could not drop the entire server with all its functionality.

API generation example:
 var a = { _b: function() { ... }, _c: function() { ... }, d: function() { ... } } var api = api.create(a); api.d(); //   


Advice 5. Collect requests in configs


I think every web developer had a situation where there was a fat client who needed a small API to work with the database on the server. A couple of requests for reading, a couple for writing, and a few more for deleting information. There is usually no logic in such a server, and it is simply a set of requests.

In order not to write wrappers for such operations every time, I decided to put all the requests in JSON, and arrange the server in the form of a small module that provides me with an API for working with this JSON.

An example of such a module under express:

 var fastQuery = require("./fastQuery"), API = fastQuery({ scoring: { get: { query: "SELECT * FROM score LIMIT $1, $2;" parameters: [ "limit", "offset" ] }, set: { query: "INSERT INTO score (user_id, score, date) VALUES ...", parameters: [ "id", "score" ] } }, profile: { get: { query: "SELECT * FROM users WHERE id = $1;", parameters: [ "id" ] } } }); 

Probably, you have already guessed that the module will run through JSON `and look for objects with properties of query and parameters. If such objects are found, it will create a function for them that will check the parameters, go to the database with queries, and send the result to the client. At the output, we get the following API:

 API.scoring.get(); API.scoring.set(); API.profile.get(); 

And we will tie it to the router object:

 exports.initRoutes = function (app) { app.get("/scoring/get", API.scoring.get); app.put("/scoring/set", API.scoring.set); app.get("/profile/get", API.profile.get); } 

I will not build my framework for this purpose, because The technology stack on the server is different in different firms. To work with such an object, in any case, you will need to write a small binding, on top of something, to process requests and work with the database. In addition, it is possible that at the moment when you read these lines there will already be several ready-made frameworks for this task.

Now imagine that you have two more server developers. One writes in PHP, and the second in Java. If you have all server-side logic limited to just such JSON, with a list of requests to the database, then you can instantly transfer / deploy a similar API not only on another machine, but in a completely different language (provided that communication with the client is standardized and all communicate via REST API).

Tip 6. Bring everything to configs


Since I like to write configs, I have repeatedly had a situation where the system has standard settings, settings for a particular case and settings that occurred at a given point in time. I had to do mix of different JSON objects. I decided to select a separate module for these purposes, and at the same time I added the ability to take JSON objects from a file, since storing the settings in a separate json file is also very convenient. Thus, now, when I need to make settings for something, I write:

 var data = config.get("config.json", "save.json", { name: "Petr", age: 12 }); 

As you might have guessed, the module enumerates the arguments passed to it. If it is a string, then it tries to open the file and read the settings from it, if it is an object, then it immediately tries to cross it with the previous ones.

In the example above, we first take some standard settings from the config.json file, then overlay the saved settings from the save.json file on them, and then add the settings that are relevant at the time. At the output we get a mix of three JSON objects. The number of arguments passed to the module can be anything. For example, we can ask to bring only the default settings:

 var data = config.get("config.json"); 


Tip 7. File Handling and Social Link Module for SEO


One of the main features that I like about NodeJS is the ability to work with files and write parsers in JavaScript. While the API NodeJS provides many methods and ways to solve problems, but in practice - you need very little. For half a year of active work with parsers, I used only two commands - read and write to a file. Moreover, in order not to suffer with callback-functions and various problems of asynchrony, I always did all the work with files in synchronous mode. This is how a small file handling module appeared, the API of which was very similar to localStorage:

 var file = requery("./utils.file.js"), //   text = file.get("text.txt); //     file.set("text.txt", "Hello world!"); //     

On the basis of this module of work with files, other modules will appear. For example, a module for SEO. In one of the past articles, I already wrote that there are a huge number of different meta tags related to SEO. When I started writing a build system for HTML applications, I paid special attention to the CEO.

The bottom line is that we have a small text file describing the site / application / game and directly the HTML file to parse. The Social Link module should find all meta tags related to SEO in the HTML file and fill them out. The external API of the module is waiting for text from the file. This was done in order to be able to connect it to the assembly systems and run through it the text of several files without causing an extra read / write procedure to the file each time.

For example, before the module:
 <title></title> <meta property="og:title" content=""/> <meta name="twitter:title" content=""/> 

After the module:
 <title> </title> <meta property="og:title" content=" "/> <meta name="twitter:title" content=" "/> 

A list and description of all meta-tags for SEO and not only, you can see in the book http://bakhirev.biz/ .

Tip 8. Life is easier without callbacks.

Many developers complain about endless chains of callbacks when writing a server on NodeJS. In fact, you are not always obliged to write them, and it is often possible to build an architecture in which such chains will be minimal. Consider a small task.

Task:
Transfer files from server A to server B, get some information from the database, update this information, send data to server B.

Decision:
This is a rather routine procedure that I repeatedly had to perform to solve any sorting / processing tasks for content. Usually, developers create a loop and some callback function with the nextFile () method. When at the next iteration we call nextFile (), the callback mechanism starts from the beginning, and we process the next file. As a rule, it is required to process only one file at a time and, if the procedure is completed successfully, proceed to processing the next file. The code of the form will help us to simplify nesting:

 var locked = false, index = 0, timer = setInterval(function() { if(locked) return; locked = true; nextFile(index++); }, 1000); 

Now we will try once a second to start processing the file. If the program is free, it will set locked to false and we will be able to launch the next file for processing. Such constructions often help reduce nesting, distribute the load over time (since the next iteration of processing is started no more than once per second) and at least a little to slip from endless callbacks.

Total
Files with modules can be downloaded here: http://bakhirev.biz/_node.zip (now it's 2 am and I am too lazy to deal with GitHub and bring the code into a human view).
Book here: http://bakhirev.biz/
In case of a habro effect here in PDF.

If the tips above came to your taste, then I want to immediately warn you that the book is completely different. And there is also at the end a list of various smart people who have made an invaluable contribution without knowing it, and which you should definitely find and read separately.

Source: https://habr.com/ru/post/236787/


All Articles