Introduction
Node.js brings JavaScript to the server side, allowing you to build scalable backend applications. Combined with Express.js, the most popular Node.js framework, you can create powerful APIs and web servers. In this lesson, you'll learn how to set up a Node.js environment, create HTTP servers, implement routing, use middleware, and build RESTful APIs.
Learning Objectives
By the end of this lesson, you will be able to:
- Set up a Node.js development environment
- Create HTTP servers with Express.js
- Implement routing and middleware
- Handle request and response objects
- Serve static files and templates
- Build RESTful API endpoints
Prerequisites
- Completion of Lessons 01-06
- Understanding of HTTP and web servers
- Familiarity with npm and package management
- Node.js installed (v16 or higher recommended)
Estimated Time
4.5 hours (including practice exercises)
What is Node.js?
Node.js is a JavaScript runtime built on Chrome's V8 engine. It allows you to run JavaScript on the server, enabling full-stack JavaScript development.
Key Features
- Asynchronous & Event-Driven - Non-blocking I/O operations
- Fast Execution - Built on V8 JavaScript engine
- NPM Ecosystem - Largest package registry in the world
- Single-Threaded - Uses event loop for concurrency
- Cross-Platform - Runs on Windows, macOS, Linux
Setting Up Your Project
# Create project directory
mkdir my-node-app
cd my-node-app
# Initialize npm project
npm init -y
# Install Express
npm install express
# Install development dependencies
npm install --save-dev nodemon
Package.json Configuration
{
"name": "my-node-app",
"version": "1.0.0",
"description": "Node.js backend application",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": ["node", "express", "api"],
"author": "Your Name",
"license": "MIT",
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
Creating Your First Server
// Import Express
const express = require('express');
// Create Express app
const app = express();
// Define port
const PORT = process.env.PORT || 3000;
// Basic route
app.get('/', (req, res) => {
res.send('Hello, Node.js!');
});
// Start server
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
# Run the server
npm run dev
# Server will start on http://localhost:3000
Routing
Routes define how your application responds to client requests:
Basic Routes
const express = require('express');
const app = express();
// GET request
app.get('/', (req, res) => {
res.send('Home Page');
});
// POST request
app.post('/api/data', (req, res) => {
res.json({ message: 'Data received' });
});
// PUT request
app.put('/api/data/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Updated item ${id}` });
});
// DELETE request
app.delete('/api/data/:id', (req, res) => {
const { id } = req.params;
res.json({ message: `Deleted item ${id}` });
});
// Handle 404
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
Route Parameters
// URL parameters
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ userId });
});
// Multiple parameters
app.get('/posts/:postId/comments/:commentId', (req, res) => {
const { postId, commentId } = req.params;
res.json({ postId, commentId });
});
// Query parameters
app.get('/search', (req, res) => {
const { q, page, limit } = req.query;
res.json({ query: q, page, limit });
});
// Example: /search?q=nodejs&page=1&limit=10
Router Modules
const express = require('express');
const router = express.Router();
// GET all users
router.get('/', (req, res) => {
res.json({ users: [] });
});
// GET single user
router.get('/:id', (req, res) => {
res.json({ user: { id: req.params.id } });
});
// CREATE user
router.post('/', (req, res) => {
res.status(201).json({ message: 'User created' });
});
// UPDATE user
router.put('/:id', (req, res) => {
res.json({ message: 'User updated' });
});
// DELETE user
router.delete('/:id', (req, res) => {
res.json({ message: 'User deleted' });
});
module.exports = router;
const express = require('express');
const app = express();
// Import routes
const userRoutes = require('./routes/users');
const postRoutes = require('./routes/posts');
// Use routes
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
app.listen(3000);
Middleware
Middleware functions have access to request and response objects and can modify them or end the request-response cycle:
Built-in Middleware
const express = require('express');
const app = express();
// Parse JSON bodies
app.use(express.json());
// Parse URL-encoded bodies
app.use(express.urlencoded({ extended: true }));
// Serve static files
app.use(express.static('public'));
Custom Middleware
// Logger middleware
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next(); // Pass control to next middleware
};
app.use(logger);
// Authentication middleware
const authenticate = (req, res, next) => {
const token = req.headers.authorization;
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
// Verify token (simplified)
if (token === 'valid-token') {
req.user = { id: 1, name: 'John' };
next();
} else {
res.status(403).json({ error: 'Invalid token' });
}
};
// Protected route
app.get('/api/protected', authenticate, (req, res) => {
res.json({ message: 'Protected data', user: req.user });
});
// Error handling middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Something went wrong!' });
});
Third-Party Middleware
# Install popular middleware
npm install cors morgan helmet compression
const express = require('express');
const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
const compression = require('compression');
const app = express();
// Security headers
app.use(helmet());
// Enable CORS
app.use(cors());
// HTTP request logger
app.use(morgan('dev'));
// Compress responses
app.use(compression());
// Parse JSON
app.use(express.json());
Request and Response Objects
Request Object
app.post('/api/example', (req, res) => {
// URL parameters
const { id } = req.params;
// Query parameters
const { page, limit } = req.query;
// Request body
const { name, email } = req.body;
// Headers
const contentType = req.get('Content-Type');
const userAgent = req.get('User-Agent');
// Request method
console.log(req.method); // POST
// Request URL
console.log(req.url); // /api/example
console.log(req.path); // /api/example
// IP address
console.log(req.ip);
res.json({ received: true });
});
Response Object
app.get('/api/response-examples', (req, res) => {
// Send JSON
res.json({ message: 'Success', data: [] });
// Send text
res.send('Plain text response');
// Send HTML
res.send('HTML Response
');
// Set status code
res.status(201).json({ created: true });
// Set headers
res.set('X-Custom-Header', 'value');
// Redirect
res.redirect('/new-url');
// Send file
res.sendFile('/path/to/file.pdf');
// Download file
res.download('/path/to/file.pdf', 'filename.pdf');
// Set cookie
res.cookie('token', 'abc123', { httpOnly: true });
// Clear cookie
res.clearCookie('token');
});
Building a RESTful API
const express = require('express');
const app = express();
app.use(express.json());
// In-memory data store
let products = [
{ id: 1, name: 'Laptop', price: 999, stock: 10 },
{ id: 2, name: 'Phone', price: 699, stock: 15 },
{ id: 3, name: 'Tablet', price: 499, stock: 20 }
];
let nextId = 4;
// GET all products
app.get('/api/products', (req, res) => {
const { minPrice, maxPrice, inStock } = req.query;
let filtered = products;
if (minPrice) {
filtered = filtered.filter(p => p.price >= Number(minPrice));
}
if (maxPrice) {
filtered = filtered.filter(p => p.price <= Number(maxPrice));
}
if (inStock === 'true') {
filtered = filtered.filter(p => p.stock > 0);
}
res.json({
count: filtered.length,
products: filtered
});
});
// GET single product
app.get('/api/products/:id', (req, res) => {
const product = products.find(p => p.id === Number(req.params.id));
if (!product) {
return res.status(404).json({ error: 'Product not found' });
}
res.json(product);
});
// CREATE product
app.post('/api/products', (req, res) => {
const { name, price, stock } = req.body;
// Validation
if (!name || !price) {
return res.status(400).json({ error: 'Name and price are required' });
}
const newProduct = {
id: nextId++,
name,
price: Number(price),
stock: Number(stock) || 0
};
products.push(newProduct);
res.status(201).json(newProduct);
});
// UPDATE product
app.put('/api/products/:id', (req, res) => {
const productIndex = products.findIndex(p => p.id === Number(req.params.id));
if (productIndex === -1) {
return res.status(404).json({ error: 'Product not found' });
}
const { name, price, stock } = req.body;
products[productIndex] = {
id: products[productIndex].id,
name: name || products[productIndex].name,
price: price !== undefined ? Number(price) : products[productIndex].price,
stock: stock !== undefined ? Number(stock) : products[productIndex].stock
};
res.json(products[productIndex]);
});
// DELETE product
app.delete('/api/products/:id', (req, res) => {
const productIndex = products.findIndex(p => p.id === Number(req.params.id));
if (productIndex === -1) {
return res.status(404).json({ error: 'Product not found' });
}
const deleted = products.splice(productIndex, 1)[0];
res.json({ message: 'Product deleted', product: deleted });
});
// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal server error' });
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});
Environment Variables
# Install dotenv
npm install dotenv
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_USER=admin
DB_PASSWORD=secret123
JWT_SECRET=your-secret-key
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
const NODE_ENV = process.env.NODE_ENV || 'development';
console.log(`Running in ${NODE_ENV} mode`);
app.listen(PORT, () => {
console.log(`Server on port ${PORT}`);
});
.env files to version control. Add it to .gitignore.
Key Takeaways
- Node.js enables JavaScript on the server side
- Express.js simplifies building web servers and APIs
- Middleware functions process requests before reaching routes
- RESTful APIs use HTTP methods for CRUD operations
- Route parameters and query strings handle dynamic data
- Environment variables store configuration securely
- Error handling middleware catches and processes errors
- Modular routing keeps code organized and maintainable
Practice Exercises
Exercise 1: Blog API
Create a complete blog API with posts and comments:
// Build an API with:
// - GET /api/posts (all posts)
// - GET /api/posts/:id (single post)
// - POST /api/posts (create post)
// - PUT /api/posts/:id (update post)
// - DELETE /api/posts/:id (delete post)
// - GET /api/posts/:id/comments (post comments)
// - POST /api/posts/:id/comments (add comment)
Exercise 2: Authentication Middleware
Implement JWT-based authentication:
// Create middleware that:
// 1. Checks for Authorization header
// 2. Verifies JWT token
// 3. Attaches user to request object
// 4. Protects specific routes
Exercise 3: File Upload
Handle file uploads with multer:
// Install multer: npm install multer
// Create endpoint that:
// 1. Accepts file uploads
// 2. Validates file type and size
// 3. Saves files to disk
// 4. Returns file URL
Exercise 4: Rate Limiting
Implement rate limiting middleware:
// Create middleware that:
// 1. Tracks requests per IP
// 2. Limits to 100 requests per 15 minutes
// 3. Returns 429 status when exceeded
// 4. Resets counter after time window
Additional Resources
What's Next?
In Lesson 08, you'll learn database integration with MongoDB. You'll discover how to store and retrieve data persistently, design schemas, perform CRUD operations, and connect your Node.js application to a database.