How to Create a CRUD API Using Express.js and AWS DynamoDB

How to Create a CRUD API Using Express.js and AWS DynamoDB

One of the most interesting fully managed AWS services that i have worked with is DynamoDB. It is a highly scalable key-value and document database that has milli-second performance that makes it one of the best storage solutions to store application data. By "fully managed", it means AWS handles the infrastructure and software updates. All you have to do is access the service and use it.

That being said, this makes DynamoDB a perfect use case for storing data for internet-scale web applications that are performance conscious. In this article I'm going to illustrate how to use DynamoDB with your Nodejs(Express.js) CRUD(Create, Read, Update, Delete) API.

I have listed the steps below of what we are going to cover:

  1. Create an IAM User with sufficient access rights to DynamoDB
  2. Create a DynamoDB table
  3. Create the Node.js functions to connect to the database and perform CRUD operations
  4. Incorporate the Node.js functions in an Express.js API
  5. Test our API in Postman

Before you start, please make sure you have an active AWS account to be able to use DynamoDB. You can create the account here.

1. Create an IAM User with sufficient access rights to DynamoDB

IAM is short for Identity and Access Management. It is an AWS service used for managing access and authentication to other AWS services. In order to access DynamoDB, go to the IAM dashboard and click on "Users" and then the "Add User" button to create a new user.

iam-users-list.PNG

Enter your desired name for the new user. Make sure to give the user sdk access by ticking the box where it says Programmatic Access and click Next: Permissions.

setup-user.PNG

There are three ways to set permissions for an IAM user. Namely- Adding user to a group with defined permissions for a job function, copying permissions from another existing user and attaching an existing policy. Using groups is considered best-practice as it becomes easy to manage and audit users and permissions. This is way we are going to use in this tutorial. As seen on the image below, click create group and make sure you highlighted Add user to group button and click create group.

groups.PNG

A popup will show up that will be asking you to enter your desired group name and choosing the policy you want to attach. Search for AmazonDynamoDBFullAccess as shown below.

acess.PNG

You will be asked to add tags(simple labels consisting of a customer-defined key and an optional value that can make it easier to manage, search for, and filter resources.) and review all the details we have been setting up. You will then be presented with access credentials, which we will use to access the DynamoDB. Make sure to download the csv file with details and keep them secure. Please note that AWS will only show you these credentials once.

credentials.PNG

2. Create a DynamoDB table

Search for DynamoDB in the management console and a page like this should show up. Click Create Table.

dynamodb.PNG

Enter your desired table name and make sure you use proper naming conventions that suite your requirements. Specify the name of your partition key and your desired data type and create table.

dbcreation.PNG

3. Node.js Functions to Connect to DynamoDB and perform CRUD operations

Now lets get to the code. Open your favorite text editor of choice(I will be using VS Code) and create a folder for our project. Install the required npm packages for our API by running the below command in your terminal.

npm install aws-sdk express dotenv

Create a .env file for storing your access credentials(That's what the dotenv package is for). Never store credentials in your code. I will be deleting these credentials as soon as i publish this article.

AWS_ACCESS_KEY_ID=AKIAYMJEQQER3J4C3W2U
AWS_SECRET_ACCESS_KEY=kAeXqQcbqenkCoAJmfvunaSeB9NudpJKuOaZgzYU
AWS_DEFAULT_REGION=us-east-1

Create a new file and name it dynamodb.js. Write the code below. This is for importing our packages we will be using in this file i.e. the aws-sdk and dotenv.

const AWS = require('aws-sdk')
require('dotenv').config()

Now lets call the AWS config function so that we can pass our environment variables(The ones we store on the .env file).

AWS.config.update({
    region: process.env.AWS_DEFAULT_REGION,
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})

Initialize a new DynamoDB client by including this line in your file.

const dynamoClient = new AWS.DynamoDB.DocumentClient()

Create a const for storing your table name where data will be store and retrieved from.

const TABLE_MEMBERS = "crud-api-members"

The above code can now allow us access to the DynamoDB table. Now lets take a look at the actual functions that will help us perform CRUD operations. I have commented each function to summarize what it does.

// this function is for adding/updating an entry/member in the table
// make sure you add the item being added and the table name in the params const
const addMember = async (member) => {
    const params = {
        TableName: TABLE_MEMBERS,
        Item: member
    }

    return await dynamoClient.put(params).promise()
}

// this function is retrieving a table entry by its id.
// make sure you include the id key in the params const
const getMemberById = async (id) => {
    const params = {
        TableName: TABLE_MEMBERS,
        Key: {
            id
        }
    }
    return await dynamoClient.get(params).promise()
}

// this function is retrieving all entries in the table
const getMembers = async () => {
    const params = {
        TableName: TABLE_MEMBERS
    }

    const members = await dynamoClient.scan(params).promise()
    console.log(members)
    return members
}

// this function is deleting a table entry by its id.
// make sure you include the id key in the params const
const deleteMember = async (id) => {
    const  params = {
        TableName: TABLE_MEMBERS,
        Key: {
            id
        }
    }

    return await dynamoClient.delete(params).promise()
}

// sample table entry
const member = 
    {
        id: "6",
        Name: 'Rainn',
        Surname: 'Scott',
        Gender: 'Male',
        Age: 24
    }

//export our functions to be used for our api
module.exports = {
    dynamoClient,
    getMembers,
    addMember,
    getMemberById,
    deleteMember
}

4. Incorporate the Node.js functions in an Express.js API

Now that we have created our functions, lets proceed with creating our api. The code below will be in our index.js file.

// indexjs file for our express api
const express = require('express')
const path = require('path')
const app = express()
const port = 3000

// for us to be able to post json data to our api
app.use(express.json())

/*
    our api endpoints will be accessed on the route
    /api/members/ on the members.js file
*/
app.use('/api/members', require('./routes/api/members'))

// listening port
app.listen(port, () => {
    console.log(`Backend Listening at Port ${port}`)
})

Our API endpoints will all be in one file members.js

// import express
const express = require('express')

/*      import all our functions from the dynamodb.js
        that perform crud operations
*/
const {getMembers, addMember, deleteMember, getMemberById} = require('../../dynamodb')

// the router function for creating routes
const router = express.Router();

// for us to be able to post json data to our api
router.use(express.json())

// api endpoint for retrieving all table entries
router.get('/', async (req, res) => {
        try {
                const members = await getMembers()
                res.json(members)
        } catch (error) {
                console.error(err)
                res.status(500).json({err: `Something went wrong`})
        }
})

// api endpoint for adding a new entry in the table
router.post('/add', async (req, res) => {
        const member = req.body
        try {
                const newMember = await addMember(member)
                res.json(newMember)
        } catch (error) {
                console.error(err)
                res.status(500).json({err: `Something went wrong`})
        }
})

// api endpoint for updating an existing entry in the table
router.put('/update/:id', async (req, res) => {
        const member = req.body
        const { id } = req.params
        member.id = id
        try {
                const updatedMember = await addMember(member)
                res.json(updatedMember)
        } catch (error) {
                console.error(err)
                res.status(500).json({err: `Something went wrong`})
        }
})

// api endpoint for deleting an entry in the table
router.delete('/delete/:id', async (req, res) => {
        const { id } = req.params
        try {
                const deletedMember = await deleteMember(id)
                res.json(deletedMember)
        } catch (error) {
                console.error(err)
                res.status(500).json({err: `Something went wrong`})
        }
})

// api endpoint for retrieving a table entry by id
router.get('/:id', async (req, res) => {
        const id = req.params.id
        try {
                const members = await getMemberById(id)
                res.json(members)
        } catch (error) {
                console.error(err)
                res.status(500).json({err: `Something went wrong`})
        }
})

module.exports = router

5. Test our API in Postman

Our API is now complete! Its now time to test one endpoint and see if its actually working. Lets try to add a new entry to our dynamo table. I will be using Postman, an application for testing API endpoints.

postman.PNG

As seen from the image above, our post request was successful(We got a status of 200 OK) Lets check for the JSON data we just sent via postman in our DynamoDB table.

results.PNG

Congratulations!! We have successfully created a working CRUD API that interacts with DynamoDB. As you will observe when you eventually try this on your own, DynamoDB is extremely fast, easy to use and this makes it a top choice for building internet applications. If you would like to check the whole project, kindly check it on this GitHub repository .

If you have any questions or suggestions, please leave a comment below and i will do my best to respond as timely as possible. I respond fast on twitter . Otherwise see you on my next post :).