DEV Community

gerard-sans for AWS

Posted on • Originally published at Medium

Create a REST API integrated with Amazon DynamoDB using AWS Amplify and Vue

Image for post

In this article you will create a REST API integrated with Amazon DynamoDB using AWS Amplify including CRUD operations and publication. Access to the REST API will allow both registered users and guests. In order to test it you will create a client including an authentication flow using Vue.

  • Setting up a new project with the Vue CLI
  • Creating a REST API with Amplify CLI
  • Creating a new REST API
  • Implementing CRUD operations using Amazon DynamoDB
  • Pushing your REST API to the cloud
  • Publishing your app via the AWS Amplify Console
  • Cleaning up cloud services

Please let me know if you have any questions or want to learn more at @gerardsans.

> Final solution in GitHub.

Setting up a new project with the Vue CLI

Before moving to the next section, please complete the steps described in “Build your first full-stack serverless app with Vue”. Here you will set up the initial project, familiarise with Amplify CLI and add an authorisation flow so users can register themselves via an automated verification code sent to their email and login.

Creating a REST API with Amplify CLI

The Amplify CLI provides a guided workflow to easily add, develop, test and manage REST APIs to access your AWS resources from your web and mobile applications.

A REST API or HTTP endpoint will be composed by one or more paths. Eg: /todos. Each path will use a Lambda function to handle HTTP requests and responses. Amplify CLI creates a single resource in Amazon API Gateway so you can handle all routes, HTTP Methods and paths, with a single Lambda function via a Lambda Proxy integration. HTTP proxy integrations forward all requests and responses directly through to your HTTP endpoint. Eg: /todos.

Image for post

Architecture diagram for the Todo App

Creating a new REST API

In this post, you are going to create a REST API to service a todo app with a single endpoint /todos using Amazon DynamoDB as a data source. To create it, use the following command:

amplify add api
Enter fullscreen mode Exit fullscreen mode

Answer the following questions

  • Please select from one of the below mentioned services: REST
  • Provide a friendly name for your resource to be used as a label for this category in the project: todosApi
  • Provide a path (e.g., /book/{isbn}): /todos

This will be the configuration for /todos path in Amazon API Gateway:

/                          
|\_ /todos        Main resource. Eg: /todos  
   ANY           Methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT  
   OPTIONS       Allow pre-flight requests in CORS by browser  
   |\_ /{proxy+}  Eg: /todos/, /todos/id  
      ANY        Methods: DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT  
      OPTIONS    Allow pre-flight requests in CORS by browser
Enter fullscreen mode Exit fullscreen mode

By default, Amplify CLI creates a greedy path variable /todos/{proxy+} that catches all child resources for a path and forwards them to your Lambda. This will match all child routes including /todos/id.

  • Choose a Lambda source Create a new Lambda function
  • Provide a friendly name for your resource to be used as a label for this category in the project: todosLambda
  • Provide the AWS Lambda function name: todosLambda
  • Choose the runtime that you want to use: NodeJS
  • Choose the function template that you want to use: CRUD function for Amazon DynamoDB

