Create a REST API integrated with Amazon DynamoDB using AWS Amplify and Vue
Tags: aws, dynamodb, amplify, vue
Published: 2020-09-28 21:38:56

![Image for post](https://miro.medium.com/max/2048/1*jHdpWisXsTmkE2JY3BFAxQ.png)

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](https://twitter.com/intent/user?screen_name=gerardsans).

\> Final solution in [GitHub](https://github.com/gsans/amplify-rest-todos-ui-vue).

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](https://dev.to/aws/build-your-first-full-stack-serverless-app-with-vue-and-aws-amplify-14k4)”. 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](https://docs.amplify.aws/cli/restapi/testing) 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**](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html). HTTP proxy integrations forward all requests and responses directly through to your **HTTP endpoint**. Eg: `/todos`.

![Image for post](https://miro.medium.com/max/2104/1*LzW0P96lcw0ipzfprH_WEw.png)

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
```

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
```

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
```

*   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**](https://github.com/awslabs/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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) and operations match route definitions in **AWS Serverless Express** and [**DynamoDB Document Client**](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/dynamodb-example-document-client.html).

![Image for post](https://miro.medium.com/max/2728/1*uBlBjgAr0PlvXzSgQjzRIw.png)

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
```

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

```javascript
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()  
});
```

This is the same code from the default template adding the [**uuid**](https://github.com/uuidjs/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](https://docs.amplify.aws/cli/teams/overview) and enabling [CORS headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).

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 use`API.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`.

```javascript
// 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);  
})
```

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**](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html) 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.

```javascript
// 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) })  
    }  
  });  
});
```

This returns the todos array as part of the `body`. Don’t forget to use `JSON.stringify`as 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.

```javascript
// 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);  
})
```

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](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes?#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`.

```javascript
// 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) })  
    }  
  });  
});
```

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`.

```javascript
// 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);  
})
```

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.body`contains the description for the new todo: `text`. Generate a new `id`, set `complete` to `false`, add`createdAt`and `updatedAt` timestamps and include the `userId`. To create the todo use `dynamodb.put`.

```javascript
// 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) })  
    }  
  });  
});
```

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

```javascript
// 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"; 
  }  
}
```

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.

```javascript
// 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);  
})
```

The update in **todosLambda** uses most of the code you are already familiar but including a new [update expression](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.NodeJs.03.html). 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`.

```javascript
// 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) })  
    }  
  });  
});
```

> 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](https://developer.mozilla.org/en-US/docs/Learn/Server-side/Express_Nodejs/routes?#Route_parameters). If successful, the result `body` will be `{}` with `statusCode:200`.

```javascript
// 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);  
})
```

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

```javascript
// 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) })  
    }  
  });  
});
```

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
```

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)
```

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
```

Next visit the [**AWS Amplify Console**](https://aws.amazon.com/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](https://miro.medium.com/max/2048/0*0nNOE1I-LmwdwNQw.png)

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
```

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.

{% youtube hXL5z1TaqTk %}

Thanks for reading!
===================

Have you got any questions about this tutorial or **AWS Amplify**? Feel free to reach me anytime at [@gerardsans](https://twitter.com/intent/user?screen_name=gerardsans).

![Image for post](https://miro.medium.com/max/2160/1*g3JXgwtV9b5B3lePTUG3ng.jpeg)

> _My Name is_ [_Gerard Sans_](https://twitter.com/gerardsans)_. I am a Developer Advocate at_ [_AWS Mobile_](https://aws.amazon.com/products/mobile) _working with_ [_AWS Amplify_](https://aws-amplify.github.io/) _and_ [_AWS AppSync_](https://aws.amazon.com/appsync/) _teams._