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.
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
We can re-use much of what we covered in Chapter 1. We are going to:
Listen for a GET request on the
Create a request handler that reads the
products.jsonfile 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
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:
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,
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
__dirname is useful for creating absolute paths to files using the current module as a starting point.
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/data.txt, and in
fs.readFile('data.txt', console.log). If we were to run
node example.jsfrom within
data.txtwould be loaded and logged correctly. However, if instead we were to run
/Users/fullstack, we would get an error. This is because Node.js would unsuccessfully look for
/Users/fullstackdirectory, because that is our current working directory.
__dirnameis 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.
__dirnameis always set to the directory name of the current module. For example, if we created a file
console.log(__dirname), when we run
/Users/fullstack, we could see that
/Users/fullstack. Similarly, if we wanted the module's file name instead of the directory name, we could use the
After we use
fs.readFile(), we parse the data and use the
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
res.status() to set the status code to 500 (server error) and send a JSON error message.