The Lambda function template, CRUD function for Amazon DynamoDB implements route handlers for GET, POST, PUT and DELETE Http Methods and paths for /todos and /todos/*. Some possible routes examples include:

GET /todos         List all todos  
GET /todos/1       Load a todo by id  
POST /todos        Create a todo  
PUT /todos         Update a todo  
DELETE /todos/1    Delete a todo by id
Enter fullscreen mode Exit fullscreen mode
  • Do you want to access other resources in this project from your Lambda function? No
  • Do you want to invoke this function on a recurring schedule? No
  • Do you want to configure Lambda layers for this function? No
  • Do you want to edit the local lambda function now? Yes

We are going to change this template later but it’s good that you have it open as you follow the next steps.

  • Press enter to continue
  • Restrict API access Yes
  • Who should have access? Authenticated and Guest users
  • What kind of access do you want for Authenticated users? create, read, update, delete
  • What kind of access do you want for Guest users? read

Amplify CLI restricts API access combining Amazon Cognito for authentication and AWS IAM (Identity and Access Management) for granting execution permissions on routes.

  • Do you want to add another path? No

That’s all! Before we publish to the cloud though, let’s see how to change the default template to implement create, read, update and delete operations for your todo app.

Implementing CRUD operations with Amazon DynamoDB

In order to manage your todo app you want to implement all CRUD operations, these are: create, read, update and delete. The template we picked in the last section uses AWS Serverless Express. Amazon API Gateway will proxy incoming requests to your todosLambda.

For the implementation, you need to add route handlers to match all HTTP methods and paths you want to support. The first to match with an incoming request will be then executed. This will be in the same order as you have define them. If there’s no match an error will be returned.

See below, how different HTTP methods and operations match route definitions in AWS Serverless Express and DynamoDB Document Client.

Image for post

REST API mapping between HTTP requests, AWS Serverless Express and Document Client calls.

Before we start, you need to install a library to generate the ids for new todos. To install this dependency run the following commands from the root of your project:

cd /amplify/backend/function/todosLambda/src  
npm i --save uuid
Enter fullscreen mode Exit fullscreen mode

Open /amplify/backend/function/todosLambda/src/app.js and replace its content for:

const AWS = require('aws-sdk')
var awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')  
var bodyParser = require('body-parser')  
var express = require('express')  
const { v4: uuidv4 } = require('uuid')

AWS.config.update({ region: process.env.TABLE_REGION });  
const dynamodb = new AWS.DynamoDB.DocumentClient();
let tableName = "todosTable";  
if (process.env.ENV && process.env.ENV !== "NONE") {  
  tableName = tableName + '-' + process.env.ENV;  
}

var app = express()  
app.use(bodyParser.json())  
app.use(awsServerlessExpressMiddleware.eventContext())app.use(function (request, response, next) {  
  response.header("Access-Control-Allow-Origin", "\*")  
  response.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept")  
  next()  
});
Enter fullscreen mode Exit fullscreen mode

This is the same code from the default template adding the uuid library. This takes care of initialising DynamoDB Document Client API to interact with DynamoDB, the DynamoDB table name taking into account the current environment and enabling CORS headers.

Let’s handle the first route, we will use the HTTP Method and path by the title for reference as in the next section. For each operation, first we will include the code from the client; and then, followed by the code serving it in the todosLambda.

Fetching todos (GET, /todos)

In the client, we use the Amplify JavaScript library aws-amplify to consume the REST API. In order to fetch the todos useAPI.get with the todosApi followed by the path /todos and an empty payload. The result is an array with the todos as part of the body. Remember to use JSON.parse as the result.body needs to be in plain text due to HTTP transport. The result object also contains the statusCode and path as url.

// client request: fetching todos  
import { API } from 'aws-amplify';

API.get('todosApi', '/todos', {}).then(result => {  
 this.todos = JSON.parse(result.body);  
}).catch(err => {  
 console.log(err);  
})
Enter fullscreen mode Exit fullscreen mode

Let’s look at the code in todosLambda. To fetch todos, map a handler function to the GET Method and the /todos path. Then run a dynamodb.scan operation. This fetches all records in the table specified. Limit the results to 100 items. Find more details in the Developer Guide from Amazon DynamoDB.

Scan returns all the data in a table so you may consider to change it for a query if you expect more than just few records.

// todosLambda route handler: fetching todos
app.get("/todos", function (request, response) {  
  let params = {  
    TableName: tableName,  
    limit: 100  
  }  
  dynamodb.scan(params, (error, result) => {  
    if (error) {  
      response.json({ statusCode: 500, error: error.message });  
    } else {  
      response.json({ statusCode: 200, url: request.url, body: JSON.stringify(result.Items) })  
    }  
  });  
});
Enter fullscreen mode Exit fullscreen mode

This returns the todos array as part of the body. Don’t forget to use JSON.stringifyas it needs to be in plain text due to HTTP transport.

Once the operation finishes, check for errors and create a JSON response accordingly. You will use the same approach in the rest of operations.

Fetching a todo by id (GET, /todos/:id)

Let’s quickly see the code necessary on the client below.

// client request: fetching a todo by id
import { API } from 'aws-amplify';

API.get('todosApi', `/todos/${id}`, {}).then((result) => {  
  this.todo = JSON.parse(result.body);  
}).catch(err => {  
  console.log(err);  
})
Enter fullscreen mode Exit fullscreen mode

Notice how to use API.get with a path using a template string generating the path. Eg: /todos/${id} becomes "/todos/c71f0be2–607d-48bc-b721–4cb77c90a58f". The result receives the todo details.

Let’s see how to get the details from a single todo in todosLambda. In order to capture the todo id use route parameters, a feature from AWS Serverless Express. Route parameters use a semicolon to capture values at specific position in the URL like in /users/**:userId**/books/**:bookId**. Values are then available in the request object as request.params.userId and request.params.bookId.

// todosLambda route handler: fetching a todo by id
app.get("/todos/:id", function (request, response) {  
  let params = {  
    TableName: tableName,  
    Key: {  
      id: request.params.id  
    }  
  }  
  dynamodb.get(params, (error, result) => {  
    if (error) {  
      response.json({ statusCode: 500, error: error.message });  
    } else {  
      response.json({ statusCode: 200, url: request.url, body: JSON.stringify(result.Item) })  
    }  
  });  
});
Enter fullscreen mode Exit fullscreen mode

As in the code above, use dynamodb.get to set your table and partition key Key.id from the request parameters.

For todosApi we only have a partition key, if you have a composed key (partition key + sort key) include the sort key too as part of the Key.sk.

Creating a new todo (POST, /todos)

In order to create a new todo, use API.post and set the payload to include the new todo description text within the request body.

// client request: creating a new todo
import { API } from 'aws-amplify';

API.post('todosApi', '/todos', {  
 body: {  
    text: "todo-1"  
  } 
}).then(result => {  
  this.todo = JSON.parse(result.body);  
}).catch(err => {  
  console.log(err);  
})
Enter fullscreen mode Exit fullscreen mode

In todosLambda, creating a new todo is a bit more elaborated as we need to provide some new attributes and values. As we saw in the code above, the request.bodycontains the description for the new todo: text. Generate a new id, set complete to false, addcreatedAtand updatedAt timestamps and include the userId. To create the todo use dynamodb.put.

// todosLambda route handler: creating a new todoapp.post("/todos", function (request, response) {  
  const timestamp = new Date().toISOString(); 
  let params = {  
    TableName: tableName,  
    Item: {  
      ...request.body,  
      id: uuidv4(),               // auto-generate id  
      complete: false,            // default for new todos  
      createdAt: timestamp,  
      updatedAt: timestamp,  
      userId: getUserId(request)  // userId from request  
    }  
  } 
  dynamodb.put(params, (error, result) => {  
    if (error) {  
      response.json({ statusCode: 500, error: error.message, url: request.url });  
    } else {  
      response.json({ statusCode: 200, url: request.url, body: JSON.stringify(params.Item) })  
    }  
  });  
});
Enter fullscreen mode Exit fullscreen mode

The helper function getUserId below extracts the user id from Amazon Cognito.

// todosLambda route handler: helper function
const getUserId = (request) => {  
  try {  
    const reqContext = request.apiGateway.event.requestContext;  
    const authProvider = reqContext.identity.cognitoAuthenticationProvider;  
    return authProvider ? authProvider.split(":CognitoSignIn:").pop() : "UNAUTH";  
  } catch (error) {  
    return "UNAUTH"; 
  }  
}
Enter fullscreen mode Exit fullscreen mode

This code tries to capture the user id from the request identity context and if unsuccessful, returns an unauthorised token with UNAUTH as the value.

Updating a todo (PUT, /todos)

At this point you should know how to use the Amplify API. In order to update a todo use API.put and the /todos path. As you did creating a new todo provide the todo changes including its id as part of the body. As in the code below, change both the description and the complete values. The result contains any fields that were changed and their new values.

// client request: updating a todo
import { API } from 'aws-amplify';

API.put('todosApi', `/todos`, {   
 body: {  
    id: id,  
    text: "todo-2",  
    complete: true  
  }  
}).then(result => {  
  this.todo = JSON.parse(result.body);  
}).catch(err => {  
  console.log(err);  
})
Enter fullscreen mode Exit fullscreen mode

The update in todosLambda uses most of the code you are already familiar but including a new update expression. This expression includes optional attributes so we need to create it dynamically. Eg: to service a request changing only the description text but not the other attributes. We used UPDATED_NEW to return only the updated attributes in DynamoDB but if you need all attributes use ALL_NEW.

// todosLambda route handler: updating a todo
app.put("/todos", function (request, response) {  
  const timestamp = new Date().toISOString(); 
  const params = {  
    TableName: tableName,  
    Key: {  
      id: request.body.id,
    },  
    ExpressionAttributeNames: { '#text': 'text' },  
    ExpressionAttributeValues: {},  
    ReturnValues: 'UPDATED_NEW',
  };  
  params.UpdateExpression = 'SET ';  
  if (request.body.text) {  
    params.ExpressionAttributeValues[':text'] = request.body.text;  
    params.UpdateExpression += '#text = :text, ';  
  }  
  if (request.body.complete) {  
    params.ExpressionAttributeValues[':complete'] = request.body.complete;  
    params.UpdateExpression += 'complete = :complete, ';  
  }  
  if (request.body.text || request.body.complete) {  
    params.ExpressionAttributeValues[':updatedAt'] = timestamp;  
    params.UpdateExpression += 'updatedAt = :updatedAt';  
  }  
  dynamodb.update(params, (error, result) => {  
    if (error) {  
      response.json({ statusCode: 500, error: error.message, url: request.url });  
    } else {  
      response.json({ statusCode: 200, url: request.url, body: JSON.stringify(result.Attributes) })  
    }  
  });  
});
Enter fullscreen mode Exit fullscreen mode

To keep the semantics of HTTP PUT use the todo id from request.body.id instead of the request.params.id.

Deleting a todo by id (DELETE, /todos/:id)

Let’s implement the request to delete a todo. On the client, use API.del with a path including the id as a route parameter. If successful, the result body will be {} with statusCode:200.

// client request: deleting a todo by id
import { API } from 'aws-amplify';

API.del('todosApi', `/todos/${id}`, {}).then(result => {  
  console.log(result);  
}).catch(err => {  
  console.log(err);  
})
Enter fullscreen mode Exit fullscreen mode

In todosLambda, use the same techniques from the previous route handlers now using dynamodb.delete operation.

// todosLambda route handler: deleting a todo by id
app.delete("/todos/:id", function (request, response) {  
  let params = {  
    TableName: tableName,  
    Key: {  
      id: request.params.id 
    }  
  }  
  dynamodb.delete(params, (error, result) => {  
    if (error) {  
      response.json({ statusCode: 500, error: error.message, url: request.url });  
    } else {  
      response.json({ statusCode: 200, url: request.url, body: JSON.stringify(result) })  
    }  
  });  
});
Enter fullscreen mode Exit fullscreen mode

This completes all the CRUD operations. Before we can test it we need to publish it.

Pushing your new REST API to the cloud

Let’s deploy the new REST API in the cloud by running this command in the root of your project:

amplify push
Enter fullscreen mode Exit fullscreen mode

At the end of this command you can take note of your new REST API url.

REST APIs follow this pattern https://{restapi-id}.execute-api.{region}.amazonaws.com/{environment}/{path}

Let’s see an overview of all the resources created by Amplify CLI.

REST  
 |\_ /todos (path)  
    |\_ todosApi (Amazon API Gateway)   
       |\_ todosLambda (AWS Lambda)  
          |\_ Logs (Amazon CloudWatch)
Enter fullscreen mode Exit fullscreen mode

We have covered all AWS Services but Amazon CloudWatch. This service will allow you to monitor usage and access to logs during development and testing.

Publishing your app via the AWS Amplify Console

The first thing you need to do is create a new repo for this project. Once you’ve created the repo, copy the URL for the project to the clipboard and initialise git in your local project:

git init  
git remote add origin [repo@repoofyourchoice.com](mailto:repo@repoofyourchoice.com):username/project-name.git  
git add .git commit -m 'initial commit'git push origin master
Enter fullscreen mode Exit fullscreen mode

Next visit the AWS Amplify Console in your AWS account. Click Get Started to create a new deployment. Next, authorise your repository provider as the repository service. Next, choose the new repository and branch for the project you just created and click Next. In the next screen, create a new role and use this role to allow the AWS Amplify Console to deploy these resources and click Next. Finally, click Save and Deploy to deploy your application!

Image for post

AWS Amplify Console deployment steps.

Cleaning up cloud services

If at any time, you would like to delete the services from your project and your AWS Account, you can do this by running:

amplify delete
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations! You successfully created a REST API using AWS Amplify, implemented all CRUD operations using Amazon DynamoDB and created a client to consume it restricting access to both registered users and guests using Vue. Thanks for following this tutorial.

If you prefer, you can also follow this video to achieve the same result.

Thanks for reading!

Have you got any questions about this tutorial or AWS Amplify? Feel free to reach me anytime at @gerardsans.

Image for post

My Name is Gerard Sans. I am a Developer Advocate at AWS Mobile working with AWS Amplify and AWS AppSync teams.

Top comments (0)