Let’s delve into the core aspects of Node.js network development: building an HTTP server and implementing routing control.
This content is the foundation of all Node.js-based web frameworks (such as Express, Koa). Understanding it will give you a deeper insight into what these frameworks actually do.
—
### **6.3 Building an HTTP Server**
In Node.js, we do not need to rely on external web servers like Apache or Nginx to run a website. Node.js itself provides a powerful `http` core module that allows us to create a fully functional HTTP server with just a few lines of code.
#### **1. Creating a Basic HTTP Server**
Let’s start with the classic “Hello, World!” server.
**Core Steps:**
1. **Import the `http` module**: This is a built-in module in Node.js and does not require installation.
2. **Create the server**: Use the `http.createServer()` method. This method takes a callback function as a parameter, which will be executed every time an HTTP request is received.
3. **Listen on a port**: Use the `server.listen()` method to start listening for requests on a specified port.
**Code Example (`simple-server.js`):**
// 1. Import the http module
const http = require('http');
// 2. Create the HTTP server
// The callback function for createServer will be called on each request
// It receives two parameters:
// - req (request): The request object containing all information about the client request, such as URL, headers, etc.
// - res (response): The response object used to send a response back to the client.
const server = http.createServer((req, res) => {
console.log(`Received request! Request path: ${req.url}`);
// Set response headers
// Status code 200 indicates success
// 'Content-Type' tells the browser that the returned data type is plain text
res.writeHead(200, { 'Content-Type': 'text/plain' });
// Send the response body and end the response
res.end('Hello, Node.js HTTP Server!');
});
// 3. Start the server and listen on port 3000
// When the server starts successfully, the callback function will be executed
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
});
**How to Run:**
1. Save the above code as `simple-server.js`.
2. Open a terminal and navigate to the directory where the file is located.
3. Run the command: `node simple-server.js`.
4. You will see the terminal output: `Server running at http://localhost:3000`.
5. Open a browser and visit `http://localhost:3000`, and you will see the page displaying “Hello, Node.js HTTP Server!”.
6. Return to the terminal, and you will notice that every time you refresh the browser, it will print `Received request! Request path: /`. If you visit `http://localhost:3000/about`, the terminal will print `Received request! Request path: /about`.
This simple example reveals the essence of how a web server works: **Listening for requests -> Processing requests -> Returning responses**.
—
#### **2. Routing Control for the HTTP Server**
Now that we have a server, it returns the same content regardless of whether we access `/`, `/about`, or `/user`. This is clearly not what we want. **Routing** is about executing different code and returning different content based on different URL paths.
We can implement simple routing by checking the `req.url` property.
**Code Example (`routed-server.js`):**
const http = require('http');
const server = http.createServer((req, res) => {
// Get the requested URL and remove query parameters (e.g., ?id=123)
const path = req.url.split('?')[0];
// Set a common response header, specifying the content type as HTML
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
// Route based on different paths
if (path === '/' || path === '/home') {
res.end('<h1>Welcome to the homepage</h1>');
} else if (path === '/about') {
res.end('<h1>About Us</h1><p>This is a simple Node.js server example.</p>');
} else if (path === '/contact') {
res.end('<h1>Contact Us</h1><p>Email: [email protected]</p>');
} else {
// If the path does not match any defined routes, return a 404 page
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>404 - Page Not Found</h1><p>Sorry, the page you are looking for does not exist.</p>');
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server with routing running at http://localhost:${PORT}`);
});
**How to Run and Test:**
1. Save as `routed-server.js` and run `node routed-server.js`.
2. Test in the browser:
* Access `http://localhost:3000/` or `http://localhost:3000/home`, and you will see “Welcome to the homepage”.
* Access `http://localhost:3000/about`, and you will see the “About Us” page.
* Access `http://localhost:3000/contact`, and you will see the “Contact Us” page.
* Access any other path, such as `http://localhost:3000/nothing`, and you will see the 404 page.
**This `if-else` structure is the most primitive routing controller.** It successfully dispatches requests based on the URL.
### **From Native to Framework: Why Use Express?**
Although we implemented routing using the native `http` module, we will soon discover some issues:
* **Code Bloat**: As the number of routes increases, `if-else` or `switch-case` statements become very long and hard to maintain.
* **Single Functionality**: Handling POST requests, parsing request bodies, managing cookies, and session management all require us to write complex code manually.
* **Lack of Middleware**: It is not easy to insert common functionalities (like logging, authentication) into the request handling process.
To solve these problems, the community developed web frameworks like **Express.js**. It wraps the native `http` module and provides a cleaner, more powerful API.
**Rewriting the above routing example with Express:**
First, install Express:
`npm install express`
Then, create `express-server.js`:
// 1. Import express
const express = require('express');
// 2. Create an express application
const app = express();
const PORT = 3000;
// 3. Define routes
// app.get(path, callback) specifically handles GET requests
app.get('/', (req, res) => {
// res.send() automatically sets Content-Type and ends the response
res.send('<h1>Welcome to the homepage</h1>');
});
app.get('/home', (req, res) => {
res.send('<h1>Welcome to the homepage</h1>');
});
app.get('/about', (req, res) => {
res.send('<h1>About Us</h1><p>This is an Express server example.</p>');
});
app.get('/contact', (req, res) => {
res.send('<h1>Contact Us</h1><p>Email: [email protected]</p>');
});
// 4. Handle 404 (place after all routes)
app.use((req, res) => {
res.status(404).send('<h1>404 - Page Not Found</h1><p>Sorry, the page you are looking for does not exist.</p>');
});
// 5. Start the server
app.listen(PORT, () => {
console.log(`Express server running at http://localhost:${PORT}`);
});
**Comparing, you will find the advantages of Express:**
* **Clearer Code**: Each route is an independent `app.get()` call, making the logic straightforward.
* **Friendlier API**: `res.send()` handles many details automatically (like setting `Content-Type`, ending the response).
* **More Powerful Features**: Express has built-in support for route parameters, query strings, middleware, etc.
### **Summary**
| Feature | Native `http` Module | Express.js Framework |
| :— | :— | :— |
| **Learning Curve** | Steep, requires manual handling of all details | Gentle, intuitive API design |
| **Code Volume** | More code needed to achieve the same functionality | Concise code, high readability |
| **Routing** | Uses `if-else` or `switch`, hard to extend | Uses `app.get()`, `app.post()`, etc., very convenient |
| **Middleware** | No native concept, needs to be implemented manually | Core feature, rich ecosystem |
| **Use Cases** | Learning underlying principles, building minimal tools, microservices | Most web applications, API services |
**Recommended Learning Path:**
1. **First master the native `http` module**: Understand the underlying mechanisms of how Node.js handles network requests.
2. **Then learn Express.js**: Utilize the power and convenience of the framework to efficiently build real applications.
I hope this detailed explanation helps you better understand the basics of Node.js network development! If you have any deeper questions about any part, feel free to ask.
——————————–
Of course! Handling HTTP request headers is a very common and important task in web development. Request headers contain various metadata sent from the client (browser, API calling tools, etc.) to the server, such as content type, user agent, authentication information, etc.
In Node.js, the way to handle request headers depends on whether you are using the native `http` module or a framework like Express. Let’s look at both.
—
### **1. Handling Request Headers with the Native `http` Module**
In the native `http` module, all request header information is stored in the `req` object’s `req.headers` property passed to the `createServer` callback function.
`req.headers` is a regular JavaScript object where the keys are the names of the request headers (in lowercase), and the values are the corresponding request header contents.
**Important Note:**
* Node.js automatically converts all request header names to **lowercase**. For example, the `Content-Type` request header sent by the browser will become `req.headers[‘content-type’]` in Node.js.
* If a request header appears multiple times (e.g., `Set-Cookie`), its value will be a string separated by commas `,`.
#### **Code Example: Reading and Displaying All Request Headers**
// headers-server.js
const http = require('http');
const server = http.createServer((req, res) => {
console.log('--- Received a new request ---');
console.log(`Request Method: ${req.method}`);
console.log(`Request URL: ${req.url}`);
// 1. Get all request headers
const headers = req.headers;
console.log('All Request Headers:', headers);
// 2. Get specific request headers
const userAgent = headers['user-agent'];
const acceptLanguage = headers['accept-language'];
const contentType = headers['content-type'];
console.log(`User Agent: ${userAgent}`);
console.log(`Accepted Language: ${acceptLanguage}`);
console.log(`Content Type: ${contentType}`);
// Return request header information as a response to the client
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
res.end(JSON.stringify({
message: 'Request header information has been printed in the server console',
headers: headers,
userAgent: userAgent,
acceptLanguage: acceptLanguage
}, null, 2)); // null, 2 is for formatting JSON
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
console.log('Please access using a browser or Postman and check the server console output.');
});
**How to Test:**
1. Run `node headers-server.js`.
2. Open a browser and visit `http://localhost:3000`.
3. Check your terminal, and you will see detailed request header information sent by the browser.
4. You can also use Postman or `curl` to send custom request headers, for example:
curl -H "X-Custom-Header: MyValue" -H "Authorization: Bearer mytoken" http://localhost:3000
In the terminal output, you will see the two custom request headers `x-custom-header` and `authorization`.
—
### **2. Handling Request Headers with Express.js**
Express.js wraps the native `req` object and provides more convenient methods for handling request headers, but its core data still comes from `req.headers`.
The Express `req` object provides a dedicated method `req.get()` to retrieve request headers, which is **case-insensitive** and more intuitive to use.
#### **Code Example: Reading Request Headers in Express**
First, ensure you have installed Express: `npm install express`
// express-headers-server.js
const express = require('express');
const app = express();
const PORT = 3000;
// A middleware to print all request headers
app.use((req, res, next) => {
console.log('--- Received a new request ---');
console.log(`Request Method: ${req.method}`);
console.log(`Request URL: ${req.url}`);
// 1. Get all request headers (Express still retains req.headers)
console.log('All Request Headers:', req.headers);
next(); // Pass control to the next middleware or route
});
// Define a route to demonstrate how to get specific request headers
app.get('/', (req, res) => {
// 2. Use req.get() to get specific request headers (recommended)
// Note: req.get() is case-insensitive
const userAgent = req.get('User-Agent');
const acceptLanguage = req.get('Accept-Language');
const customHeader = req.get('X-Custom-Header'); // Get custom request header
// You can also directly access req.headers, but req.get() is more convenient
const contentType = req.headers['content-type'];
res.json({
message: 'This is the request header information returned from the Express server',
userAgent: userAgent,
acceptLanguage: acceptLanguage,
customHeader: customHeader,
contentType: contentType
});
});
app.listen(PORT, () => {
console.log(`Express server running at http://localhost:${PORT}`);
});
**How to Test:**
1. Run `node express-headers-server.js`.
2. Use `curl` to send a request with a custom header:
curl -H "X-Custom-Header: HelloFromCurl" http://localhost:3000
3. You will receive a JSON response where the `customHeader` field value is “HelloFromCurl”. Meanwhile, the server terminal will also print the complete request header information.
—
### **Common Use Cases**
Once you understand how to retrieve request headers, you can use them in many practical scenarios:
1. **Content Negotiation**
* Through the `Accept` request header, the client tells the server what type of content it wishes to receive (e.g., `text/html`, `application/json`). The server can return different formats of data based on this value.
* Through the `Accept-Language` request header, determine the user’s language preference and return the corresponding language page.
2. **Authentication and Authorization**
* In API development, the `Authorization` request header is often used to carry tokens, such as `Bearer <token>`. The server needs to read this header to verify the user’s identity.
3. **Cross-Origin Resource Sharing**
* When handling CORS preflight requests (the `OPTIONS` method), the server needs to check request headers like `Access-Control-Request-Method` and `Access-Control-Request-Headers` to decide whether to allow the cross-origin request.
4. **API Version Control**
* You can specify the API version the client wants to use through custom request headers (e.g., `API-Version: v1`).
5. **Logging and Analysis**
* The `User-Agent` request header contains information about the client’s operating system, browser, etc., which can be used for statistical analysis or to provide different versions of pages for different devices.
### **Summary**
| Feature | Native `http` Module | Express.js |
| :— | :— | :— |
| **Get All Headers** | `req.headers` | `req.headers` |
| **Get Single Header** | `req.headers[‘header-name’]` (name must be lowercase) | `req.get(‘Header-Name’)` (case-insensitive, recommended) |
| **Convenience** | Low, requires manual case handling | High, friendlier API |
**Key Points:**
* Request headers are a key channel for passing metadata between the client and server.
* In native Node.js, all request headers are in the `req.headers` object, and the keys are converted to lowercase.
* In Express, it is recommended to use the `req.get()` method to retrieve request headers, as it is more convenient and case-insensitive.
By mastering request header handling, your Node.js applications can interact with clients in a richer and more intelligent way.
——————————–
Very well, this is a crucial question. Validating authentication information in request headers is the first line of defense in protecting APIs and web resources. Let’s discuss some of the most common authentication methods and how to implement them in Node.js.
We will start with the most basic methods and gradually transition to the most mainstream methods in modern applications.
—
### **Core Concept: Authentication Process**
No matter which authentication method is used, the basic process is similar:
1. **Client**: When making a request, the client places the authentication credentials (such as username and password, token) in a specific HTTP request header.
2. **Server**: In the middleware or route handler processing the request, extract the credentials from the request header.
3. **Server**: Validate the credentials’ validity (for example, check if the username and password match, or if the token is valid and not expired).
4. **Server**:
* If the credentials are valid, allow the request to continue, and typically attach user information (such as `userId`, `role`) to the request object for subsequent handlers to use.
* If the credentials are invalid or missing, return a `401 Unauthorized` status code and abort the request.
—
### **Method 1: Basic Authentication**
This is the simplest authentication method, but **it has low security** and is not recommended for production environments unless used with HTTPS.
* **Request Header**: `Authorization`
* **Format**: `Basic <credentials>`
* **Credentials**: `<credentials>` is a string that is the `username:password` encoded in Base64.
#### **Express Implementation Example**
We can create a middleware to handle Basic Auth.
// basic-auth-server.js
const express = require('express');
const app = express();
// Simulated user database
const users = {
admin: 'password123'
};
// Base64 decode function (built-in Node.js)
const { atob } = require('buffer'); // In Node.js environment
// Basic Auth middleware
const basicAuthMiddleware = (req, res, next) => {
// 1. Get Authorization information from request header
const authHeader = req.get('Authorization');
if (!authHeader || !authHeader.startsWith('Basic ')) {
res.set('WWW-Authenticate', 'Basic realm="Restricted Area"'); // Prompt the browser to pop up a login box
return res.status(401).json({ message: 'Authentication information is missing or incorrectly formatted' });
}
// 2. Extract and decode credentials
// authHeader.split(' ')[1] gets the Base64 encoded string
const base64Credentials = authHeader.split(' ')[1];
const credentials = atob(base64Credentials); // Decode
const [username, password] = credentials.split(':'); // Separate username and password
// 3. Validate credentials
const validPassword = users[username];
if (!validPassword || validPassword !== password) {
return res.status(401).json({ message: 'Invalid username or password' });
}
// 4. Authentication successful, attach user information to req object and continue
req.user = { username };
next(); // Pass control to the next handler
};
// Apply middleware to protected routes
app.use('/secure', basicAuthMiddleware);
app.get('/secure/profile', (req, res) => {
// Because authentication is successful, we can safely access req.user here
res.json({
message: `Welcome, ${req.user.username}! You have accessed a protected resource.`,
user: req.user
});
});
app.get('/', (req, res) => {
res.json({ message: 'This is a public route, no authentication required.' });
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}`);
console.log('Try accessing the /secure/profile route for testing.');
});
**How to Test:**
1. Run `node basic-auth-server.js`.
2. Access `http://localhost:3000/`, and you will see public information.
3. Access `http://localhost:3000/secure/profile`, and the browser will prompt a login box. Enter `admin` and `password123` to access.
4. You can also use `curl`:
# Incorrect credentials
curl -u admin:wrongpass http://localhost:3000/secure/profile
# Correct credentials
curl -u admin:password123 http://localhost:3000/secure/profile
—
### **Method 2: Bearer Token (JWT)**
This is the **most mainstream** authentication method for modern web APIs, especially in front-end and back-end separated architectures. It is stateless and scalable.
* **Request Header**: `Authorization`
* **Format**: `Bearer <token>`
* **Credentials**: `<token>` is usually a JSON Web Token (JWT).
JWT is an encrypted string containing three parts (Header, Payload, Signature) that the server can independently verify its validity without querying the database.
#### **Express Implementation Example**
You need to install the `jsonwebtoken` library first: `npm install jsonwebtoken`
// jwt-auth-server.js
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Secret key, should be read from environment variables in production
const JWT_SECRET = 'your-super-secret-key-that-is-long-and-random';
// Simulated user database
const users = {
user1: { id: 1, username: 'user1', password: 'pass111' }
};
// Login route for issuing JWT
app.post('/login', express.json(), (req, res) => {
const { username, password } = req.body;
const user = users[username];
if (!user || user.password !== password) {
return res.status(401).json({ message: 'Invalid credentials' });
}
// User information validated successfully, generate JWT
const token = jwt.sign(
{ userId: user.id, username: user.username }, // Payload (data to carry)
JWT_SECRET, // Secret Key
{ expiresIn: '1h' } // Options (like expiration time)
);
res.json({ message: 'Login successful', token });
});
// JWT verification middleware
const jwtAuthMiddleware = (req, res, next) => {
const authHeader = req.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Token missing or incorrectly formatted' });
}
const token = authHeader.split(' ')[1];
try {
// Verify and decode Token
const decodedPayload = jwt.verify(token, JWT_SECRET);
// Verification successful, attach decoded user information to req object
req.user = decodedPayload;
next();
} catch (error) {
// jwt.verify will throw an error if the Token is invalid (e.g., expired, signature error)
return res.status(401).json({ message: 'Invalid or expired Token' });
}
};
// Protect routes that require authentication
app.use('/api', jwtAuthMiddleware);
app.get('/api/data', (req, res) => {
res.json({
message: `Hello, ${req.user.username}! This is your data.`,
data: [1, 2, 3, 4, 5]
});
});
const PORT = 3000;
app.listen(PORT, () => {
console.log(`JWT authentication server running at http://localhost:${PORT}`);
console.log('1. First POST /login to get token');
console.log('2. Then use the token to GET /api/data');
});
**How to Test:**
1. Run `node jwt-auth-server.js`.
2. **Get Token**:
curl -X POST -H "Content-Type: application/json" \
-d '{"username": "user1", "password": "pass111"}' \
http://localhost:3000/login
You will receive a response like this: `{“message”:”Login successful”,”token”:””}`. Use this token to access protected routes.