Skip to content

Commit 155fd11

Browse files
committed
feat: Add `aws-node-express-dynamodb-api
1 parent 09a5890 commit 155fd11

File tree

8 files changed

+314
-0
lines changed

8 files changed

+314
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ serverless install -u https://github.com/serverless/examples/tree/master/folder-
5757
| [Aws Node Dynamodb Backup](https://github.com/serverless/examples/tree/master/aws-node-dynamodb-backup) <br/> Serverless DynamoDB changes backed up to S3 | nodeJS |
5858
| [Aws Env Variables Encrypted In A File](https://github.com/serverless/examples/tree/master/aws-node-env-variables-encrypted-in-a-file) <br/> Serverless example managing secrets in an encrypted file | nodeJS |
5959
| [Aws Env Variables](https://github.com/serverless/examples/tree/master/aws-node-env-variables) <br/> This example demonstrates how to use environment variables for AWS Lambdas. | nodeJS |
60+
| [Aws Node Express Dynamodb Api](https://github.com/serverless/examples/tree/master/aws-node-express-dynamodb-api) | nodeJS |
6061
| [Aws Fetch File And Store In S3](https://github.com/serverless/examples/tree/master/aws-node-fetch-file-and-store-in-s3) <br/> Fetch an image from remote source (URL) and then upload the image to a S3 bucket. | nodeJS |
6162
| [Sfe Demo Leadcapture](https://github.com/serverless/examples/tree/master/aws-node-fullstack/backend) | nodeJS |
6263
| [Frontend](https://github.com/serverless/examples/tree/master/aws-node-fullstack/frontend) | nodeJS |
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
.serverless
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<!--
2+
title: 'Serverless Framework Node Express API service backed by DynamoDB on AWS'
3+
description: 'This template demonstrates how to develop and deploy a simple Node Express API service backed by DynamoDB running on AWS Lambda using the traditional Serverless Framework.'
4+
layout: Doc
5+
framework: v2
6+
platform: AWS
7+
language: nodeJS
8+
authorLink: 'https://github.com/serverless'
9+
authorName: 'Serverless, inc.'
10+
authorAvatar: 'https://avatars1.githubusercontent.com/u/13742415?s=200&v=4'
11+
-->
12+
13+
# Serverless Framework Node Express API on AWS
14+
15+
This template demonstrates how to develop and deploy a simple Node Express API service, backed by DynamoDB database, running on AWS Lambda using the traditional Serverless Framework.
16+
17+
18+
## Anatomy of the template
19+
20+
This template configures a single function, `api`, in `serverless.yml` which is responsible for handling all incoming requests thanks to configured `http` events. To learn more about `http` event configuration options, please refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). As the events are configured in a way to accept all incoming requests, `express` framework is responsible for routing and handling requests internally. Implementation lives in `src` directory and takes advantage of `serverless-http` package, which allows you to wrap existing `express` applications. To learn more about `serverless-http`, please refer to corresponding [GitHub repository](https://github.com/dougmoscrop/serverless-http). Additionally, it also handles provisioning of a DynamoDB database that is used for storing data about users. The `express` application exposes two endpoints, `POST /users` and `GET /user/{userId}`, which allow to create and retrieve users.
21+
22+
## Usage
23+
24+
### Deployment
25+
26+
This example is made to work with the Serverless Framework dashboard, which includes advanced features such as CI/CD, monitoring, metrics, etc.
27+
28+
In order to deploy with dashboard, you need to first login with:
29+
30+
```
31+
serverless login
32+
```
33+
34+
install dependencies with:
35+
36+
```
37+
npm install
38+
```
39+
40+
and then perform deployment with:
41+
42+
```
43+
serverless deploy
44+
```
45+
46+
After running deploy, you should see output similar to:
47+
48+
```bash
49+
Serverless: Packaging service...
50+
Serverless: Excluding development dependencies...
51+
Serverless: Creating Stack...
52+
Serverless: Checking Stack create progress...
53+
........
54+
Serverless: Stack create finished...
55+
Serverless: Uploading CloudFormation file to S3...
56+
Serverless: Uploading artifacts...
57+
Serverless: Uploading service aws-node-express-dynamodb-api.zip file to S3 (718.53 KB)...
58+
Serverless: Validating template...
59+
Serverless: Updating Stack...
60+
Serverless: Checking Stack update progress...
61+
....................................
62+
Serverless: Stack update finished...
63+
Service Information
64+
service: aws-node-express-dynamodb-api
65+
stage: dev
66+
region: us-east-1
67+
stack: aws-node-express-dynamodb-api-dev
68+
resources: 13
69+
api keys:
70+
None
71+
endpoints:
72+
ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/
73+
ANY - https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/{proxy+}
74+
functions:
75+
api: aws-node-express-dynamodb-api-dev-api
76+
layers:
77+
None
78+
```
79+
80+
_Note_: In current form, after deployment, your API is public and can be invoked by anyone. For production deployments, you might want to configure an authorizer. For details on how to do that, refer to [http event docs](https://www.serverless.com/framework/docs/providers/aws/events/apigateway/). Additionally, in current configuration, DynamoDB Table will be removed when running `serverless remove`. To retain DynamoDB Table even after removal of the stack, add `DeletionPolicy: Retain` to its resource definition.
81+
82+
### Invocation
83+
84+
After successful deployment, you can create a new user by calling the corresponding endpoint:
85+
86+
```bash
87+
curl --request POST 'https://xxxxxx.execute-api.us-east-1.amazonaws.com/dev/users' --header 'Content-Type: application/json' --data-raw '{"name": "John", "userId": "someUserId"}'
88+
```
89+
90+
Which should result in the following response:
91+
92+
```bash
93+
{"userId":"someUserId","name":"John"}
94+
```
95+
96+
You can later retrieve the user by `userId` by calling the following endpoint:
97+
98+
```bash
99+
curl https://xxxxxxx.execute-api.us-east-1.amazonaws.com/dev/users/someUserId
100+
```
101+
102+
Which should result in the following response:
103+
104+
```bash
105+
{"userId":"someUserId","name":"John"}
106+
```
107+
108+
If you try to retrieve user that does not exist, you should receive the following response:
109+
110+
```bash
111+
{"error":"Could not find user with provided \"userId\""}
112+
```
113+
114+
### Local development
115+
116+
It is also possible to emulate DynamodB, API Gateway and Lambda locally by using `serverless-dynamodb-local` and `serverless-offline` plugins. In order to do that, execute the following commands:
117+
118+
```bash
119+
serverless plugin install -n serverless-dynamodb-local
120+
serverless plugin install -n serverless-offline
121+
```
122+
123+
It will add both plugins to `devDependencies` in `package.json` file as well as will add it to `plugins` in `serverless.yml`. Make sure that `serverless-offline` is listed as last plugin in `plugins` section:
124+
125+
```
126+
plugins:
127+
- serverless-dynamodb-local
128+
- serverless-offline
129+
```
130+
131+
You should also add the following config to `custom` section in `serverless.yml`:
132+
133+
```
134+
custom:
135+
(...)
136+
dynamodb:
137+
start:
138+
migrate: true
139+
stages:
140+
- dev
141+
```
142+
143+
Additionally, we need to reconfigure `AWS.DynamoDB.DocumentClient` to connect to our local instance of DynamoDB. We can take advantage of `IS_OFFLINE` environment variable set by `serverless-offline` plugin and replace:
144+
145+
```javascript
146+
const dynamoDbClient = new AWS.DynamoDB.DocumentClient();
147+
```
148+
149+
with the following:
150+
151+
```javascript
152+
const dynamoDbClientParams = {};
153+
if (process.env.IS_OFFLINE) {
154+
dynamoDbClientParams.region = 'localhost'
155+
dynamoDbClientParams.endpoint = 'http://localhost:8000'
156+
}
157+
const dynamoDbClient = new AWS.DynamoDB.DocumentClient(dynamoDbClientParams);
158+
```
159+
160+
After that, running the following command with start both local API Gateway emulator as well as local instance of emulated DynamoDB:
161+
162+
```bash
163+
serverless offline start
164+
```
165+
166+
To learn more about the capabilities of `serverless-offline` and `serverless-dynamodb-local`, please refer to their corresponding GitHub repositories:
167+
- https://github.com/dherault/serverless-offline
168+
- https://github.com/99x/serverless-dynamodb-local
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "aws-node-express-dynamodb-api",
3+
"version": "1.0.0",
4+
"description": "",
5+
"dependencies": {
6+
"express": "^4.17.1",
7+
"serverless-http": "^2.7.0"
8+
}
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name: aws-node-express-dynamodb-api
2+
org: serverlessinc
3+
description: Deploys a Node Express API service backed by DynamoDB with traditional Serverless Framework
4+
keywords: aws, serverless, faas, lambda, node, express, dynamodb
5+
repo: https://github.com/serverless/examples/aws-node-express-dynamodb-api
6+
license: MIT
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
service: aws-node-express-dynamodb-api
2+
3+
frameworkVersion: '2'
4+
variablesResolutionMode: '20210219'
5+
configValidationMode: 'error'
6+
7+
custom:
8+
tableName: 'users-table-${self:provider.stage}'
9+
10+
provider:
11+
name: aws
12+
runtime: nodejs12.x
13+
lambdaHashingVersion: '20201221'
14+
stage: dev
15+
iam:
16+
role:
17+
statements:
18+
- Effect: Allow
19+
Action:
20+
- dynamodb:Query
21+
- dynamodb:Scan
22+
- dynamodb:GetItem
23+
- dynamodb:PutItem
24+
- dynamodb:UpdateItem
25+
- dynamodb:DeleteItem
26+
Resource:
27+
- Fn::GetAtt: [ UsersTable, Arn ]
28+
apiGateway:
29+
shouldStartNameWithService: true
30+
environment:
31+
USERS_TABLE: ${self:custom.tableName}
32+
33+
functions:
34+
api:
35+
handler: src/index.handler
36+
events:
37+
- http:
38+
path: /
39+
method: ANY
40+
- http:
41+
path: /{proxy+}
42+
method: ANY
43+
44+
resources:
45+
Resources:
46+
UsersTable:
47+
Type: AWS::DynamoDB::Table
48+
Properties:
49+
AttributeDefinitions:
50+
- AttributeName: userId
51+
AttributeType: S
52+
KeySchema:
53+
- AttributeName: userId
54+
KeyType: HASH
55+
ProvisionedThroughput:
56+
ReadCapacityUnits: 1
57+
WriteCapacityUnits: 1
58+
TableName: ${self:custom.tableName}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const AWS = require("aws-sdk");
2+
const express = require("express");
3+
4+
const app = express();
5+
6+
const USERS_TABLE = process.env.USERS_TABLE;
7+
const dynamoDbClient = new AWS.DynamoDB.DocumentClient();
8+
9+
app.use(express.json());
10+
11+
app.get("/users/:userId", async function (req, res) {
12+
const params = {
13+
TableName: USERS_TABLE,
14+
Key: {
15+
userId: req.params.userId,
16+
},
17+
};
18+
19+
try {
20+
const { Item } = await dynamoDbClient.get(params).promise();
21+
if (Item) {
22+
const { userId, name } = Item;
23+
res.json({ userId, name });
24+
} else {
25+
res
26+
.status(404)
27+
.json({ error: 'Could not find user with provided "userId"' });
28+
}
29+
} catch (error) {
30+
console.log(error);
31+
res.status(500).json({ error: "Could not retreive user" });
32+
}
33+
});
34+
35+
app.post("/users", async function (req, res) {
36+
const { userId, name } = req.body;
37+
if (typeof userId !== "string") {
38+
res.status(400).json({ error: '"userId" must be a string' });
39+
} else if (typeof name !== "string") {
40+
res.status(400).json({ error: '"name" must be a string' });
41+
}
42+
43+
const params = {
44+
TableName: USERS_TABLE,
45+
Item: {
46+
userId: userId,
47+
name: name,
48+
},
49+
};
50+
51+
try {
52+
await dynamoDbClient.put(params).promise();
53+
res.json({ userId, name });
54+
} catch (error) {
55+
console.log(error);
56+
res.status(500).json({ error: "Could not create user" });
57+
}
58+
});
59+
60+
app.use((req, res, next) => {
61+
return res.status(404).json({
62+
error: "Not Found",
63+
});
64+
});
65+
66+
module.exports = app;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const serverless = require("serverless-http");
2+
const app = require("./app");
3+
4+
module.exports.handler = serverless(app);

0 commit comments

Comments
 (0)