Manual Routing in Node.js
When building a web server in Node.js without a framework, the built-in http
module is usually used. The ability to use the http.createServer()
method to create an HTTP server is provided by this module. The request
(req
) and response
(res
), two essential objects, are passed to the callback function of the createServer()
method when a request is received.
The entire content of the incoming HTTP request
is contained in the request object. Request.url
is one of its numerous characteristics that is very crucial for basic routing because it provides the path section of the URL that the client is requesting. By looking at this request.url
value, you may use conditional logic to decide what content or action should be taken for a certain path. This is typically done with if/else if
or switch
statements.
Here’s a basic example of how you might set up manual routing:
const http = require('http'); // Loads the http module
const port = 3000; // Define a port number
const server = http.createServer((request, response) => { // Create a server instance
// Check the requested URL
if (request.url === '/') {
// Route for the homepage
response.writeHead(200, { 'Content-Type': 'text/plain' }); // Set HTTP status and content type
response.end('Welcome to the Node.js homepage!\n'); // Send response body and close connection
} else if (request.url === '/about') {
// Route for the about page
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('This is the about page for our Node.js application.\n');
} else {
// Default route for unlisted URLs (404 Not Found)
response.writeHead(404, { 'Content-Type': 'text/plain' }); // Set 404 status
response.end('404 Not Found: The page you requested could not be found.\n'); // Custom 404 message
}
});
server.listen(port, () => { // Start the server and listen on the specified port
console.log(`Server running at http://localhost:${port}/`); // Log server status
});
To run this code, save it as app.js
(or any other filename) and execute it from your command line within the same directory: node app.js
.
Code Output: When you run node app.js
, you’ll see: Server running at http://localhost:3000/
Now, open your web browser or use curl
to access the following URLs:
http://localhost:3000/
: Output:Welcome to the Node.js homepage!
http://localhost:3000/about
: Output:This is the about page for our Node.js application.
http://localhost:3000/contact
(or any other unlisted URL): Output:404 Not Found: The page you requested could not be found.
Serving Different Data Types: Respond with Plain Text or JSON Data
When constructing the server’s response
to the client, the response object is essential. Setting HTTP headers, such as the Content-Type
header, which informs the client of the type of data being returned, is one of its features.
- Plain Text: As the aforementioned example illustrates, you should specify the
Content-Type
to'text/plain'
for answers that are plain text. After that,response.end()
can be used to convey the string data. If your response is longer, you may need to useresponse.write()
more than once before usingresponse.end()
. - JSON Data: APIs frequently send and receive data in the text-based JSON (JavaScript Object Notation) data interchange standard. Setting the
Content-Type
header to'application/json'
allows you to provide JSON. Before delivering it usingresponse.end()
, you must first useJSON.stringify()
to turn your JavaScript object into a JSON string.
Let’s modify our server to include a JSON endpoint:
const http = require('http');
const port = 3000;
const server = http.createServer((request, response) => {
if (request.url === '/') {
// Homepage (Plain Text)
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Welcome to the Node.js homepage!\n');
} else if (request.url === '/api/data') {
// JSON API endpoint
const data = {
message: 'Hello from Node.js API!',
timestamp: new Date().toISOString(),
status: 'success'
};
response.setHeader('Content-Type', 'application/json'); // Set Content-Type for JSON
response.writeHead(200); // Set HTTP status to 200 OK
response.end(JSON.stringify(data)); // Send JSON string
} else if (request.url === '/about') {
// About page (Plain Text)
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('This is the about page for our Node.js application.\n');
} else {
// 404 Not Found
response.writeHead(404, { 'Content-Type': 'text/plain' });
response.end('404 Not Found: The page you requested could not be found.\n');
}
});
server.listen(port, () => {
console.log(`Server running at http://localhost:${port}/`);
});
Code Output:
http://localhost:3000/api/data
: Output: {"message":"Hello from Node.js API!","timestamp":"2024-XX-XXTXX:XX:XX.XXXZ","status":"success"}
(The timestamp will vary based on current time).
Handling Not Found (404): Implement a Default Response for Unlisted URLs
Managing requests for URLs that your server does not specifically route is crucial. Sending a 404 Not Found
HTTP status code is the standard method for doing this. http.STATUS_CODES
, which is provided by the http
module, can return the standard message for a specified status code (for example, 'Not Found'
).
In our combined example, the else
block effectively serves as the 404 handler:
// ... (previous code) ...
} else {
// Default route for unlisted URLs (404 Not Found)
response.writeHead(404, { 'Content-Type': 'text/plain' }); // Set 404 status
// You could also use: response.end(`${response.statusCode} ${http.STATUS_CODES[response.statusCode]}: The page you requested could not be found.\n`);
response.end('404 Not Found: The page you requested could not be found.\n'); // Custom 404 message
}
});
// ... (rest of the server setup) ...
By using this method, a client will be sent a 404 Not Found
response, which makes it obvious that the resource is unavailable, if they request http://localhost:3000/nonexistent
(or any other path that isn’t specifically specified by an if
or else if
condition).
Essentially, creating a simple Node.js server without a framework is similar to building a structure out of nothing but your own designs and raw materials. Every HTTP request and response, every brick and beam, is entirely within your control. You can choose exactly where each piece of data flows and how it is displayed. This is in contrast to employing a framework, which is similar to using prefabricated parts and adhering to pre-existing architectural patterns. This method speeds up the process but gives you less control over the fundamental components.