In this tutorial we are going to integrate AWS' DynamoDB into a Next.JS application we have built in the previous tutorial.
Amazon DynamoDBis a fully managed NoSQL database service offered by Amazon Web Services (AWS)
- Serverless
- Java Runtime Engine (JRE) version 6.x or newer
Change directories to your development directory and clone Nextjs app on a Lambda function repo.
git clone https://github.com/teddynted/nextjs-on-a-lambda-function.git
cd nextjs-on-a-lambda-function
npm installFor the purpose of running our on localhost, we need to install serverless-dynamodb-local plugin.
brew cask install java
npm install --save-dev serverless-dynamodb-local
You also need to install
Java Runtime versiononly if it's not installed.
Open serverless.yml and add following entry to the plugins array
plugins:
- serverless-dynamodb-localInstall DynamoDB Local:
sls dynamodb installLet's configure DynamoDb and add a table called posts-dev in serverless.yml:
Add the following to serverless.yml, we are basically describing our posts table and defining postId attribute as our primary key. we get TableName value from the custom variable ${self:custom.postsTableName} in the next section.
BillingMode PAY_PER_REQUEST tells DynamoDB that we want to pay per request and use the On-Demand Capacity option.
resources:
Resources:
PostsDynamoDBTable:
Type: 'AWS::DynamoDB::Table'
Properties:
AttributeDefinitions:
-
AttributeName: postId
AttributeType: S
KeySchema:
-
AttributeName: postId
KeyType: HASH
BillingMode: PAY_PER_REQUEST
TableName: ${self:custom.postsTableName}Add the following custom: block to our serverless.yml, here we are defining a custom variable postsTableName that is used the above snippet and we also adding a basic configuration to our dynamodb.
table name should match your enviroment e.g
posts-dev,posts-uatetc.
custom:
postsTableName: 'posts-${self:provider.stage}'
dynamodb:
stages:
- dev
start:
migrate: true
port: ${env:DYNAMODB_PORT, '8000'}
seed: true
inMemory: true
convertEmptyValues: true
seed:
dev:
sources:
- table: ${self:custom.postsTableName}
sources: [seeds/posts.json]Add the iamRoleStatements property to the provider block in your serverless.yml, we are adding permissions to our DynamoDb table within your account.
provider:
name: aws
runtime: nodejs12.x
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Scan
- 'lambda:InvokeFunction'
Resource:
- { "Fn::GetAtt": ["PostsDynamoDBTable", "Arn" ] }
- '*'
- "arn:aws:dynamodb:${self:provider.region}:*:table/*"
environment:
POSTS_TABLE: ${self:custom.postsTableName}
NODE_ENV: production
LAMBDA: true
STAGE: ${self:provider.stage}The above environment variables are made available to our code under process.env.
Seeding it's a process of posting sample data into your dynamodb table, I will show you how to seed when deploying to dev environment. Let's do the following to create a sample json data.
mkdir seeds && cd seeds
touch posts.jsonAdd this content to posts.json:
[
{
"post_title": "Lorem Ipsum",
"author": "John Doe",
"post_category": [
{
"value": "React",
"label": "React"
}
],
"created_at": "2020-05-23T18:54:07.157Z",
"post_desc": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
"postId": "32f39119-42dd-4423-8601-2a0dae190527"
}
]Upon running
sls offline startpoststable will be seeded/populated with predefined json data.
Let's get data from our dynamodb post table. Create a server directory in root of your project and add two files:
mkdir server && cd server
touch dynamo.js postsModel.jspostsModel.js
const POSTS_TABLE = process.env.POSTS_TABLE;
const IS_OFFLINE = process.env.IS_OFFLINE;
const params = {
TableName: POSTS_TABLE
};
const visible = ["postId", "post_title", "post_body"];
const transform = v => {
console.log(v);
return Object.keys(v)
.filter(v => {
return IS_OFFLINE ? true : visible.includes(v);
})
.reduce((obj, key) => {
obj[key] = v[key];
return obj;
}, {});
};
module.exports = {
params,
visible,
transform
};Set up DynamoDb so that we can perform operations against it.
dynamo.js
const AWS = require("aws-sdk");
const { IS_OFFLINE } = process.env;
if( IS_OFFLINE ) {
AWS.config.update({
region: "us-east-1",
endpoint: "http://localhost:8000"
});
}
const dynamoDb = new AWS.DynamoDB.DocumentClient();
const postsModel = require("./postsModel");
exports.getPosts = () => {
return new Promise( async (resolve, revoke) => {
const params = {
TableName: postsModel.params.TableName,
}
dynamoDb.scan(params, (err, data) => {
if (err) {
revoke(err.message);
} else {
if( data.Items ) {
resolve(data.Items);
} else {
resolve([]);
}
}
});
});
};In the root directory, edit your custom server file to add API end-point that will query DynamoDB and send back data.
server.js
const express = require("express");
const next = require("next");
const bodyParser = require("body-parser");
const cors = require("cors");
const { LAMBDA, PORT, NODE_ENV } = process.env;
const dynamo = require("./server/dynamo");
const port = parseInt(PORT, 10) || 3000;
const dev = NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
global.fetch = require("node-fetch");
const createServer = () => {
const server = express();
server.use(bodyParser.json({ limit: "50mb" }));
server.use(cors());
server.get("/posts", async (req, res) => {
try {
const data = await dynamo.getPosts();
res.status(200).json({
data: data
});
} catch (error) {
res.status(500).json(error);
}
});
server.get("*", (req, res) => handle(req, res));
return server;
};
const server = createServer();
if (!LAMBDA) {
app.prepare().then(() => {
server.listen(port, err => {
if (err) throw err;
console.log(`Ready on http://localhost:${port}`);
});
});
}
exports.app = app;
exports.server = server;In this section we're going to initiate a call in the front-end to get us data from the back-end and display that data using Apisauce
npm i apisauce --saveLet's also upgrade to the latest next package, since our cloned repo is on 8.0.0:
npm uninstall next
npm install next
npm install babel-loaderAdd the code below to this file pages/index.js:
import React from 'react'
import { create } from 'apisauce'
const api = create({
baseURL: "http://localhost:3000",
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
timeout: 60000
});
const fetchData = async () => await api.get('/posts')
.then(res => ({
posts: res.data,
}))
.catch(() => ({
posts: [],
}),
);
class Index extends React.Component {
static async getInitialProps(ctx) {
const res = await fetchData();
const { posts } = res;
return { posts }
}
render() {
return <div>{JSON.stringify(this.props.posts)}</div>
}
}
export default IndexRun this command:
sls offline startAnd open http://localhost:3000 in a browser, You should be able to see the same result as the screenshot below:
Complete code can be found on Github.
