How to Build a RESTful API with a Serverless Framework on AWS Lambda

I’ll take you through the entire process of implementing RESTful API service on Serverless Framework using AWS Lambda, a serverless compute service and you better believe it’s going to be awesome.

If you are going to use Serverless functionality offered by Azure Functions, and Google Cloud Functions, be sure to check the documentation.

If you don’t know about the serverless computing then I strongly recommended watching the following video before getting started.


Build RESTful API with a Serverless Framework

With a "Serverless Framework", we can quickly build, configure and deploy resources within few commands. We can store our code and configuration into a centralized repository so we can design proper workflow and developers can later write, reuse and refer other developers codebase.

Let's walk the walk together and to build a Pokemon RESTful API services with a "Serverless Framework". Please check out the table below for reference.

#ENDPOINTMETHODDESCRIPTION
1pokemon/GETGet a list of all pokemon from the database
2pokemon/{id}GETGet a specific pokemon.
3pokemon/POSTAdd new pokemon to the database.
4pokemon/{id}PUTUpdate existing pokemon.
5pokemon/{id}DELETEDelete existing pokemon.

You can find the code for this article here: https://github.com/sagar-gavhane/pokemon-app

Prerequisites

We need to install the following tools and frameworks:

  1. Node.js 8.10 or above
  2. MySQL
  3. Visual Studio Code (preferred) or any code editor
  4. Postman

Note: This guide is simply to give you an idea of how to build an API and should not be mistaken as a guide for creating Productive API and we strongly suggest that you write your own functions for efficient serverless autoscaling.  


Serverless Setup

We have to install Serverless globally, so fire up a terminal window and run:

npm install -g serverless

Note: You may need to run the command as sudo.

Now, let's install plugins and libraries step by step.


Install Dependencies

Install the following packages to work with "Serverless Framework"

  • express - Fast, unopinionated, minimalist web framework for Node.js.
  • body-parser - Parse incoming request bodies in a middleware before your handlers, available under the req.body property.
  • mysql - A pure node.js JavaScript Client implementing the MySql protocol.
  • serverless-http - Plugin allows you to wrap express API for serverless use.
  • serverless-offline - Plugin to emulate AWS Lambda and API Gateway for speed up local development.
npm install express body-parser mysql serverless-http --save # app dependancies
npm install serverless-offline --save-dev # development dependancies

App structure

Before we start writing the handler code, we’re going to structure the project folder and configure our tools.

Create the following structure at the root level:

/pokemon-app/
|--/configs
|----/dbConfig.js
|--/node_modules
|--.gitignore
|--index.js
|--package.json
|--serverless.yml

Make sure to list private files into a file.gitignore so that we don’t accidentally commit it to the public repository. Copy paste raw material from https://www.gitignore.io/api/node to file.gitignore.

serverless.yml file serves as a manifest for our RESTful API service. Where we define our functions, events, and necessary resources. Later, with serverless CLI we configure and deploy our service to AWS infrastructure.

# serverless.yml
service: pokemon-service

provider:
  name: aws
  runtime: nodejs8.10
  stage: dev
  region: us-east-1
  memorySize: 512

functions:
  pokemonFunc:
    handler: index.handler
    events:
      - http:
          path: pokemon
          method: get
      - http:
          path: pokemon/{id}
          method: get
      - http:
          path: pokemon
          method: post
      - http:
          path: pokemon/{id}
          method: put
      - http:
          path: pokemon/{id}
          method: delete

plugins:
  - serverless-offline

We are doing a few things here:

  1. servicepokemon-service is the name of the service. You can give any type name for your service.
  2. provider: This is where we specify the name of the provider we are using (AWS as cloud service provider) and configurations specific to it. In our case, we’ve configured the runtime (Node.js) with 8.10 version and region toous-east-1.
  3. functions: We specify the functions provided by our service, Here I'm specifying aspokemonFunc function name with eventshttp. We can also say that this is our AWS Lambda function.

We have to store our pokemon somewhere, for sake of simplicity I'm chosen MySQL but you can also use another type database. I have already created a database with name pokemon_db and inside a database created table pokemon_tb with id, name, height, weight, avatar, and createAt columns.

