How to build Serverless Jamstack apps with Aws, Vue,Nuxt and Netlify. Part 1

How to build Serverless Jamstack apps with Aws, Vue,Nuxt and Netlify. Part 1

Building the api

Foodie is a fictional restaurant that serves a variety of dishes and is pretty hot in their city. They wish to push their business further, by taking it online. So they need an online service, in other to give their clients the ability to place an order from anywhere within the city they operate. They've contracted 2 engineers to make this happen. You and Miss Melissa. Miss Melissa is in charge of the frontend. So she'll give users the ability to place orders, and these orders would be saved in a table in a database(A NoSql DB like Dynamo preferably). Your mission is to build an admin dashboard to display the list of saved orders, so that the chef( also happens to be the Admin) knows what dishes to serve, what dishes are ordered frequently, and in what seasons. How much is made a day? and so on .

So this app involves 2 parts.

  1. Creating a serverless API with aws using lambda, DynamoDb, and API Gateway. We'll use this API to serve data to our JamStack app.
  2. Creating a Web application with Vue, which would interact with our serverless API.

Prerequisite

I've already written a beginner's guide to building serverless APIs, which I recommend you go through in other to be comfortable with what we are about to build now. You'll have to install node, AWS CLI and serverless framework.
Serverless Api design with AWS lambda, dynamo DB and API Gateway

Creating our serverless api.

I want to create an api, with 2 endpoints.

  1. Loading dummy order items into our table(Orders)
  2. Getting all order items from our table

I'll be using the serverless framework to facilitate building this API.
From the command line, type this in.

serverless

Take a look at my command line print and fill yours in accordingly. I'll be using python as the runtime language. You'll be asked to create an AWS IAM profile, and set up an access key Id and Access Secret.

RosiusNsMacBook:Documents mac$ serverless

Serverless: No project detected. Do you want to create a new one? Yes
Serverless: What do you want to make? AWS Python
Serverless: What do you want to call this project? serverless-order-api

The project successfully created in the  'serverless-order-api' folder.

You can monitor, troubleshoot, and test your new service with a free Serverless account.

Serverless: Would you like to enable this? No
You can run the “serverless” command again if you change your mind later.


No AWS credentials were found on your computer, you need these to host your application.

Serverless: Do you want to set them up now? Yes
Serverless: Do you have an AWS account? Yes

If your browser does not open automatically, please open the URL: https://console.aws.amazon.com/iam/home?region=us-east-1#/users$new?step=final&accessKey&userNames=serverless&permissionType=policies&policies=arn:aws:iam::aws:policy%2FAdministratorAccess

Serverless: Press Enter to continue after creating an AWS user with access keys 
Serverless: AWS Access Key Id: AKXXXXXXXXXXXXXXXXXX
Serverless: AWS Secret Access Key: P7XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

AWS credentials saved on your machine at ~/.aws/credentials. Go there to change them at any time.

Serverless: Would you like to set up a command line <tab> completion? No

Once you've successfully created the serverless app, please open it up with your favorite IDE. I'll be using vscode.
Type this out in your serverless.yml file.

# Welcome to Serverless!
#
# This file is the main config file for your service.
# It's very minimal at this point and uses default values.
# You can always add more config options for more control.
# We've included some commented-out config examples here.
# Just uncomment any of them to get that config option.
#
# For full config options, check the docs:
#    docs.serverless.com
#
# Happy Coding!

service: serverless-order-api
# app and org for use with dashboard.serverless.com
app: order-api
#org: your-org-name

# You can pin your service to only deploy with a specific Serverless version
# Check out our docs for more details
frameworkVersion: '2'

provider:
  name: aws
  runtime: python3.8
  region: us-east-2


  lambdaHashingVersion: 20201221

# you can overwrite defaults here
  stage: dev
#  region: us-east-1

# you can add statements to the Lambda function's IAM Role here
  environment:
    DYNAMODB_TABLE: orders
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:Query
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:PutItem
        - dynamodb:BatchWriteItem
      Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"




functions:
  load:
    handler: orders/load_orders_data.load
    events:
      - http:
          path: orders
          method: post
          cors: true  
  list:
    handler: orders/list.list
    events:
      - http:
          path: orders
          method: get
          cors: true


resources:
  Resources:
    OrdersDynamoDbTable:
      Type: 'AWS::DynamoDB::Table'
      DeletionPolicy: Retain
      Properties:
        AttributeDefinitions:
          -
            AttributeName: order_no
            AttributeType: S
        KeySchema:
          -
            AttributeName: order_no
            KeyType: HASH
        ProvisionedThroughput:
          ReadCapacityUnits: 1
          WriteCapacityUnits: 1
        TableName: ${self:provider.environment.DYNAMODB_TABLE}

Our DynamoDB table is called orders, since I'll be saving order items in it. I've given the right permissions to the user under iamRoleStatements.
We have 2 functions.

  1. Load function responsible for populating the orders table with dummy order data.
  2. List function responsible for getting all order items from the orders table and displaying them in JSON format. Then the cloud formations template, with a primary key set to order_no.
    .

Load Function

Here's how a single order item looks like.

{

                "order_no": "#6753",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }

                ]

            }

order_no is the primary key attribute.
The objective now is to load at least 10 order items into our orders table.
If you are loading a lot of data at a time, you can make use of DynamoDB.Table.batch_writer() in other to speed up the process and reduce the number of write requests made to the service.

