Always Use Async/Await
Callbacks lead to deeply nested code that is hard to read and debug. async/await makes asynchronous code look and behave like synchronous code.
// Don't: Callback hell
fs.readFile("a.txt", (err, a) => {
fs.readFile("b.txt", (err, b) => {
// Nested callbacks...
});
});
// Do: Async/await
async function readFiles() {
const a = await fs.readFile("a.txt", "utf-8");
const b = await fs.readFile("b.txt", "utf-8");
}
Handle All Errors
Always wrap async code in try/catch. In Express, catch errors and pass them to next() so your error-handling middleware can respond properly.
app.get("/api/users/:id", async (req, res, next) => {
try {
const user = await User.findById(req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json(user);
} catch (err) {
next(err);
}
});
Use Environment Variables
Never hardcode secrets like database passwords or API keys. Use environment variables and the dotenv package to load them from a .env file.
// Don't hardcode secrets
const dbPassword = process.env.DB_PASSWORD;
// Use .env file (never commit this)
require("dotenv").config();
Structure Your Project
A clear folder structure keeps your code organized as the project grows. Separate concerns into routes, models, controllers, middleware, and utilities.
project/
src/
routes/ # API routes
models/ # Database models
controllers/ # Business logic
middleware/ # Custom middleware
utils/ # Helper functions
tests/ # Test files
.env # Environment variables
.gitignore # Ignored files
package.json # Project config
Never Block the Event Loop
Synchronous operations and heavy CPU work block the event loop, making your server unresponsive. Use async APIs for I/O and worker_threads for CPU-intensive tasks.
// Don't: Synchronous file operations
const data = fs.readFileSync("huge-file.txt");
// Do: Async file operations
const data = await fs.readFile("huge-file.txt");
// Don't: CPU-intensive work on main thread
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Do: Use worker_threads for heavy computation
Use ESLint
ESLint catches bugs before they happen and enforces consistent code style across your project.
npm install --save-dev eslint
npx eslint --init
The Golden Rule: Write code that is easy to read, handles errors gracefully, and does not block the event loop. Everything else is secondary.