In this journal, we will be show casing Top 10 NodeJS Tips for Developers to help you become a better Node Developer. These tips are from the experienced Nodejs developers, who have seen and learned the language in the trenches. Here’s the brief description of what we will be covering:
Top 10 NodeJS Tips
- Avoid complexity.
- Use asynchronous code.
- Avoid blocking require.
- Know that require is cached.
- Always check for errors.
- Use try…catch only in sync code.
- Return callbacks or use if … else.
- Listen to the error events.
- Know your npm
- Use exact versions in package.json.
- Bonus — Use different dependencies. Put things your project needs only in development in
--devDependencies
and then usenpm i --production
. The more un-required dependencies you have, the greater the risk of vulnerability.
Let’s take a deep dive and understand these points in more details:
Avoid Complexity
According to one of the legends, US Navy originated a famous phrase KEEP IT SIMPLE STUPID (KISS).
You to plan and organize your code into the smallest chunks possible until they look too small and then make them even smaller. By keeping your code modularized into smaller parts, you and other developers can easily understand the code better. This can also help in testing it better. Consider the following example:
app.use(function(req, res, next) { if (req.session.admin === true) return next() else return next(new Error('Not authorized')) }, function(req, res, next) { req.db = db next() })
or this code:
const auth = require('./middleware/auth.js') const db = require('./middleware/db.js')(db) app.use(auth, db)
Most of the developers like us will prefer the second example, especially when the names are self-explanatory. You might show how smart you are by chaining several methods together in just one line. You might also think that you understand the code how it works by the way it is written. However, if you design your code like this, it will be harder for you to understand it later. So, it is always advisable to write the code in a clean and simple way. This makes sure that its easier for you and for your team to understand it better. Keeping things simple is especially true for Node which uses the asynchronous way.
Use asynchronous code
Avoid synchronous code like the plague.
Synchronous code is preferred when you are writing CLI commands or scripts that are not related to web apps. Now-a-days, most of the developers are building web-apps, therefore they are using async code to avoid thread blocking and improve the response time. Synchronous code has a very small place in Node.
For example, this might be okay if we are just building a database script, and not a system to handle parallel/concurrent tasks:
let data = fs.readFileSync('./employees.json') db.collection('employees').insert(data, (results)) => { fs.writeFileSync('./employeeIDs.json', results, () => {process.exit(1)}) })
But this would be better when building a web app:
app.use('/seed/:name', (req, res) => { let data = fs.readFile(`./${req.params.name}.json`, () => { db.collection(req.params.name).insert(data, (results)) => { fs.writeFile(`./${req.params.name}IDs.json`, results, ()={res.status(201).send()}) }) }) })
The difference is whether you are writing concurrent (typically long running) or non-concurrent (short running) systems. As a rule of thumb, always write async code in Node.
Avoid Blocking require
Put ALL your require statements at the top of the file because they are synchronous and will block the execution.
Node uses the CommonJS module format to load modules into the web-apps. The built-in require
function is an easy to include modules which exist in separate files. The Node/CommonJS way of module loading in synchronous but most of the developers don’t know that require
is cached. Till the time there are no drastic changes to the filename (and in the case of npm modules there are none), then the code from the module will be loaded into the variable and executed just once.
const react = require('react')
The way require works is: you import what was exported in that particular module, or a file. However, even with caching functionality, require
statements should always be the first statements of your app.
const axios = require('axios') const express = require('express') app = express() app.post('/connect', (req, res) => { axios.post('/api/authorize', req.body.auth) .then((response) => res.send(response)) })
Know that require is cached
As mentioned above that the require is cached, but an interesting fact is that we can also have a code outside of the module.exports.
console.log('I will not be cached and only run once, the first time')
module.exports = () => { console.log('I will be cached and will run every time this module is invoked') }
If you are sure that some of the code might need to run only once, then you can use this feature to your advantage.
Always Check for Errors
Using try...catch
in Node is useless, as Node uses the event loop and executes asynchronously. Any errors that occurred are separated from the context of an error handler (such as try...catch
). So the code like this in Node is useless:
try { request.get('/accounts', (error, response) => { data = JSON.parse(response) }) } catch(error) { // Will NOT be called console.error(error) }
But you can still use try...catch
in synchronous Node code. So a better-refactored code of the above code snippet can be like this:
request.get('/accounts', (error, response) => { try { data = JSON.parse(response) } catch(error) { // Will be called console.error(error) } })
If the request call cannot be wrapped in a try...catch
block, then it will lead the errors unhandled coming from the request
object. This type of problem has been solved by providing you with error as a callback argument. In Node, you have to manually handle the error in each and every callback. The same can be done by making sure that it’s not null, then either you display the error message to the user and logging it. You can also pass it back up to the call stack by calling the callback with error (if you have the callback and another function up the call stack).
request.get('/accounts', (error, response) => { if (error) return console.error(error) try { data = JSON.parse(response) } catch(error) { console.error(error) } })
To avoid manual error check on myriads of nested callbacks, here is a little trick that can be used. You can use the okay library.
var ok = require('okay') request.get('/accounts', ok(console.error, (response) => { try { data = JSON.parse(response) } catch(error) { console.error(error) } }))
Return Callbacks or Use if … else
As you must all developers know that Node is concurrent, this feature can turn into a bug if we are not careful. To be on the safe-side, you must terminate the execution with a return statement.
let error = true if (error) return callback(error) console.log('I will never run - good.')
Avoid some unintended concurrency (and failures) due to mishandled control flow.
let error = true if (error) callback(error) console.log('I will run. Not good!')
Just to be sure, return a callback to prevent execution from continuing.
Listen to the error Events
Most of the Node objects/classes extend the event emitter and emit the error event. This can be an opportunity for developers to catch those errors and handle them even before they wreak havoc.
You should always make it a good habit to create event listeners for error by using .on():
var req = http.request(options, (res) => { if (('' + res.statusCode).match(/^2\d\d$/)) { // Success, process response } else if (('' + res.statusCode).match(/^5\d\d$/)) // Server error, not the same as req error. Req was ok. } }) req.on('error', (error) => { console.log(error) })
Know your NPM
Many Node developers know that there is --save
(for npm install) which will create an entry in the package.json file with the version of the module and install that module. There’s also --save-dev
option available for devDependencies (Modules which are not required in production). There’s a shortcut also available for using these two options -S for –save and -D for –save-dev. We bet that most of the user’s don’t know about it.
If you are not willing to update the module after its installation then you should go ahead and remove the ^ signs which -S and -D created for you. The ^ sign will allow npm install (or npm -i) to pull the latest minor version from npm (second digit in the semver). Example: v4.2.0 to v4.3.0 is a minor release.
npm team believes in semver (semantic versioning). What we mean by semver is that they put caret ^ sign because they trust open source developers to not introduce breaking changes in minor releases. You should lock your versions and not trust them. You could use npm shrinkwrap. This command repurposes package-lock.json
into a publishable npm-shrinkwrap.json
or simply creates a new one. The file created and updated by this command will then take precedence over any other existing or future package-lock.json
files.
Comments are closed.