This method returns a handle to a batch writer object that will automatically handle buffering and sending items in batches. In addition, the batch writer will also automatically handle any unprocessed items and resend them as needed. All you need to do is call put_item for any items you want to add, and delete_item for any items you want to delete.
Here's how my load function looks like. Please grab the complete code from the github url.

from decimal import Decimal
import json
import boto3
import os

dynamodb = boto3.resource('dynamodb')
def load(event, context):

    table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

    with table.batch_writer() as batch:


        batch.put_item(
            Item={

                "order_no": "#6757",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )

        batch.put_item(

            Item={

                "order_no": "#6758",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":5
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal4+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )
        batch.put_item(
            Item={
                "order_no": "#6759",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )
        batch.put_item(
            Item={
                "order_no": "#6760",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )
        batch.put_item(

            Item={
                "order_no": "#6761",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )
        batch.put_item(
            Item={
                "order_no": "#6762",
                "order_date":"12 jan 2021, 08:28pm",
                "ordered_by":"https://rosius.s3.us-east-2.amazonaws.com/cropped_rosius.png",
                "total":"$26.40",
                "items":[
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal1+(1).jpg",
                "name":"Vegetable Mixups",
                "desc":"Vegetable Fritters with Egg",
                "price":"$10.20",
                "qty":3
                },
                {
                "pic":"https://rosius.s3.us-east-2.amazonaws.com/meal2+(1).jpeg",
                "name":"Chicken Mixed Salad",
                "desc":"Roasted Chicken, mixed with salad",
                "price":"$16.20",
                "qty":2
                }
                ]
            }
        )


    # create a response
    response = {
        "statusCode": 200,
        'headers': {
           'Access-Control-Allow-Origin': '*',
           'Access-Control-Allow-Credentials': True


        },
        "body": json.dumps({"successful":"Items successfully loaded into database"})
    }

    return response

List Function

We fetch all order items from the order table using the scan function.

import json
import os
from orders import decimalencoder

import boto3
dynamodb = boto3.resource('dynamodb')


def list(event, context):
    table = dynamodb.Table(os.environ['DYNAMODB_TABLE'])

    # fetch all orders from the database
    result = table.scan()

    # create a response
    response = {
        "statusCode": 200,
        'headers': {
           'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Credentials': True


        },
        "body": json.dumps(result['Items'], cls=decimalencoder.DecimalEncoder)
    }

    return response

And that's about it. The complete code is available on my Github repo. Don't forget to change the value of the region in the serverless.yml file to yours. Once that's done, deploy the API .

Deploying

serverless deploy

If everything goes well, 2 API endpoints would be generated. One to load data and the other to get the data. I'll be using Postman to test the endpoints. Here's a screenshot of my lambda functions and dynamo DB

Screen Shot 2021-03-31 at 21.41.03.png

Screen Shot 2021-03-31 at 21.46.35.png

Postman load endpoint test

Screen Shot 2021-03-31 at 22.06.34.png

Postman List endpoint test

Screen Shot 2021-03-31 at 21.55.04.png

Security

Our endpoints work just fine, but now, we need to secure them. Log into your AWS console, type API gateway in the search bar, and click on it.
See the photo below.

Screen Shot 2021-04-02 at 12.19.55.png In the next screen, select the API you want to secure. Mine is dev-serverless-order-api

Screen Shot 2021-04-02 at 12.22.14.png. On the next screen, select Usage plan at the bottom left-hand corner, click the Create button and fill in the required fields and click next

Screen Shot 2021-04-02 at 12.25.02.png On the next screen, select your API(dev-serverless-order-api) select the stage and confirm, then click on next.

Screen Shot 2021-04-02 at 12.26.11.png On the next screen Usage plan API keys click on Create API key and add to usage plan button, fill required fields and save. Then click Done.

Screen Shot 2021-04-02 at 12.27.10.png

Screen Shot 2021-04-02 at 12.27.41.png

Clicking the done button takes you back to the initial screen. You can go to the API Keys tab on the left-hand side of the screen and see your API Key there. You can click on the key, and then “show” where it says API Key, and there it is! Your generated API key.

Screen Shot 2021-04-02 at 12.48.21.png

Finally, select the resource menu of your API on the left-hand side of the screen, click on /orders, click on GET method, and select Method Request.

Screen Shot 2021-04-02 at 12.51.34.png Set API Key Required to true. Do the same for the POST method.

Screen Shot 2021-04-02 at 12.51.46.png

Now, Please Deploy your app again for all the changes to take effect. Let's test in postman. Let's try to get data without adding the api-key. We get the status code 403. Forbidden. That's nice

Screen Shot 2021-04-02 at 17.00.00.png

When we add the api-key to our header. x-api-key: "API-KEY". It gets our data.

Screen Shot 2021-04-02 at 17.00.57.png Now, our API key is a lot secure.
Let's continue in part 2,where we would be building a JAMSTACK application with it.

Part 2

phatrabbitapps.com/how-to-build-serverless-..

Thanks a lot for getting this far with me.
Hope you loved it, please leave a like
Let me know in the comments below, if you have any questions or if I made a mistake somewhere. I'll be more than glad to respond.
Checkout my other article, if you've got some more time.
Demystifying DynamoDb
Happy Coding ❤️