code-best-practices
When preparing for a job interview that involves Node.js, it's essential to be aware of best practices related to its built-in commands and libraries. Here are 15 best practices that you could discuss:
Use Asynchronous Methods: Prefer asynchronous methods over synchronous ones to avoid blocking the event loop, especially with
fs
andcrypto
modules.Handle Errors Gracefully: Always handle errors in callbacks and promises. Use
try/catch
withasync/await
to ensure proper error handling.Stream Large Files: Use streams from the
fs
module to read and write large files to prevent memory overflow.Utilize Promises and Async/Await: Use the
util.promisify
to convert callback-based functions to promise-based ones for better flow control withasync/await
.Avoid Global State: Minimize the use of global variables or require caches to avoid state-related bugs.
Use
Buffer
Safely: When dealing withBuffer
, be cautious with methods that change the underlying memory, and avoid using deprecated methods likenew Buffer()
.Modularize Code: Use the
module
system to create reusable and isolated code parts.Use Environmental Variables: Use
process.env
to manage configuration and secrets, keeping them out of codebases.Secure HTTP Headers: Use the
helmet
package to set secure HTTP headers for Express applications.Optimize Performance with Clustering: Utilize the
cluster
module to run multiple Node.js worker processes on a multicore system, improving performance.Manage Child Processes: Use the
child_process
module effectively to manage external processes, ensuring they do not hang or affect the main process.Control Memory Usage: Monitor and control memory usage with
global.gc()
andprocess.memoryUsage()
to avoid leaks.Implement Proper Logging: Utilize
console
for basic logging or more sophisticated logging libraries to aid in debugging and monitoring.Profiling and Debugging: Use built-in tools like
node --inspect
and profilers to debug and optimize your applications.Keep Node.js Updated: Regularly update Node.js to the latest stable version to benefit from performance improvements, security patches, and new features.
For an interview, you should be prepared to discuss how you've applied these best practices in past projects or how you would use them in hypothetical scenarios. Additionally, be ready to demonstrate your understanding of Node.js's event-driven architecture and non-blocking I/O model, as these are fundamental concepts that inform many of these best practices.
Here are short code snippets exemplifying each of the Node.js best practices mentioned:
Use Asynchronous Methods:
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);
});Handle Errors Gracefully:
process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection:', error);
});Stream Large Files:
const fs = require('fs');
const stream = fs.createReadStream('largefile.txt');
stream.on('data', (chunk) => {
console.log(chunk);
});Utilize Promises and Async/Await:
const { promisify } = require('util');
const fs = require('fs');
const readFileAsync = promisify(fs.readFile);
async function readAsync() {
try {
const content = await readFileAsync('example.txt', 'utf8');
console.log(content);
} catch (err) {
console.error(err);
}
}Avoid Global State:
// Instead of using a global variable, pass state through function arguments
function processData(input) {
// process the input
}Use
Buffer
Safely:// Use Buffer.from() instead of new Buffer()
const buffer = Buffer.from('some data', 'utf8');Modularize Code:
// greeting.js
module.exports = function greet(name) {
return `Hello, ${name}!`;
};
// app.js
const greet = require('./greeting');
console.log(greet('World'));Use Environmental Variables:
console.log(process.env.NODE_ENV); // 'development' or 'production'
Secure HTTP Headers:
const helmet = require('helmet');
const express = require('express');
const app = express();
app.use(helmet());Optimize Performance with Clustering:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World\n');
}).listen(8000);
}Manage Child Processes:
const { exec } = require('child_process');
exec('ls -lh', (err, stdout, stderr) => {
if (err) {
console.error(`exec error: ${err}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});Control Memory Usage:
if (global.gc) {
global.gc();
} else {
console.log('Garbage collection is not exposed');
}Implement Proper Logging:
console.log('Log message');
console.error('Error message');Profiling and Debugging:
// Start your Node.js application with `node --inspect app.js`
Keep Node.js Updated:
# Use nvm (Node Version Manager) to update Node.js
nvm install node # "node" is an alias for the latest version
nvm use node
Each of these snippets is a direct implementation of the respective best practice. Remember to tailor the code to the specific needs of your project and environment.
Certainly! Here are ten more best practices for Node.js with accompanying code snippets:
Validate Environment Variables:
require('dotenv').config();
const requiredEnvs = ['PORT', 'DB_CONNECTION_STRING'];
requiredEnvs.forEach((env) => {
if (!process.env[env]) {
throw new Error(`Environment variable ${env} is missing`);
}
});Gracefully Shutdown:
process.on('SIGTERM', () => {
console.log('SIGTERM signal received: closing HTTP server');
server.close(() => {
console.log('HTTP server closed');
});
});Avoid Blocking the Event Loop:
setImmediate(() => {
// Perform a CPU-intensive task here without blocking the event loop
});Use Dependency Injection:
class Database {
constructor(connectionString) {
this.connectionString = connectionString;
}
// Database methods...
}
class UserService {
constructor(database) {
this.database = database;
}
// User service methods that use the database...
}
const db = new Database(process.env.DB_CONNECTION_STRING);
const userService = new UserService(db);Rate Limiting to Prevent Abuse:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});
app.use('/api/', apiLimiter);Avoid Using
*
inrequire()
:// Instead of
const _ = require('lodash');
// Use specific functions to avoid bundling the entire library
const { map, reduce } = require('lodash');Optimize Use of JSON:
const data = JSON.parse(fs.readFileSync('data.json', 'utf8'));
// When sending a large JSON response
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(data));Perform Input Validation:
const Joi = require('joi');
const schema = Joi.object({
username: Joi.string().alphanum().min(3).max(30).required(),
password: Joi.string().pattern(new RegExp('^[a-zA-Z0-9]{3,30}$'))
});
// Validate a user input object
const { error, value } = schema.validate({ username: 'abc', password: '123' });
if (error) throw error;Implementing Caching:
const NodeCache = require('node-cache');
const myCache = new NodeCache();
function getCachedData(key) {
if (myCache.has(key)) {
return myCache.get(key);
} else {
// Retrieve data from the source, then cache it
const data = dataSource.getData(key);
myCache.set(key, data);
return data;
}
}Avoid Hard-Coding Values:
const DEFAULT_PORT = 3000;
const port = process.env.PORT || DEFAULT_PORT;
app.listen(port);
Remember that these are basic implementations and should be tailored to your specific use case and environment. Each snippet aims to illustrate the best practice in a straightforward manner.
Sure, here are 10 additional Node.js best practices with code examples:
Use Async/Await for Asynchronous Code:
const fs = require('fs').promises;
async function readConfig() {
try {
const data = await fs.readFile('./config.json', 'utf8');
const config = JSON.parse(data);
// Do something with config
} catch (error) {
console.error('Error reading config:', error);
}
}Utilize Stream for Large Data:
const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {
const stream = fs.createReadStream('./large-file.txt');
stream.pipe(res);
});
server.listen(8000);Use Environment Variables for Configuration:
// config.js
module.exports = {
port: process.env.PORT || 3000,
db: process.env.DATABASE_URL
};Centralize Error Handling:
// error-handler.js
module.exports = function (err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
};
// in your app.js
const errorHandler = require('./error-handler');
app.use(errorHandler);Modularize Your Code:
// logger.js
module.exports = {
info: (msg) => console.log(`Info: ${msg}`),
error: (msg) => console.error(`Error: ${msg}`)
};
// in another file
const logger = require('./logger');
logger.info('This is an informational message');Use Promises for Database Operations:
const { Pool } = require('pg');
const pool = new Pool();
async function getUser(id) {
const res = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
return res.rows[0];
}Use Helmet for Basic Security:
const helmet = require('helmet');
app.use(helmet());Use Compression Middleware:
const compression = require('compression');
app.use(compression());Sanitize User Input to Prevent Injection:
const sanitize = require('mongo-sanitize');
app.post('/submit', (req, res) => {
const safeInput = sanitize(req.body.input);
// Process safeInput
});Use a Linter and Formatter:
// Linters like ESLint help catch errors and enforce style
// Formatters like Prettier help format your code consistently
Implementing these practices will help you write more efficient, secure, and maintainable Node.js applications. Always remember to tailor best practices to your project's specific needs and constraints.