09/02/2026⏱️ 10 min read
Node.js and Express.js: Building RESTful APIs and Web Applications
Node.jsExpress.jsJavaScriptREST APIBackendWeb Development

Node.js and Express.js: Building RESTful APIs and Web Applications

Introduction

Node.js has revolutionized backend development by enabling JavaScript to run on the server, creating a unified language ecosystem for full-stack development. Express.js, built on top of Node.js, has become the de facto standard web framework for building APIs and web applications.
This comprehensive guide covers Node.js and Express.js from fundamentals to advanced topics. You'll learn how to build RESTful APIs, handle asynchronous operations, implement middleware, manage authentication, and deploy production-ready applications.

What is Node.js?

Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. It allows JavaScript to run outside the browser, enabling server-side development with JavaScript.
Key Features:
  • Event-Driven: Non-blocking I/O operations
  • Asynchronous: Handles concurrent operations efficiently
  • Single-Threaded: Uses event loop for concurrency
  • NPM: Largest package ecosystem
  • Cross-Platform: Runs on Windows, macOS, Linux
Why Node.js?
  • JavaScript Everywhere: Same language for frontend and backend
  • High Performance: Efficient for I/O-intensive applications
  • Large Ecosystem: Millions of packages available
  • Active Community: Strong community support
  • Real-time Applications: Excellent for WebSockets and real-time features

Getting Started with Node.js

Setting up Node.js is straightforward:
Installation:
bash
# Check Node.js version node --version # Check npm version npm --version # Install Node.js from nodejs.org # Or use nvm (Node Version Manager) nvm install 18 nvm use 18
Your First Node.js Application:
javascript
// app.js const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, Node.js!'); }); const PORT = 3000; server.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });
Running the Application:
bash
node app.js
Using ES6 Modules:
javascript
// package.json { "type": "module" } // app.js import http from 'http'; const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, Node.js!'); }); server.listen(3000, () => { console.log('Server running on http://localhost:3000'); });

Asynchronous Programming in Node.js

Understanding asynchronous programming is crucial for Node.js:
Callbacks:
javascript
// Callback example const fs = require('fs'); fs.readFile('file.txt', 'utf8', (err, data) => { if (err) { console.error('Error:', err); return; } console.log('File content:', data); });
Promises:
javascript
// Promise example const fs = require('fs').promises; fs.readFile('file.txt', 'utf8') .then(data => { console.log('File content:', data); }) .catch(err => { console.error('Error:', err); });
Async/Await:
javascript
// Async/await example const fs = require('fs').promises; async function readFile() { try { const data = await fs.readFile('file.txt', 'utf8'); console.log('File content:', data); } catch (err) { console.error('Error:', err); } } readFile();
Event Loop:
javascript
// Understanding the event loop console.log('1'); setTimeout(() => { console.log('2'); }, 0); Promise.resolve().then(() => { console.log('3'); }); console.log('4'); // Output: 1, 4, 3, 2

Introduction to Express.js

Express.js is a minimal and flexible Node.js web application framework:
Key Features:
  • Routing: Define routes easily
  • Middleware: Extend functionality
  • Template Engines: Support for various templating
  • Static Files: Serve static assets
  • Error Handling: Built-in error handling
Installing Express:
bash
npm init -y npm install express
Basic Express Application:
javascript
// app.js const express = require('express'); const app = express(); const PORT = 3000; // Middleware app.use(express.json()); app.use(express.urlencoded({ extended: true })); // Routes app.get('/', (req, res) => { res.json({ message: 'Hello, Express!' }); }); app.get('/api/users', (req, res) => { res.json([ { id: 1, name: 'John' }, { id: 2, name: 'Jane' } ]); }); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });

Express.js Routing