CREATE TABLE `pokemon_tb` (
  `id` int(11) NOT NULL,
  `name` varchar(255) NOT NULL,
  `height` float NOT NULL,
  `weight` float NOT NULL,
  `avatar` varchar(255) NOT NULL,
  `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `pokemon_tb` ADD PRIMARY KEY (`id`);

ALTER TABLE `pokemon_tb` MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;

Rather than creating and managing connections every time, we configure pool connections once inside a filedbConfig.js and reused it multiple times.

// dbConfig.js
const mysql = require('mysql')
const pool  = mysql.createPool({
  host            : 'localhost',
  user            : 'root',
  password        : '12345',
  database        : 'pokemon_app_db',
})

module.exports = pool

Writing the handler function

Let's focus on handling RESTful API route inside the index.js file with express. First, we imported the packageserverless-http at the top. Second, we exported a handler function which is our application wrapped in the serverless package.

Here, we're implementing basic five routes for handling operationcrud with pokemon (without any validation).

const express = require('express')
const serverless = require('serverless-http')
const bodyParser = require('body-parser')
const pool = require('./configs/dbConfig')

const app = express()

app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

// Handle pokemon GET route for all pokemon
app.get('/pokemon/', (req, res) => {
  const query = 'SELECT * FROM pokemon_tb'
  pool.query(query, (err, results, fields) => {
    if (err) {
      const response = { data: null, message: err.message, }
      res.send(response)
    }

    const pokemons = [...results]
    const response = {
      data: pokemons,
      message: 'All pokemons successfully retrieved.',
    }
    res.send(response)
  })
})

// Handle pokemon GET route for specific pokemon
app.get('/pokemon/:id', (req, res) => {
  const id = req.params.id
  const query = `SELECT * FROM pokemon_tb WHERE id=${id}`
  pool.query(query, (err, results, fields) => {
    if (err) {
      const response = { data: null, message: err.message, }
      res.send(response)
    }

    const pokemon = results[0]
    const response = {
      data: pokemon,
      message: `Pokemon ${pokemon.name} successfully retrieved.`,
    }
    res.status(200).send(response)
  })
})

// Handle pokemon POST route
app.post('/pokemon/', (req, res) => {
  const { name, height, weight, avatar } = req.body

  const query = `INSERT INTO pokemon_tb (name, height, weight, avatar) VALUES ('${name}', '${height}', '${weight}', '${avatar}')`
  pool.query(query, (err, results, fields) => {
    if (err) {
      const response = { data: null, message: err.message, }
      res.send(response)
    }

    const { insertId } = results
    const pokemon = { id: insertId, name, height, weight, avatar }
    const response = {
      data: pokemon,
      message: `Pokemon ${name} successfully added.`,
    }
    res.status(201).send(response)
  })
})

// Handle pokemon PUT route
app.put('/pokemon/:id', (req, res) => {
  const { id } = req.params
  const query = `SELECT * FROM pokemon_tb WHERE id=${id} LIMIT 1`
  pool.query(query, (err, results, fields) => {
    if (err) {
      const response = { data: null, message: err.message, }
      res.send(response)
    }

    const { id, name, height, weight, avatar } = { ...results[0], ...req.body }
    const query = `UPDATE pokemon_tb SET name='${name}', height='${height}', weight='${weight}', avatar='${avatar}' WHERE id='${id}'`
    pool.query(query, (err, results, fields) => {
      if (err) {
        const response = { data: null, message: err.message, }
        res.send(response)
      }

      const pokemon = {
        id,
        name,
        height,
        weight,
        avatar,
      }
      const response = {
        data: pokemon,
        message: `Pokemon ${name} is successfully updated.`,
      }
      res.send(response)
    })
  })
})

// Handler pokemon DELETE route
app.delete('/pokemon/:id', (req, res) => {
  const { id } = req.params
  const query = `DELETE FROM pokemon_tb WHERE id=${id}`
  pool.query(query, (err, results, fields) => {
    if (err) {
      const response = { data: null, message: err.message }
      res.send(response)
    }

    const response = {
      data: null,
      message: `Pokemon with id: ${id} successfully deleted.`,
    }
    res.send(response)
  })
})

// Handle in-valid route
app.all('*', function(req, res) {
  const response = { data: null, message: 'Route not found!!' }
  res.status(400).send(response)
})

// wrap express app instance with serverless http function
module.exports.handler = serverless(app)

Terminal snapshot:

Terminal snapshot

 

Get all pokemon:

Get all pokemon

 

Get pokemon by id:

Get pokemon by id

 

Add new pokemon:

Add new pokemon

 

Update existing pokemon:

Update existing pokemon

 

Delete existing pokemon:

Delete existing pokemon


MySQL

For simplicity I choose MySQL but you can use any NoSQL database.

Here's a simple tutorial provided by AWS that will help you to configure and Connect to Serverless MySQL Database


Deployment

Deploying services with the serverless framework is so simple, we require to just hit deploy command.

serverless deploy

Creating RESTful API with a serverless framework is pretty straightforward. For serverless, we have to switch our development workflow. I found that lots of companies are moving towards creating and managing micro-services architecture instead of the monolithic app.


That's it!
You've successfully created a RESTful API with a Serverless Framework.
You may also be interested in learning How to build a Serverless Website with AWS Lamda in 7 Easy Steps.
Got questions, Let us know in the comments below!

This post was originally published on dev.to by @sagar on 19 Oct 2018 and reposted with permitted edits.

happy learning . . .👇🏾

Learning How to Learn