Node.js enables JavaScript to run server-side using a single-threaded event loop and non-blocking I/O. This design allows efficient handling of multiple requests by offloading operations that might block the event loop, such as I/O, to the system’s kernel.
const http = require('http');http.createServer((req, res) => {res.writeHead(200, { 'Content-Type': 'text/plain' });res.end('Hello, world!');}).listen(8080);console.log('Server running at http://localhost:8080/');
Node.js ModulesNode.js structures code using require() to import and module.exports to export functions or variables. This approach maintains each file’s scope, minimizing global variable conflicts. By organizing code into modules, you enhance modularity and reuse within your applications.
// mathOperations.jsconst sum = (a, b) => a + b;module.exports = sum;// app.jsconst sum = require('./mathOperations');console.log(sum(5, 3)); // Output: 8
npm (Node Package Manager) manages libraries and tracks dependencies in package.json. It distinguishes development tools from production code, helping teams maintain organized and efficient Node projects.
// Initialize a new Node projectnpm init -y// Install express as a dependencynpm install express// View updated `package.json`{"name": "my-app","version": "1.0.0","dependencies": {"express": "^4.17.1"}}
Node.js Project StructureIn a typical Node.js backend setup, structuring your code into specific folders such as routes, controllers, services, middleware, and config enhances readability and maintenance. Each folder has a dedicated purpose, ensuring that parts of the codebase remain focused and clearly defined for their specific tasks.
// sample folder structureproject/│├── routes/│ ├── userRoutes.js│ ├── authRoutes.js│├── controllers/│ ├── userController.js│ ├── authController.js|├── services/│ ├── userService.js│ ├── authService.js|├── middleware/│ ├── authMiddleware.js│ ├── errorHandler.js|├── config/│ ├── dbConfig.js│ ├── serverConfig.js
dotenv Setup in Node.jsThe dotenv package helps manage sensitive configuration data by loading variables from a .env file into process.env. This way, your source code remains clean and secure. Use dotenv in your Node.js project to manage parameters like ports and API keys efficiently.
require('dotenv').config();// Access environment variablesconst PORT = process.env.PORT || 3000;const DATABASE_URL = process.env.DATABASE_URL;const SECRET_KEY = process.env.SECRET_KEY;console.log(`Server running on port: ${PORT}`);
Node.js Async/AwaitIn Node.js, async functions return Promises. The await keyword pauses execution until the Promise resolves, providing a more straightforward way to handle asynchronous code. This pattern helps prevent callback hell and makes the code easier to read and maintain.
async function fetchData(url) {try {const response = await fetch(url);const data = await response.json();return data;} catch (error) {console.error('Fetch error:', error);}}fetchData('https://api.example.com/data').then(data => console.log(data));
Node.js Try/CatchIn Node.js, the try/catch construct is essential for error handling within async functions. By catching thrown errors, it ensures smoother execution of code. The finally block executes after try and catch, regardless of an error’s occurrence. This is indispensable for cleanup tasks.
async function fetchData() {try {const response = await fetch('https://api.example.com/data');const data = await response.json();return data;} catch (error) {console.error('Error fetching data:', error);} finally {console.log('Fetch attempt complete.');}}
A stack trace in Node.js offers a breakdown of function calls leading to errors. By showing the sequence of calls, it helps identify the root cause of a bug with the first line pointing to the error source in the application code. This insight is essential for debugging efficiently.
function foo() {bar();}function bar() {baz();}function baz() {throw new Error('Something went wrong!');}foo();// Running this will produce a stack trace, showing the chain of calls.
Node.js Data ValidationIn Node.js backend development, validating client request data ensures security by preventing malicious input. Encrypting sensitive secrets, such as API keys, rather than hardcoding them in your codebase improves security and reduces vulnerabilities.
// Example of data validation in Node.js with express-validatorconst { body, validationResult } = require('express-validator');// Middleware for data validationapp.post('/data', [body('email').isEmail().withMessage('Enter a valid email'),body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters long')], (req, res) => {const errors = validationResult(req);if (!errors.isEmpty()) {return res.status(400).json({ errors: errors.array() });}// Process valid data});// Example of using environment variables for secretsconst apiSecretKey = process.env.API_SECRET_KEY;// Avoid storing keys like below// const apiSecretKey = '123456abcdef';