Express provides powerful routing capabilities:
Basic Routes:
javascript
// GET request app.get('/users', (req, res) => { res.json({ users: [] }); }); // POST request app.post('/users', (req, res) => { const user = req.body; // Create user res.status(201).json({ user }); }); // PUT request app.put('/users/:id', (req, res) => { const { id } = req.params; const user = req.body; // Update user res.json({ user }); }); // DELETE request app.delete('/users/:id', (req, res) => { const { id } = req.params; // Delete user res.status(204).send(); });
Route Parameters:
javascript
// Route with parameters app.get('/users/:id', (req, res) => { const { id } = req.params; res.json({ id, name: 'John' }); }); // Multiple parameters app.get('/users/:userId/posts/:postId', (req, res) => { const { userId, postId } = req.params; res.json({ userId, postId }); });
Query Parameters:
javascript
// Query parameters app.get('/users', (req, res) => { const { page, limit, sort } = req.query; res.json({ page, limit, sort }); }); // Example: /users?page=1&limit=10&sort=name
Route Handlers:
javascript
// Multiple handlers app.get('/users', (req, res, next) => { console.log('Middleware 1'); next(); }, (req, res, next) => { console.log('Middleware 2'); next(); }, (req, res) => { res.json({ users: [] }); } );

Express Middleware

Middleware functions have access to request, response, and next:
Custom Middleware:
javascript
// Logger middleware const logger = (req, res, next) => { console.log(`${req.method} ${req.path} - ${new Date().toISOString()}`); next(); }; app.use(logger); // Authentication middleware const authenticate = (req, res, next) => { const token = req.headers.authorization; if (!token) { return res.status(401).json({ error: 'Unauthorized' }); } // Verify token req.user = { id: 1, name: 'John' }; next(); }; app.use('/api/protected', authenticate);
Error Handling Middleware:
javascript
// Error handling middleware app.use((err, req, res, next) => { console.error(err.stack); res.status(500).json({ error: 'Something went wrong!', message: err.message }); }); // Using error middleware app.get('/error', (req, res, next) => { try { throw new Error('Something went wrong'); } catch (err) { next(err); } });
Third-Party Middleware:
javascript
// CORS const cors = require('cors'); app.use(cors()); // Helmet (security) const helmet = require('helmet'); app.use(helmet()); // Rate limiting const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 100 // limit each IP to 100 requests per windowMs }); app.use('/api/', limiter);

Building RESTful APIs

Express makes building RESTful APIs straightforward:
Complete REST API Example:
javascript
// routes/users.js const express = require('express'); const router = express.Router(); let users = [ { id: 1, name: 'John', email: 'john@example.com' }, { id: 2, name: 'Jane', email: 'jane@example.com' } ]; // GET /users router.get('/', (req, res) => { res.json(users); }); // GET /users/:id router.get('/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) { return res.status(404).json({ error: 'User not found' }); } res.json(user); }); // POST /users router.post('/', (req, res) => { const { name, email } = req.body; const newUser = { id: users.length + 1, name, email }; users.push(newUser); res.status(201).json(newUser); }); // PUT /users/:id router.put('/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) { return res.status(404).json({ error: 'User not found' }); } Object.assign(user, req.body); res.json(user); }); // DELETE /users/:id router.delete('/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index === -1) { return res.status(404).json({ error: 'User not found' }); } users.splice(index, 1); res.status(204).send(); }); module.exports = router; // app.js const express = require('express'); const usersRouter = require('./routes/users'); const app = express(); app.use(express.json()); app.use('/api/users', usersRouter);

Database Integration

Integrate databases with Express applications:
MongoDB with Mongoose:
javascript
// models/User.js const mongoose = require('mongoose'); const userSchema = new mongoose.Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true }, createdAt: { type: Date, default: Date.now } }); module.exports = mongoose.model('User', userSchema); // Connect to MongoDB mongoose.connect('mongodb://localhost:27017/myapp', { useNewUrlParser: true, useUnifiedTopology: true }); // routes/users.js const User = require('../models/User'); router.get('/', async (req, res) => { try { const users = await User.find(); res.json(users); } catch (err) { res.status(500).json({ error: err.message }); } }); router.post('/', async (req, res) => { try { const user = new User(req.body); await user.save(); res.status(201).json(user); } catch (err) { res.status(400).json({ error: err.message }); } });
PostgreSQL with pg:
javascript
// db.js const { Pool } = require('pg'); const pool = new Pool({ user: 'user', host: 'localhost', database: 'myapp', password: 'password', port: 5432 }); module.exports = pool; // routes/users.js const pool = require('../db'); router.get('/', async (req, res) => { try { const result = await pool.query('SELECT * FROM users'); res.json(result.rows); } catch (err) { res.status(500).json({ error: err.message }); } });

