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

Terminal
# 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

package.json
{
  "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

server.js
// 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}`);
});
Terminal
# 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

JavaScript
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

JavaScript
// 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

routes/users.js
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;
server.js
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

JavaScript
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

JavaScript
// 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

Terminal
# Install popular middleware
npm install cors morgan helmet compression
JavaScript
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

JavaScript
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

JavaScript
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

server.js
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

Terminal
# Install dotenv
npm install dotenv
.env
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_USER=admin
DB_PASSWORD=secret123
JWT_SECRET=your-secret-key
server.js
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}`);
});
Important: Never commit .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:

Task
// 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:

Task
// 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:

Task
// 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:

Task
// 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

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.