Thursday, March 23, 2023
HomeReactPatterns and Anti-patterns in Node.js by Aman Mittal

Patterns and Anti-patterns in Node.js by Aman Mittal


Patterns and Anti-patterns in Node.js

Revealed on Apr 3, 2022

12 min learn

cover_image

Node.js is a back-end JavaScript runtime constructed on Chrome’s V8 engine that is asynchronous and event-driven by nature. It is comparatively easy to create a REST API with Node.js and use frameworks like Specific.js. With this simplicity comes numerous flexibility. Nevertheless, you may get side-tracked on what patterns to comply with when constructing scalable network-driven purposes.

This text focuses on among the patterns and practices to comply with when constructing Node.js purposes. You’ll find out about coding model, error dealing with, loggers, and testing.

Let’s dive in!

Node.js Coding Fashion and Finest Practices

🔗

const and let Key phrases to Declare Variables

🔗

There are alternative ways to declare variables in JavaScript: the old fashioned var and the more moderen let and const.

var declares a function-scoped (when declared inside a operate) or globally-scoped variable (when declared outdoors a operate).

let and const declare block-scoped variables.

let lets you create variables whose worth can change. When pointing to an object, it may be assigned to a different object.

1let myInt = 3;

2myInt = 6;

3console.log(myInt);

4let myArray = [0, 1, 2, 3];

5console.log(myArray);

6let myOtherArray = ['one', 'two', 'three'];

7myArray = myOtherArray;

8console.log(myArray);

The const key phrase could be a little complicated. It would not essentially outline a relentless worth, it defines a relentless reference to a price. It creates a read-only reference to a price, however this doesn’t suggest the worth it holds is immutable, simply that it can’t be reassigned.

1const myInt = 3;

2myInt = 6;

3const myArray = [0, 1, 2, 3];

4console.log(myArray);

5myArray[0] = 'eleven';

6console.log(myArray);

7

8let myOtherArray = ['one', 'two', 'three'];

9myArray = myOtherArray;

As proven above, if it holds a primitive, you can not assign it one other worth. When it holds an object/array, you possibly can alter the worth of that object (its properties/parts), however you can not assign it one other object.

With the definitions down, let’s take a look at why it is best to think about using let and const over var.

  1. Duplicate variable declarations utilizing var is not going to set off an error.

With var you possibly can declare a variable in the identical scope as a equally named variable. Due to this, you possibly can unknowingly overwrite one other variable’s worth.

1operate thisFunction() {

2 var x = 1;

3

4

5 var x = 2;

6

7 console.log(x);

8}

9

10thisFunction();

Each const and let can’t be re-declared, so you can not by chance create a reproduction variable in the identical scope.

1operate thisFunction() {

2 let x = 1;

3

4

5 let x = 2;

6

7 console.log(x);

8}

9

10thisFunction();

Should you attempt to run the above code, you’re going to get the next error:

SyntaxError: Identifier 'x' has already been declared

  1. var lets you learn a variable that has not been declared.

If you attempt to entry a var earlier than it’s declared, it can return undefined. This would possibly trigger bugs while you attempt to use a variable in your code that has not been declared. Monitoring down the bug could be tough for the reason that code would possibly trigger no errors that can trigger it to crash, nevertheless it would possibly trigger sudden outcomes while you use the undefined.

The next code will run simply positive.

1console.log(bar);

2var bar = 1;

With let and const, you will not have the ability to use a variable that has not been declared.

1console.log(foo);

2let foo = 2;

Attempting to run the above will give the under error:

ReferenceError: Can not entry 'foo' earlier than initialization

  1. As a result of they’re block-scoped, let and const make for extra readable and simple code, that’s much less error-prone.
    With block-scoped variables, it’s simpler to learn by means of code and observe down the scope through which a variable operates. You simply have to take a look at the inner-most block through which it has been declared to know its scope.

Take a look at the next code.

1let x = 5;

2

3operate thisFunction() {

4 let x = 1;

5

6 if (true) {

7 let x = 2;

8 }

9

10 console.log(x);

11}

12

13thisFunction();

14

15console.log(x);

Since let x = 2; is said contained in the block of the if assertion, you realize it solely operates inside that block. As you possibly can see, it would not have an effect on equally named variables outdoors the block. You possibly can declare variables inside blocks with out worrying that you just could be re-declaring them.

When utilizing var, it is not so easy.

1var x = 5;

2

