Being able to write and run JavaScript on your server is nice.
Most servers will want to vend out and process content living in
files, too. Your next job is to make your server read files
from a subfolder and send them in a response back to the browser.
This is similar to what browser-sync
was doing for you in earlier chapters.
Create a new folder named app inside of your chattrbox project folder. In it, create an index.html file with the following text:
Hello, File!
You do not need any actual HTML in this file – it only needs some content that can be read. Your project folder should look like Figure 15.11.
In index.js, import the Node.js file system module, fs
, and call its
readFile method.
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { console.log('Responding to a request.');res.end('<h1>Hello, World</h1>');fs.readFile('app/index.html', function (err, data) { res.end(data); }); }); server.listen(3000);
The readFile method takes a file
name and a callback. Inside your callback, you sent the contents
of the file instead of the HTML text using
res.end
.
Notice that your callback accepts an
err
argument as well as the data from reading
the file. This is a Node.js programming convention that we will
discuss later in this chapter.
nodemon
should have restarted your program,
so you can go directly to your browser and reload.
In your browser, you should see exactly what you wrote in your index.html file:
Hello, File!
This is a good start, but your chat application will need to do more than serve a single HTML file. That HTML file may request other CSS or JavaScript files. To fulfill those requests, your node program will need to understand what file is being requested and where to look for the requested file. You will work on that next.
First, you need to get the URL path from the request object. If
the path is just '/'
, it is best to return
the index.html file. This is a common
convention from the early days of the web.
Otherwise, you should try to return the file the request object is asking for.
In index.js, update your callback to check what file the browser is requesting.
var http = require('http'); var fs = require('fs'); var server = http.createServer(function (req, res) { console.log('Responding to a request.'); var url = req.url; var fileName = 'index.html'; if (url.length > 1) { fileName = url.substring(1); } console.log(fileName); fs.readFile('app/index.html', function (err, data) { res.end(data); }); }); server.listen(3000);
Using the request object’s url
property, you
can see whether the browser is asking for the default
(index.html) or another file. If it is
another file, you call url.substring(1) to
strip off the first character, which will be a
'/'
.
For now, you are just logging the filename to the console.
After nodemon
restarts your program, try
going to http://localhost:3000/woohoo
or any
other path, including the default '/'
path. The results in your terminal will look something like Figure 15.12.
(Recall from Chapter 2 that browsers will automatically ask for a favicon.ico file, so you may see a request for it logged to the terminal as well.)
Now it is time to make use of this path information.
You could just pass the fileName to
fs.readFile, but it is better to use the
path
module, which has utilities for handling and transforming file paths.
One small but important reason for using the module
is that some operating systems use a forward slash and some
use a backslash. The path
module handles these differences with ease.
Update index.js to import the path
module and use it to find the file that was requested.
var http = require('http'); var fs = require('fs'); var path = require('path'); var server = http.createServer(function (req, res) { console.log('Responding to a request.'); var url = req.url; var fileName = 'index.html'; if (url.length > 1) { fileName = url.substring(1); } console.log(fileName); var filePath = path.resolve(__dirname, 'app', fileName); fs.readFile('app/index.html'filePath, function (err, data) { res.end(data); }); }); server.listen(3000);
Test a few filepaths in the browser to make sure your application still works the same.
The default path should return index.html, and nonexistent
paths (like '/woohoo/'
) should show nothing and log out
their filename.
Next, create a test.html file in the app folder. Write the following inside it:
Hola, Node!
Try to access it in the browser. Your node program should return it without any trouble (Figure 15.13).
You have added code that successfully serves a specific file based on the URL path. The next thing to do is abstract out that functionality into its own module.
Your callback has (at least) two jobs. It figures out what file is being requested, and it reads that file to send back in the response. To make the code a bit more modular and maintainable, one of those responsibilities should be moved to its own module.
In CoffeeRun, you declared modules in an IIFE that assigned a value
to a property of the global namespace. Modules in Node
programs work differently. You still write your module code
in a file by itself, but you do not need the IIFE.
Create a new file called extract.js in the same directory as your index.js (not in the app directory). Add a function called extractFilePath that finds the appropriate file. (This code is very similar to what you already wrote in index.js.)
var path = require('path'); var extractFilePath = function (url) { var filePath; var fileName = 'index.html'; if (url.length > 1) { fileName = url.substring(1); } console.log('The fileName is: ' + fileName); filePath = path.resolve(__dirname, 'app', fileName); return filePath; };
You have taken much of the code from index.js
and placed it in its own function, called extractFilePath.
Next, make the extractFilePath function available
so that other modules can import it with require.
To do this, assign extractFilePath to
a global variable named module.exports.
This is a special variable provided by Node.
Whatever value is assigned to it is the value other
modules are able to import. Any other variables or
functions will not be visible to other modules.
... filePath = path.resolve(__dirname, 'app', fileName); return filePath; }; module.exports = extractFilePath;
This new line tells Node that when
you import the extract
module by calling
require('./extract')
, the value returned is
the extractFilePath function.
Do that now in index.js.
Update index.js to use your new
extract
module instead of handling those
responsibilities.
var http = require('http'); var fs = require('fs');var path = require('path');var extract = require('./extract'); var server = http.createServer(function (req, res) { console.log('Responding to a request.');var url = req.url;var fileName = 'index.html';if (url.length > 1) {fileName = url.substring(1);}console.log(fileName);var filePath = path.resolve(__dirname, 'app', fileName);var filePath = extract(req.url); fs.readFile(filePath, function (err, data) { res.end(data); }); }); server.listen(3000);
You imported your custom module using the require function. You assigned the value of the module to the new variable extract. Now you are able to use the extract function just as you would the extractFilePath function.
After nodemon
has reloaded your code, test
some URL paths and confirm that the default
index.html and test.html
still load. Also, make sure that nonexistent paths come up as a
blank page and without an error.
18.225.235.144