A Complete Server: Getting Started

We're ready to start building a complete server API. We're going to build a production e-commerce app that can be adapted to be used as a starting place for almost any company.

Soon, we'll cover things like connecting to a database, handling authentication, logging, and deployment. To start, we're going to serve our product listing. Once our API is serving a product listing, it can be used and displayed by a web front-end. However, we're going to build our API so that it can also be used by mobile apps, CLIs, and programmatically as a library.

Our specific example will be for a company that sells prints of images -- each product will be an image. Of course, the same concepts can be applied if we're selling access to screencasts, consulting services, physical products, or ebooks. At the core, we're going to need to be able to store, organize, and serve our product offering.

Getting Started#

To get started with our app, we'll need something to sell. Since our company will be selling prints of images, we'll need a list of images that are available for purchase. We're going to use images from Unsplash.

Using this list of images, we'll build an API that can serve them to the browser or other clients for display. Later, we'll cover how our API can handle purchasing and other user actions.

Our product listing will be a JSON file that is an array of image objects. Here's what our products.json file looks like:

As we can see, each product in the listing has enough information for our client to use. We can already imagine a web app that can display a thumbnail for each, along with some tags and the artist's name.

Our listing is already in a format that a client can readily use, therefore our API can start simple. Using much of what we've covered in chapter 1, we can make this entire product listing available at an endpoint like http://localhost:1337/products. At first, we'll return the entire listing at once, but quickly we'll add the ability to request specific pages and filtering.

Our goal is to create a production-ready app, so we need to make sure that it is well tested. We'll also want to create our own client library that can interact with our API, and make sure that is well tested as well. Luckily, we can do both at the same time.

We'll write tests that use our client library, which in turn will hit our API. When we see that our client library is returning expected values, we will also know that our API is functioning properly.

Serving the Product Listing#

At this stage, our expectations are pretty clear. We should be able to visit http://localhost:1337/products and see our product listing. What this means is that if we go to that url in our browser, we should see the contents of products.json.

We can re-use much of what we covered in Chapter 1. We are going to:

  • Create an express server

  • Listen for a GET request on the /products route

  • Create a request handler that reads the products.json file from disk and sends it as a response

Once we have that up, we'll push on a bit farther and:

  • Create a client library

  • Write a test verifying that it works as expected

  • View our products via a separate web app

Express Server#

To start, our express server will be quite basic:

Going through it, we start by using require() to load express and core modules that we'll need later to load our product listing: fs and path.

Next, we create our express app, set up our single route (using a route handler that we will define lower in the file), and start listening on our chosen port (either specified via environment variable or our default, 1337):

Finally, we define our route handler, listProducts(). This function takes the request and response objects as arguments, loads the product listing from the file system, and serves it to the client using the response object:

Here, we use the core path module along with the globally available __dirname string to create a reference to where we are keeping our products.json file. __dirname is useful for creating absolute paths to files using the current module as a starting point.

Unlike require(), fs methods like fs.readFile() will resolve relative paths using the current working directory. This means that our code will look in different places for a file depending on which directory we run the code from. For example, let's imagine that we have created files /Users/fullstack/project/example.js and /Users/fullstack/project/data.txt, and in example.js we had fs.readFile('data.txt', console.log). If we were to run node example.js from within /Users/fullstack/project, the data.txt would be loaded and logged correctly. However, if instead we were to run node project/example.js from /Users/fullstack, we would get an error. This is because Node.js would unsuccessfully look for data.txt in the /Users/fullstack directory, because that is our current working directory.

__dirname is one of the 21 global objects available in Node.js. Global objects do not need us to use require() to make them available in our code. __dirname is always set to the directory name of the current module. For example, if we created a file /Users/fullstack/example.js that contained console.log(__dirname), when we run node example.js from /Users/fullstack, we could see that __dirname is /Users/fullstack. Similarly, if we wanted the module's file name instead of the directory name, we could use the __filename global instead.

After we use fs.readFile(), we parse the data and use the express method res.json() to send a JSON response to the client. res.json() will both automatically set the appropriate content-type header and format the response for us.

If we run into an error loading the data from the file system, or if the JSON is invalid, we will send an error to the client instead. We do this by using the express helper res.status() to set the status code to 500 (server error) and send a JSON error message.


This page is a preview of Fullstack Node.js

Start a new discussion. All notification go to the author.