Creating a REST API Part 1: Web Server Basics
We'll start our REST API project by creating some initial directories/ files, create a web server module and wire things up so that it starts up and shuts down correctly.
Join the DZone community and get the full member experience.
Join For FreeThe web server is one of the most important components of a REST API. In this post, you will start your REST API project by creating some initial directories and files. Then you'll create a web server module and wire things up so that the web server starts up and shuts down correctly. Finally, you will add some basic logging capabilities to the web server.
Starting Up and Saying Hello
The code in this project will be organized using a generic directory structure that can be adjusted and built out over time as needed. In the VM, open a new terminal by going to Applications > Favorites > Terminal, then run the following commands.
cd ~
mkdir hr_app
cd hr_app/
touch index.js
mkdir config
touch config/web-server.js
mkdir controllers
mkdir db_apis
mkdir services
touch services/web-server.js
The project will be contained in the hr_app directory. The directories within hr_app should be easy to understand based on their names and the "High-level components" overview in the parent post. The index.js file can be thought of as the "main" file in a Java app - it will be the entry point of the application. We will be adding code to that file and the web-server.js files in the config and services directories in this post.
Go to Applications > Favorites > Files to open the file browser and navigate to Home > hr_app > config. Double-click web-server.js to open it in the gedit text editor. Copy and paste the following code into the file, then save your changes.
config/web-server.js
module.exports = {
port: process.env.HTTP_PORT || 3000
};
This is a simple JavaScript module that exposes a single property named port
. In Node.js, the process
object has an env property that contains the user environment. I'm using that to set the value of port
to the value of the environment variable HTTP_PORT
. If that environment variable isn't defined, the default value will be 3000. It's common to derive ports from environment variables as they may vary in different environments or be randomly assigned at runtime.
With the configuration file ready, you can turn your attention to the web server module. Open the services/web-server.js file in gedit. Copy and paste the following code into the file and save your changes.
services/web-server.js
const http = require('http');
const express = require('express');
const webServerConfig = require('../config/web-server.js');
let httpServer;
function initialize() {
return new Promise((resolve, reject) => {
const app = express();
httpServer = http.createServer(app);
app.get('/', (req, res) => {
res.end('Hello World!');
});
httpServer.listen(webServerConfig.port, err => {
if (err) {
reject(err);
return;
}
console.log(`Web server listening on localhost:${webServerConfig.port}`);
resolve();
});
});
}
module.exports.initialize = initialize;
Here's a breakdown of the web server module so far:
- Lines 1-3: Several modules are required in. The HTTP module is included with Node.js but the express module will need to be installed via npm.
- Lines 7-27: A function named
initialize
is declared. The function immediately returns a promise which is resolved or rejected depending on whether the web server is started successfully.- Lines 9-10: A new Express application is created (which is really just a function) and then used to create an HTTP server via the HTTP module.
- Lines 12-14: The app's
get
method is used to add a handler for GET requests that come in on the root (/) path. The callback function (also called a middleware function) will be invoked when such a request is received and it will use the "response" parameter (res
) to send a "Hello World!" response to the client. - Lines 16-25: The server's
listen
method is used to bind to the specified port and start listening for incoming requests.
- Line 29: The
initialize
function is exported from the module so it can be invoked externally.
With the web server module defined, you can put it to use in the main module. Open index.js, copy and paste the following code into it, and save your changes.
const webServer = require('./services/web-server.js');
async function startup() {
console.log('Starting application');
try {
console.log('Initializing web server module');
await webServer.initialize();
} catch (err) {
console.error(err);
process.exit(1); // Non-zero failure code
}
}
startup();
The main module brings in the web server module, and then it defines and invokes an async function named startup
. Because the web server module's initialize
function returns a promise, you can use it with async/await and wrap it in a try-catch block (click here to learn more about async/await). If the initialize
function finishes successfully, then the web server will be running; otherwise, any exceptions will be caught and handled.
All you need to do now is initialize npm and install Express - then you can run the app. Run the following commands in the terminal from the hr_app directory.
npm init -y
npm install express -s
node .
The npm init
command is used to create the package.json file, which npm uses as a manifest file (the -y
flag accepts the default options). The npm install
command is used to install express (the -s
flag adds Express to the list of dependencies in the package.json). Npm stores the modules you install in the node_modules directory. It also creates a file named package.lock.json to ensure identical module trees across a team of developers.
Do you see a message telling you the web server is listening on localhost:3000? Congratulations, you created an Express based web server! Open Firefox and navigate to http://localhost:3000.
And there it is, yet another "Hello World". While not particularly exciting, it's an important first step for your API. When you're ready, you can shut down the server by returning to the terminal and pressing Ctrl+C.
Controlling the Shutdown
While shutting down by pressing Ctrl+C works, you didn't have much control over how it happened. To control the shutdown process, you will need to explicitly close the web server and exit the Node.js process.
Append the following code to the bottom of the web server module.
services/web-server.js
// *** previous code above this line ***
function close() {
return new Promise((resolve, reject) => {
httpServer.close((err) => {
if (err) {
reject(err);
return;
}
resolve();
});
});
}
module.exports.close = close;
The close
function returns a promise that is resolved when the web server is successfully closed. The httpServer.close
method stops new connections from being established, but it will not force already opened connections closed. Depending on how many connections are open and what they are doing, you might have to wait a bit for the callback to fire. Though you will not do it in this module, it is possible to use custom code or npm modules, such as http-shutdown, to force open connections closed.
With the close
function in place, the main module can be updated to invoke it at the right times. Append the following code to the end of index.js.
// *** previous code above this line ***
async function shutdown(e) {
let err = e;
console.log('Shutting down');
try {
console.log('Closing web server module');
await webServer.close();
} catch (e) {
console.log('Encountered error', e);
err = err || e;
}
console.log('Exiting process');
if (err) {
process.exit(1); // Non-zero failure code
} else {
process.exit(0);
}
}
process.on('SIGTERM', () => {
console.log('Received SIGTERM');
shutdown();
});
process.on('SIGINT', () => {
console.log('Received SIGINT');
shutdown();
});
process.on('uncaughtException', err => {
console.log('Uncaught exception');
console.error(err);
shutdown(err);
});
SIGINT and SIGTERM events are related to signals that can be sent to the process to shut it down, such as when Ctrl+C is pressed. The uncaughtException
event will occur when a JavaScript error is thrown but not caught and handled with a try-catch statement.
Try running and shutting down the application again. You'll know everything is working correctly when you see the "shutting down" messages in the terminal.
Adding Web Server Logging
There's just one last thing to round out our web server module: HTTP logging. There are various modules you could use for this type of logging, but morgan is known to be one of the best (and simplest). Let's install morgan with npm.
npm install morgan -s
Next, add the following line to services/web-server.js under the line that requires express (line 2).
const morgan = require('morgan');
Now you can incorporate morgan as a middleware function that all requests will go through with app.use
. Add this line before the app.get
call that produces the "hello world" message.
// Combines logging info from request and response
app.use(morgan('combined'));
// *** app.get call below this line ***
Note that app.use
is creating a pipeline of middleware functions that can interact with HTTP requests and responses. The middleware will execute in the order that they are included.
Restart the app and position the terminal so you can see it and Firefox at the same time. Each time you reload the page in Firefox you should see a new log entry appear in the terminal. By default, morgan streams log info to STDOUT (which is displayed in the terminal).
This is as far as I'll go with logging in this series. However, in a production application, you might want to stream morgan's output to an HTTP access log file. You might also consider a logging utility, such as Winston, for debugging and tracing as an alternative to the console
object.
The next post in this series focuses on database basics-including connection pooling-that will help you understand and build REST APIs for Node.js.
Published at DZone with permission of Dan McGhan, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments