From Serverless Applications with Node js by Slobodan Stojanović and Aleksandar Simović

This article, excerpted from Serverless Applications with Node js, explores building a back-end app and API to process and store orders for aunt Maria’s pizzeria.


Save 37% on Serverless Applications with Node js Just enter code slstojanovic into the discount code box at checkout at manning.com.


Aunt Maria’s a strong-willed person. For more than three decades, she’s been managing her pizzeria, which was the place where many generations of people from the neighborhood spent time with their families, laughed together, and even went on romantic dates. But recently, her pizzeria has seen some rough times. Just the other day, she told you that she’s seeing fewer and fewer customers. The rise of technology has caused her customers to order more online via websites or their phones. Some new companies have started stealing her customers. The new Chess’s pizzeria, for example, has a mobile app with pizza previews and online ordering, and also a chatbot for ordering via various messenger applications. Your aunt’s customers like her pizzeria, but most want to order from their homes via application, and her three-decades-old business has started to wane.

The pizzeria already has a website, but it needs a back-end application to process and store pizzas and orders.

Assembling pizza ingredients: Building the API

Aunt Maria is happy and grateful that you’re going to help her get back on her feet. She even made you her famous pepperoni pizza! (Hopefully, you aren’t hungry at this moment!)

Aunt Maria already has a website, and you’ll build a back-end application—more precisely, an API—to enable her customers to preview and order pizzas. The API is responsible for serving pizza and order information, as well as handling pizza orders. Later, Aunt Maria would also like to add a mobile application, which would consume your API’s services.

To start gently, the first API endpoints handle some simple business logic and return static JSON objects. You can see the broad overview of your initial application structure in figure 1. The figure also shows the crude HTTP request flow through your API.


Figure 1 A broad overview of the Pizza API you’ll build in this article


Here’s the list of features we’ll cover for the initial API:

  • Listing all pizzas
  • Retrieving the pizza orders
  • Creating a pizza order
  • Updating a pizza order
  • Canceling a pizza order

These listed features are small and simple; therefore, you’ll implement them in a single Lambda function.

Even though you might feel that you should separate each feature into a separate function, for now it’s simplest to put everything in the same Lambda, because the functions are tightly coupled. If you were to do inventory tracking as well, it’d go separately from the start.

Each of the written list features needs a separate route to the corresponding handler within your function. You can implement the routing yourself, but Claudia has a tool to help you with that task: the Claudia API Builder.

Figure 2 shows a more detailed overview of how to route and handle your pizza and order features within your Lambda function by using Claudia API Builder.


Figure 2 A visual representation of the AWS Lambda function that handles pizza and order processing


The figure shows that after receiving the request from the API Gateway, the Claudia API Builder redirects the requests to your defined routes and their corresponding handlers.

NOTE

At the moment of this writing the AWS API Gateway can be used in two modes:

  • with models and mapped templates for requests
  • responses proxy pass-through

Claudia API Builder is using proxy pass-through to capture all of the HTTP request details and structure them in a JS-developer-friendly way.

To learn more about proxy pass-through and models and mapped templates, you can read the official documentation at docs.aws.amazon.com/../how-to-method-settings.html.

Which pizza can I GET?

As a first method of your Pizza API you’ll create a GET pizza service which should list all available pizzas. To do this, you’ll need to fulfill these prerequisites:

  • Own an AWS account and properly set the AWS credentials file.
  • Install Node.js and its package manager, NPM.
  • Install Claudia from NPM as a global dependency.

If you’re not familiar with these steps or aren’t sure whether you’ve completed them, jump to appendix A, which guides you through each setup process.

NOTE

Code examples

From this point onward, there’ll be many code examples. We highly recommend that you try all of them, even if they feel familiar. You can use your favorite code editor unless stated otherwise.

Now that you’re fully set, you can start by creating an empty folder for your first serverless application. You can name your project folder as you like. In this article the application folder’s name is pizza-api. After you’ve created it, open your terminal, navigate to your new folder, and initialize the Node.js application. After your app is initialized, install the claudia-api-builder module from NPM as a package dependency, as explained in appendix A.

The next step is to create your application’s entry point. Create a file named api.js inside your pizza-api folder, and open it with your favorite code editor.

NOTE

ES6 syntax for the code examples

All the code examples in the article use the ES6/ES2015 syntax. If you aren’t familiar with ES6 features, such as arrow functions and/or template strings, see Manning’s ES6 in Motion, by Wes Higbee, or the second edition of Secrets of the JavaScript Ninja, Second Ed. by John Resig, Bear Bibeault, and Josip Maras.

To create an API route, you need an instance of the Claudia API Builder, as it’s a class and not a utility function. At the beginning of your api.js file require and instantiate the claudia-api-builder.

Now you’re able to use Claudia API Builder’s built-in router. To implement the GET /pizzas route, you need to use the get method of your Claudia API Builder instance. The get method receives two arguments—a route and a handler function. As the route parameter, you should pass the string /pizzas, and as the handler, pass an anonymous function.

The Claudia API Builder anonymous handler function has one major difference compared with Express.js. In Express.js, you have both the response and request as callback function arguments, but the Claudia API Builder’s callback function has only the request. To send back the response, you return the result.

Your GET /pizzas route should show a list of pizzas, but for now you’ll return a static array of pizzas from Aunt Maria’s pizzeria: Capricciosa, Quattro Formaggi, Napoletana, and Margherita.

Finally, you need to export your API instance, because Claudia API Builder is using the exported instance to fit it into your Lambda function as middleware.

At this point, your code should look like listing 1.

Listing 1 The GET /pizzas handler of your Pizza API

 
 'use strict'
  
 const Api = require('claudia-api-builder')   
 const api = new Api()                        
  
 api.get('/pizzas', () => {                   
   return [                                   
     'Capricciosa',
     'Quattro Formaggi',
     'Napoletana',
     'Margherita'
   ]
 })
  
 module.exports = api                         
 

  Require the Claudia API Builder module

  Create an instance of Claudia API Builder

  Define a route and a handler

  Return a simple list of all pizzas

  Export your Claudia API Builder instance

This is all it takes to make a simple serverless function. Before celebrating or even popping a champagne bottle, you should deploy your code to your Lambda function. To do so, jump back to your terminal and unleash the power of Claudia.

One of the main Claudia goals is single-command deployment. With Claudia, deploying your API takes a simple claudia create command. This command requires only two options: the AWS region where you want your API to be deployed and your application’s entry point. The options are passed as flags; to deploy your API execute the claudia create command with --region and --api-modules flags, as shown in listing 2.

For your region, you should choose the closest one to your users to minimize latency. The closest region to Aunt Maria’s pizzeria is in Frankfurt, Germany, and it’s called eu-central-1. You can see all the available regions in the official AWS documentation: http://docs.aws.amazon.com/general/latest/gr/rande.html#lambda_region.

Your api.js file is your API’s entry point. Claudia automatically appends the .js extension; type api as your application’s entry point.

NOTE

Name and location of your entry point are up to you as long as you provide a correct path to the entry point claudia create command. For example, if you name it index.js and put it in the src folder, the flag in the Claudia command should be --api-module src/index.

Listing 2 Deploying an API to AWS Lambda and API Gateway using Claudia

 
 claudia create \            
   --region eu-central-1 \   
   --api-module api          
 

  Create and deploy a new Lambda function

  Select the region where you want your function to be deployed

  Tell Claudia that you are building an API and that your API’s entry point is api.js

NOTE

Shell commands for Windows users

Some of the commands in the article are split into multiple lines for readability and annotations. If you’re a Windows user you might need to join those commands into a single line and remove backslashes (\).

After a minute Claudia will successfully deploy your API. You should see a response similar to listing 3. The command response has useful information about your Lambda function and your API, such as the base URL of your API, the Lambda function’s name, and the region.

During the deployment, Claudia created a claudia.json file in the root of your project along with some similar information, but without your base API URL. This file is for Claudia to relate your code to a certain Lambda function and API Gateway instance. The file is intended for Claudia only; don’t change it by hand.

NOTE

Deployment issues

In case you faced deployment issues, such as a credentials error, make sure you’ve properly set up everything as described in appendix A.

Listing 3 The claudia create command response

 
 {
   "lambda": {   
     "role": "pizza-api-executor",
     "name": "pizza-api",
     "region": "eu-central-1"
   },
   "api": {   
     "id": "g8fhlgccof",
     "module": "api",
     "url": "https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/latest"   
   }
 }
 

  Lambda function information

  API information

  Your API’s base URL

Now it’s time to “taste” your API. You can try it directly from your favorite browser. Visit the base URL from your claudia create response, but don’t forget to append your route to the base URL. It should look similar to: https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/latest/pizzas. When you open your modified base URL link in your browser, you should see the following:

 
 ["Capricciosa","Quattro Formaggi","Napoletana","Margherita"]
 

Congratulations; you had your first taste of building a serverless API with Claudia! If this was your first time, you should be proud of yourself, and this is a good time to pause.

Structuring your API

Before rushing to add more features, you should always try to spend a few minutes to rethink your API structure and organization. Adding all route processors directly into the main file makes it difficult to understand and maintain, and you should ideally split handlers from routing/wiring. Smaller code files are easier to understand and work with than with one monster file.

Considering application organization, at the moment of this writing there aren’t any specific best practices. Claudia gives you complete freedom on that topic as well. In your case, for your Pizza API, because the part for handling pizzas and orders isn’t going to be huge, the recommendation would be to move all route handlers to a separate folder and keep only the routes within your api.js file. After that, because the pizza list should have more pizza attributes than only names, you should move it to the separate file. You can even go a step further and create a folder for the data, as you did for the pizza list we mentioned earlier. After you apply these recommendations, your code structure should look similar to figure 3.


Figure 3 File structure of the Pizza API project


The first modification is moving the list of pizzas to a separate file and extending the list with additional information, such a pizza ID and ingredients. To do this, create a folder in the root of your Pizza API project, and name it data. Then create a file in your new folder, and name it pizzas.json. The new file should contain the content from listing 4.

Listing 4 JSON that contains the info about pizzas

 
 [     
   {   
     "id": 1,
     "name": "Capricciosa",
     "ingredients": [
       "tomato sauce", "mozzarella", "mushrooms", "ham", "olives"
     ]
   },
   {   
     "id": 2,
     "name": "Quattro Formaggi",
     "ingredients": [
       "tomato sauce", "mozzarella", "parmesan cheese", "blue cheese", "goat cheese"
     ]
   },
   {   
     "id": 3,
     "name": "Napoletana",
     "ingredients": [
       "tomato sauce", "anchovies", "olives", "capers"
     ]
   },
   {   
     "id": 4,
     "name": "Margherita",
     "ingredients": [
       "tomato sauce", "mozzarella"
     ]
   }
 ]
 

  This JSON file is an array of pizza objects

  Each pizza object has a pizza ID, name, and ingredients

Your next step is to move the getPizzas handler to a separate file. First, create a folder handler in your project root, and create a get-pizzas.js file inside it.

In your new get-pizzas file is the getPizzas handler, which should return the list of pizzas from listing 4. First, you need to import the pizza list from the JSON file you created. Second, you need to create a getPizzas handler function and export it to require it from your entry file. Then, instead of returning the pizza list, you should go a step further and return one pizza if a pizza ID was passed as a parameter to your getPizzas handler. To return only one pizza, you can use the Array.find method, which searches for a pizza by the pizza ID from your pizza list. If a pizza is found, you should return it as a handler result. In case there aren’t any pizzas with that ID, your application should throw an error.

The updated code of your new pizza handler should look similar to listing 5.

Listing 5 Your get pizza handler with a pizza ID filter in a separate file

 
 const pizzas = require('../data/pizzas.json')                
  
 function getPizzas(pizzaId) {    
   if (!pizzaId)                  
     return pizzas
  
   const pizza = pizzas.find((pizza) => {                     
     return pizza.id == pizzaId   
   })
  
   if (pizza)
     return pizza
  
   throw new Error('The pizza you requested was not found')   
 }
  
 module.exports = getPizzas       
 

  Import the list of pizzas from the data directory

  Create the getPizzas handler function

  If a pizza ID is not passed, return the full pizza list

  Otherwise, search the list by the passed pizza ID

  Note == instead of === — that’s because pizzaId is passed as a string, and you don’t want it to be a strict match, as in the database it may be an integer

  Throw an error if the application couldn’t find the selected pizza

  Export the getPizzas handler

You should also remove the previous getPizzas handler code from your API entry point file—api.js. Delete everything between importing Claudia API Builder and the end, where you’re exporting your Claudia API Builder instance.

After the line where you’re importing Claudia API Builder, import the new getPizzas handler from your handlers folder:

 
 const getPizzas = require('./handlers/get-pizzas')
 

NOTE

You should also create a handler for the GET route for the root path—/, which should return a static message to the user. Though this is optional, it’s highly recommended. Your API’s more user friendly when returning some friendly message instead of an error when someone’s querying your API’s base URL.

Next, you should add the route for getting the pizza list, but this time, you’ll use the get-pizzas handler that you created for the route handling. You should import the file at the beginning of your api.js entry file. If you remember, your get-pizzas handler can also filter pizzas by their ID, and you should add another route that returns a single pizza.

Write that route to accept a GET request for the /pizzas/{id} url. The /{id} part is the dynamic route parameter that tells your handler which pizza ID the user requested. Like Express.js, the Claudia API Builder supports dynamic route parameters, but it uses a different syntax. This is the reason why it has /{id} instead of /:id. The dynamic path parameters are available in the request.pathParams object.

 
 api.get('/pizzas/{id}', (request) => {  
   return getPizzas(request.pathParams.id)
 }, {  
   error: 404
 })
 

Finally, if your handler hasn’t found the pizza you wanted, you should return a 404 error.

By default, the API Gateway returns HTTP status 200 for all requests. The Claudia API Builder helps you by setting some sane defaults, such as status 500 for errors to allow your request to process the errors in your JavaScript promises, in your front-end applications.

To customize the error status, you should pass a third parameter to the api.get function. For example, in your get /pizza/{id} function handler, beside the path and your handler function, you can pass an object with custom headers and statuses. To set the status error to 404, pass an object with the error: 404 value in it. You can see how your fully updated api.js file should look in listing 6.

Listing 6 API

 
 'use strict'
  
 const Api = require('claudia-api-builder')
 const api = new Api()
  
 const getPizzas = require('./handlers/get-pizzas')   
  
 api.get('/', () => 'Welcome to Pizza API')   
  
 api.get('/pizzas', () => {   
   return getPizzas()
 })
  
 api.get('/pizzas/{id}', (request) => {   
   return getPizzas(request.pathParams.id)
 }, {
   error: 404   
 })
  
 module.exports = api
 

  Import the get-pizzas handler from your handlers directory

  Add a simple root route that returns static text to make your API user-friendly

  Replace the inline handler function with the new one you imported

  Add the route for finding one pizza by its ID

  Customize success and error status codes

Now you should deploy your API again. To update an existing Lambda function along with its API Gateway routes, Claudia has an update command. To update your Lambda function with your new changes, run this command from your terminal:

 
 claudia update
 

NOTE

Because of the claudia.json file, the claudia update command knows exactly which Lambda function the files are deployed to. The command can be customized with a --config flag. For more information see the official documentation at: https://github.com/claudiajs/claudia/blob/master/docs/update.md.

After a minute you should see a similar response to the one in listing 7. After processing the command and redeploying your application, Claudia will print out some useful information about your Lambda function and your API in the terminal. That information includes the function name, Node.js runtime, timeout, function memory size, and the base URL of your API.

Listing 7 The printed information after running the claudia update command

 
 {
   "FunctionName": "pizza-api",   
   "Runtime": "nodejs6.10",       
   "Timeout": 3,        
   "MemorySize": 128,   
   "Version": "2",      
   "url": "https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/chapter2",   
   "LastModified": "2017-07-15T14:48:56.540+0000",
   "CodeSha256": "0qhstkwwkQ4aEFSXhxV/zdiiS1JUIbwyKOpBup35l9M=",
   // Additional metadata
 }
 

  The name of your AWS Lambda function

  The Node.js runtime used to run the code

  The function timeout (in seconds)

  The maximum amount of memory that your function can use

  The deployment version

  Your API’s base URL

If you open this route link again from your browser (which should look similar to https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/chapter2/pizzas), you should see the array of all pizza objects from your data/pizza.js file.

 
         When        you       open      the       other     route     link      (something         similar   to
 

https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/chapter2/pizzas/1), you should see only the first pizza. This response should look something like this:

 
 {"id":1,"name":"Capricciosa","ingredients":
     ["tomato sauce","mozzarella","mushrooms","ham","olives"]}
 

To test whether your API’s working as expected, you should also try to get a pizza that doesn’t exist. Visit your API URL with a nonexistent pizza ID, such as this one: https://g8fhlgccof.execute-api.eu-central-1.amazonaws.com/chapter2/pizzas/42. In this case, the response should look similar to

 
 {"errorMessage" : "The pizza you requested wasn't found"}
 

Congratulations; your Pizza API’s now capable of showing a list of pizzas to Aunt Maria’s customers! This will make your Aunt Maria happy, but your API’s not done, yet. You need to implement the core feature of your API: creating a pizza order. But that’s where we’ll stop for now.


If you want to learn more about the book, check it out on liveBook here and see this slide deck.