Authentication and Authorization

Implement authentication in Express applications:
JWT Authentication:
javascript
// middleware/auth.js const jwt = require('jsonwebtoken'); const authenticate = (req, res, next) => { const token = req.headers.authorization?.split(' ')[1]; if (!token) { return res.status(401).json({ error: 'No token provided' }); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; next(); } catch (err) { res.status(401).json({ error: 'Invalid token' }); } }; module.exports = authenticate; // routes/auth.js const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); const User = require('../models/User'); router.post('/register', async (req, res) => { try { const { name, email, password } = req.body; const hashedPassword = await bcrypt.hash(password, 10); const user = new User({ name, email, password: hashedPassword }); await user.save(); res.status(201).json({ message: 'User created' }); } catch (err) { res.status(400).json({ error: err.message }); } }); router.post('/login', async (req, res) => { try { const { email, password } = req.body; const user = await User.findOne({ email }); if (!user || !await bcrypt.compare(password, user.password)) { return res.status(401).json({ error: 'Invalid credentials' }); } const token = jwt.sign( { userId: user._id }, process.env.JWT_SECRET, { expiresIn: '24h' } ); res.json({ token }); } catch (err) { res.status(500).json({ error: err.message }); } });

Error Handling and Validation

Proper error handling and validation are essential:
Input Validation:
javascript
// middleware/validate.js const { body, validationResult } = require('express-validator'); const validateUser = [ body('name').trim().isLength({ min: 1 }).withMessage('Name is required'), body('email').isEmail().withMessage('Valid email is required'), body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters') ]; const handleValidationErrors = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } next(); }; router.post('/register', validateUser, handleValidationErrors, async (req, res) => { // Registration logic });
Error Handling:
javascript
// middleware/errorHandler.js const errorHandler = (err, req, res, next) => { console.error(err.stack); if (err.name === 'ValidationError') { return res.status(400).json({ error: err.message }); } if (err.name === 'UnauthorizedError') { return res.status(401).json({ error: 'Unauthorized' }); } res.status(500).json({ error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? err.message : undefined }); }; app.use(errorHandler);

Deployment and Production Best Practices

Deploy Express applications to production:
Environment Variables:
javascript
// .env NODE_ENV=production PORT=3000 DATABASE_URL=mongodb://localhost:27017/myapp JWT_SECRET=your-secret-key // app.js require('dotenv').config(); const PORT = process.env.PORT || 3000;
Production Middleware:
javascript
// Security middleware const helmet = require('helmet'); app.use(helmet()); // Compression const compression = require('compression'); app.use(compression()); // Rate limiting const rateLimit = require('express-rate-limit'); const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }); app.use('/api/', limiter);
Process Management:
bash
# Using PM2 npm install -g pm2 pm2 start app.js pm2 save pm2 startup
Docker Deployment:
dockerfile
# Dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . EXPOSE 3000 CMD ["node", "app.js"]

Conclusion

Node.js and Express.js provide a powerful platform for building modern web applications and RESTful APIs. By understanding asynchronous programming, routing, middleware, and best practices, you can build scalable, maintainable applications.
Start with simple applications and gradually add more features. Focus on proper error handling, security, and code organization to build production-ready applications. Remember that Node.js excels at I/O-intensive applications, making it ideal for APIs, real-time applications, and microservices.
With the right approach, Node.js and Express.js can help you build fast, scalable applications that leverage JavaScript's ecosystem and Node.js's event-driven architecture.

Share this article

Comments