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

Setting up a new project with the Vue CLI

Creating a REST API with Amplify CLI

Architecture diagram for the Todo App

Creating a new REST API

amplify add api
/                        
|_ /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
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

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

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

Implementing CRUD operations with Amazon DynamoDB

REST API mapping between HTTP requests, AWS Serverless Express and Document Client calls.
cd /amplify/backend/function/todosLambda/src
npm i --save uuid
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()
});
// 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);
})

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 todosapp.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) })
}
});
});
// client request: fetching a todo by idimport { API } from 'aws-amplify';API.get('todosApi', `/todos/${id}`, {}).then((result) => {
this.todo = JSON.parse(result.body);
}).catch(err => {
console.log(err);
})
// todosLambda route handler: fetching a todo by idapp.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) })
}
});
});

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.

// client request: creating a new todoimport { 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);
})
// 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) })
}
});
});
// todosLambda route handler: helper functionconst 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";
}
}
// client request: updating a todoimport { 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);
})
// todosLambda route handler: updating a todoapp.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.

// client request: deleting a todo by idimport { API } from 'aws-amplify';API.del('todosApi', `/todos/${id}`, {}).then(result => {
console.log(result);
}).catch(err => {
console.log(err);
})
// todosLambda route handler: deleting a todo by idapp.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) })
}
});
});

Pushing your new REST API to the cloud

amplify push

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

REST
|_ /todos (path)
|_ todosApi (Amazon API Gateway)
|_ todosLambda (AWS Lambda)
|_ Logs (Amazon CloudWatch)

Publishing your app via the AWS Amplify Console

git init
git remote add origin repo@repoofyourchoice.com:username/project-name.git
git add .git commit -m 'initial commit'git push origin master
AWS Amplify Console deployment steps.

Cleaning up cloud services

amplify delete

Conclusion

Thanks for reading!

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

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store