3operate thisFunction() {

4 var x = 1;

5

6 if (true) {

7 var x = 2;

8 }

9

10 console.log(x);

11}

12

13thisFunction();

14

15console.log(x);

With var, you must be extra cautious with variables.

Within the above, we declare a variable var x = 2; contained in the if assertion. The scope of x is all the operate thisFunction(). Since there’s a equally named variable within the operate, we re-declared x, and after we later use the operate’s x, it has the worth 2. So that you want to concentrate on the variables in scope, in order to not by chance overwrite them.

Correct Naming Conventions

🔗

It is necessary to comply with a naming conference when naming constants, variables, courses, and features in an app. This helps you visually differentiate between native variables, international variables, features, courses, and so on., and keep a constant model all through your codebase.

For naming native variables and features, use lowerCamelCase.

1const myFunction() {

2 let someVariable;

3}

Even when you outline variables utilizing the const key phrase throughout the scope of a operate, lowerCamelCase is most popular.

1const myFunction() {

2 const someVariable = "That holds a string worth";

3}

Variables will also be outlined by a const key phrase in a selected use case. Should you intend to declare a variable whose worth (or nested values, within the case of declaring an object) is just not going to vary all through the lifecycle of a codebase, use UPPER_SNAKE_CASE with the const key phrase.

Outline courses in Node.js purposes with UpperCamelCase:

Following these naming conventions will enable you write extra readable code. Naming your features is important, particularly when you’re about to profile a Node.js mission. Profiling makes it less complicated to know what operate to search for when checking a reminiscence snapshot. Nevertheless, when you use nameless features, profiling could make it difficult to debug manufacturing points.

ESLint and Fashion Guides

🔗

As a substitute of overthinking a mission’s coding model, use a linting instrument like ESLint. Through the years, it has develop into the JavaScript ecosystem’s customary for fixing code kinds mechanically. ESLint checks for attainable code errors, fixes code kinds resembling spacing points, avoids anti-patterns and small errors, and retains mission code uniform. Utilizing ESLint with a instrument like Prettier will help you repair formatting points as nicely.

By default, ESLint accommodates customary guidelines for vanilla JavaScript. It has a plugin system particular to the framework. For Node.js, you should use plugins like eslint-plugin-node and eslint-plugin-node-security.

It’s a lot simpler to know a big mission when its code is written in a constant model. That is the place model guides turn out to be useful. Utilizing a mode information enhances a staff’s productiveness and avoids arguments about one of the best model information for Node.js initiatives. As well as, you possibly can opt-in to already current model guides created at firms like Google and Airbnb which were examined with time.

Error Dealing with in Node.js

🔗

You possibly can deal with errors utilizing async/await syntax and the built-in error object in Node.js. Let’s check out each.

async/await Syntax to Catch Errors

🔗

When Node.js first got here out, dealing with asynchronous code meant utilizing callbacks. From my expertise, it would not take too lengthy for nested callbacks to get out of hand. This is called ‘callback hell’, and here’s a typical instance:

