Letās fix your async code, error handling, and modular design once and for all. Itās time to write Node.js like a pro.
𤯠Wait, What Did I Do Wrong?
Youāve been writing Node.js code for a while, but is it the right way?
Most Node.js developers fall into some bad habits that can easily be avoidedāhabits that affect performance, readability, and maintainability. Donāt worry, though. Iāve got your back.
Letās walk through the right way to handle these common mistakes.
šµļøāāļø Mistake #1: Not Using async/await
Properly
ā The Old Way (callback hell):
db.query('SELECT * FROM users', (err, data) => {
if (err) {
return handleError(err);
}
doSomethingWithData(data, (err) => {
if (err) return handleError(err);
// Next logic...
});
});
This is callback hell. Itās hard to read and even harder to debug.
ā The Right Way (async/await):
async function getUsers() {
try {
const data = await db.query('SELECT * FROM users');
return data;
} catch (err) {
handleError(err);
}
}
Why it works:
Using async/await
eliminates the callback nesting, making your code cleaner, easier to understand, and easier to maintain.
ā” Mistake #2: Ignoring Proper Error Handling
ā The Old Way (no error handling):
app.get('/data', (req, res) => {
const data = await getData();
res.json(data);
});
If getData()
fails, the server will crash. Not good.
ā The Right Way (try/catch + asyncHandler):
const asyncHandler = require('express-async-handler');
app.get('/data', asyncHandler(async (req, res) => {
const data = await getData();
res.json(data);
}));
Why it works:
By using express-async-handler
(or manually handling errors), we ensure that uncaught errors wonāt crash your app. The request will automatically return a 500 error without breaking everything.
š§ Mistake #3: Writing Monolithic Code
ā The Old Way (everything in index.js
):
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
const users = getUsersFromDB();
res.json(users);
});
app.post('/users', (req, res) => {
const newUser = createUser(req.body);
res.status(201).json(newUser);
});
// And everything else...
This approach works fine for a small app, but as your app grows, your index.js
file becomes harder to maintain and scale.
ā The Right Way (modular design with routers):
const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
app.use('/users', userRoutes);
userRoutes.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
const users = getUsersFromDB();
res.json(users);
});
router.post('/', (req, res) => {
const newUser = createUser(req.body);
res.status(201).json(newUser);
});
module.exports = router;
Why it works:
By modularizing your code, you can keep your application organized, easier to scale, and easier to debug. Each piece of functionality lives in its own module, making your app cleaner and more manageable.
š« Mistake #4: Not Using Proper Environment Variables
ā The Old Way (hardcoding values):
const DB_HOST = 'localhost';
const DB_USER = 'root';
const DB_PASS = 'password';
Youāre asking for trouble by hardcoding sensitive information in your code. Security risk? Check.
ā The Right Way (use environment variables):
const DB_HOST = process.env.DB_HOST;
const DB_USER = process.env.DB_USER;
const DB_PASS = process.env.DB_PASS;
Then, add your environment variables to your .env
file.
Why it works:
Itās safer, and you can have different configurations for different environments (dev, staging, production). Never commit sensitive data into your source code.
š§āš» Mistake #5: Not Leveraging Asynchronous I/O Operations
Node.js excels in non-blocking I/O, but many developers donāt take full advantage of it.
ā The Old Way (blocking operations):
const fs = require('fs');
const data = fs.readFileSync('data.json');
This blocks the entire server while reading the file.
ā The Right Way (async I/O operations):
const fs = require('fs/promises');
const data = await fs.readFile('data.json', 'utf-8');
Why it works:
By switching to non-blocking operations, your server doesnāt freeze up while waiting for I/O. This means better performance under load, especially for file reads, database queries, and external API calls.
š§¹ Mistake #6: Forgetting to Use Caching
ā The Old Way (repeated database queries):
app.get('/products', async (req, res) => {
const products = await db.getProducts();
res.json(products);
});
If your data doesnāt change often, this is inefficient. Every request hits the database, even if the result is the same.
ā The Right Way (caching data):
const cache = {};
app.get('/products', async (req, res) => {
if (cache.products) return res.json(cache.products);
const products = await db.getProducts();
cache.products = products;
res.json(products);
});
Why it works:
Caching improves speed by storing frequently accessed data, reducing database load, and speeding up responses.
ā Summary: The Right Way to Write Node.js
Mistake | Fix |
---|---|
Callback hell | Use async/await |
No error handling | Use try/catch + express-async-handler |
Monolithic code | Modularize with routers |
Hardcoded values | Use environment variables |
Blocking I/O | Use async file/database operations |
No caching | Implement data caching |