Skip to main content

Let's build a RESTful CRUD API using AWS serverless services (part 2)

Greetings!

In the previous post, I described how to create a serverless REST API using the serverless framework in which we discussed the project setup and the create operation. In this post, we will complete other operations of our API.

You can find the complete source code here.

Read Item(s)

As we created an item in our previous post, we had to check the value using the AWS console. Now it is time to access items via the REST endpoint.

Get all books/items

When we define APIs for fetching, we use the "get" HTTP method. As we select all the books, we use the DynamoDB scan command hence we need to allow access to it.

Get all handler definition

getBooks:
  handler: src/get-books.handler
  iamRoleStatements:
    - Effect: Allow     
      Action:
        - dynamodb:Scan        
      Resource: !GetAtt crudDynamoDBTable.Arn
  events:
    - http:
        path: /books
        method: get
        cors: true

Get all handler implementation

Create a new module under the src folder as get-books.js. This code is very simple.
"use strict";

import { ScanCommand } from "@aws-sdk/lib-dynamodb";

import { response } from "./utils.js";
import { ddbDocClient, CRUD_TABLE_NAME } from "./database.js";

export const handler = async (event) => {
  try {
    const params = {
      TableName: CRUD_TABLE_NAME
    };
    const data = await ddbDocClient.send(new ScanCommand(params));
    return response(200, data.Items);
  } catch (err) {
    console.log("Error", err);
    return response(500, { message: err.message });
  }
}

Get a single book/item

This is the read operation of our crud. As we use the get operation of the DynamoDB, we need to allow GetItem action to our Lambda function. Another notable difference is the use of path parameters. The notation is to use brackets as below.
/books/{id}

Get handler definition

getBook:
  handler: src/get-book.handler
  iamRoleStatements:
    - Effect: Allow     
      Action:
        - dynamodb:GetItem        
      Resource: !GetAtt crudDynamoDBTable.Arn
  events:
    - http:
        path: /books/{id}
        method: get
        cors: true

Get handler implementation

Create a new module under the src folder as get-book.js. We use GetCommand to retrieve a book. Another thing to learn here is how we can access path parameters.
event.pathParameters.id
"use strict";

import { GetCommand } from "@aws-sdk/lib-dynamodb";

import { response } from "./utils.js";
import { ddbDocClient, CRUD_TABLE_NAME } from "./database.js";

export const handler = async (event) => {
  try {
    const bookId = event.pathParameters.id;
    const params = {
      TableName: CRUD_TABLE_NAME,
      Key: {
        bookId,
      },
    };
    const data = await ddbDocClient.send(new GetCommand(params));
    return response(200, data.Item);
  } catch (err) {
    console.log("Error", err);
    return response(500, { message: err.message });
  }
};

Update an item

Once we create an item, we would like to update it as well. Defining the handler in serverless.yml is simple for all these operations. Things to note here is we are allowing UpdateItem action to the Lambda function as this is an update operation. Another thing to note is the HTTP method. We use the "put" HTTP method as this is an update operation.

Update handler definition

updateBook:
  handler: src/update-book.handler
  iamRoleStatements:
    - Effect: Allow     
      Action:
        - dynamodb:UpdateItem        
      Resource: !GetAtt crudDynamoDBTable.Arn
  events:
    - http:
        path: /books/{id}
        method: put
        cors: true

Update handler implementation

This is a little lengthy compared to other handlers. We need to define the update statement using "UpdateExpression" and explain it to the DynamoDB client using "Expression" properties. Create a new module under src as update-book.js
"use strict";

import { UpdateCommand } from "@aws-sdk/lib-dynamodb";

import { response } from "./utils.js";
import { ddbDocClient, CRUD_TABLE_NAME } from "./database.js";

export const handler = async (event) => {
  try {
    const bookId = event.pathParameters.id;
    const book = JSON.parse(event.body);
    const params = {
      TableName: CRUD_TABLE_NAME,
      Key: {
        bookId,
      },
      UpdateExpression: "set #title = :title, #author = :author",
      ExpressionAttributeNames: {
        "#title": "title",
        "#author": "author",
      },
      ExpressionAttributeValues: {
        ":title": book.title,
        ":author": book.author,
      },
      ConditionExpression: "attribute_exists(bookId)",
    };
    const data = await ddbDocClient.send(new UpdateCommand(params));
    return response(200, { message: `Book updated for ${bookId}` });
  } catch (err) {
    console.log("Error", err);
    return response(500, { message: err.message });
  }
};

Delete an item

And finally, our last operation is to delete an item. This is very similar to the read operation other that used the HTTP method and the DynamoDB operation.

Delete handler definition

deleteBook:
  handler: src/delete-book.handler
  iamRoleStatements:
    - Effect: Allow     
      Action:
        - dynamodb:DeleteItem        
      Resource: !GetAtt crudDynamoDBTable.Arn
  events:
    - http:
        path: /books/{id}
        method: delete
        cors: true

Delete handler implementation

Create a new module under src as delete-book.js and use the below code.
"use strict";

import { DeleteCommand } from "@aws-sdk/lib-dynamodb";

import { response } from "./utils.js";
import { ddbDocClient, CRUD_TABLE_NAME } from "./database.js";

export const handler = async (event) => {
  try {
    const bookId = event.pathParameters.id;
    const params = {
      TableName: CRUD_TABLE_NAME,
      Key: {
        bookId,
      },
    };
    const data = await ddbDocClient.send(new DeleteCommand(params));
    return response(204, { message: `Book is deleted for ${bookId}` });
  } catch (err) {
    console.log("Error", err);
    return response(500, { message: err.message });
  }
};

Clean up

Remember to delete your services. We do not have to do this manually as the serverless framework provides a single command.
serverless remove

Conclusion

This ends our basic RESTful CRUD API using AWS serverless services. We have learned how to use the serverless framework as well.

Happy learning ☺

References

https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.Scan.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.ReadItem.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.UpdateItem.html
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.DeleteItem.html
https://www.udemy.com/course/building-rest-apis-with-serverless/

Comments