1operate getData(err, operate(err, res) {

2 if(err !== null) {

3 operate(valueA, operate(err, res) {

4 if(err !== null) {

5 operate(valueB, operate(err, res) {

6

7 }

8 }

9 })

10 }

11})

The instance above is sort of ergonomic. In an actual situation, there will probably be many extra strains of code in every operate’s scope. That is thought-about an anti-pattern: dealing with the callback model of errors will get extra awkward and solely will get extra un-maintainable with extra nested features.

You possibly can keep away from nested callbacks or callback hell through the use of ES6 async/await syntax (fully supported by Node.js model 8 and onwards). async/await is a method to cope with asynchronous code. It supplies a way more compact method of writing code and acquainted code syntax. To deal with errors, you should use attempt/catch blocks together with async/await syntax.

If we use async/await, we are able to rewrite the earlier instance like this:

1async operate getData(err, res) {

2 attempt {

3 let resA = await functionA(res);

4 let resB = await functionB(resA);

5

6 return resB;

7 } catch (err) {

8 logger.error(err);

9 }

10}

Constructed-in Node.js Error Object

🔗

Errors are not possible to keep away from. Nevertheless, in lots of circumstances you will need to deal with errors resembling rejected guarantees and thrown exceptions.

To keep away from issues in error-handling, use the built-in error object in Node.js. It helps you keep uniformity and forestall lack of data. You may also reap some great benefits of discovering data with StackTrace.

For instance, throw a string as proven under:

1if (!knowledge) {

2 throw 'There isn't a knowledge';

3}

This lacks any stack hint data and is an anti-pattern.

As a substitute, use the built-in Error object:

1if (!knowledge) {

2 throw new Error('There isn't a knowledge');

3}

Loggers for Your Node.js Venture

🔗

There is no denying it – we have all used console statements at occasions. They are often good for rapidly debugging one thing or printing a typical output. However the console lacks correct configuration choices for production-grade purposes.

Additionally it is essential for a logger to be high-performant in figuring out errors and attainable points. A sluggish logging library would possibly hurt your utility’s runtime efficiency.

A typical logger helps you to use right log ranges resembling deadly, warn, information, error, debug, and hint. These ranges assist to determine and distinguish between completely different vital occasions. A logger may also assist present contextual data in a JSON object, with timestamped log strains so that you can decide when the log entry occurred. The logging format needs to be readable by human beings.

A superb logging library supplies options that make it simpler to centralize and format logs. Within the Node.js ecosystem, the next are among the choices out there:

  • Winston: A preferred logging library that’s simply configurable.
  • Bunyan: One other in style logging library that outputs in JSON by default.
  • Log4js: A logger for the Specific framework that helps coloured console logging out of the field.
  • Pino: A logger that’s centered on efficiency. It’s thought-about to be quicker than its options.

An instance of configuring Pino:

1const app = require('categorical')();

2const pino = require('pino-http')();

3

4app.use(pino);

5

6app.get('/', operate (req, res) {

7 req.log.information('one thing');

8 res.ship('howdy world');

9});

10

11app.pay attention(3000);

Pino additionally helps numerous Internet frameworks within the Node.js ecosystem, resembling Fastify, Specific, Hapi, Koa, and Nest.

Writing Exams in Node.js

🔗

Should you work on a giant utility, you will make steady modifications to the app’s supply code. By writing exams, you possibly can keep away from breaking current options when pushing a brand new change. Failing exams may also enable you decide the place to make modifications in particular sections of your code.

Write API Exams

🔗

In a Node.js utility, writing API exams is an efficient begin. They supply extra protection than unit testing. You need to use frameworks like Supertest, Jest, or some other library that gives a high-level abstraction for testing APIs.

Think about the instance under. It’s a easy Specific app that serves one route:

1const categorical = require('categorical');

2const bodyParser = require('body-parser');

3

4const app = categorical();

5

6app.use(bodyParser.json());

7app.use(bodyParser.urlencoded({ prolonged: true }));

8

9

10

11app.get('/', (req, res, subsequent) => {

12 res.json({ howdy: 'Howdy World' });

13});

14

15module.exports = app;

Here is applicable method to write this utilizing Supertest:

1const request = require('supertest');

2const app = require('./index');

3

4describe('howdy check', () => {

5 it('/ ought to return a response', async () => {

6 const res = await request(app).get('/');

7 anticipate(res.statusCode).toEqual(200);

8 anticipate(res.physique).toEqual({ howdy: 'Howdy World' });

9 });

10});

Write Clear Check Names

🔗

A check identify needs to be descriptive and self-explanatory for different individuals working in your staff and embody what’s being examined, the situation, and anticipated consequence.

Examine Outdated Packages

🔗

You possibly can test for outdated packages with instructions like npm outdated or use a package deal like npm-check. This can stop construct fails associated to outdated packages.

Examine for Susceptible Dependencies

🔗

A package deal can have vulnerabilities. Use community-based instruments resembling npm audit or industrial instruments like snyk to find vulnerabilities. Should you do not use these instruments, your solely various is to maintain up with tech communities on-line.

Wrap Up: Write Higher Code for Your Node.js Apps

🔗

On this article, we coated practices and patterns that can assist you keep away from anti-patterns and write higher code to your Node.js purposes.

We checked out some key rules round coding model, error dealing with, loggers, and testing. A few of the practices we mentioned are extra normal – like checking for outdated packages or weak dependencies. Others – resembling utilizing a performant logging library utilizing ESLint and magnificence guides – will enable you keep a constant method of writing code, particularly when engaged on giant initiatives.

Blissful coding!


I am a software program developer and a technical author. On this weblog, I write about Technical writing, Node.js, React Native and Expo.

At the moment, working at Expo. Beforehand, I’ve labored as a Developer Advocate, and Senior Content material Developer with firms like Draftbit, Vercel and Crowdbotics.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments