Skip to content

Commit 36af94e

Browse files
committed
Lambda durable functions - Call a REST API Node.js
1 parent 422e391 commit 36af94e

File tree

6 files changed

+334
-0
lines changed

6 files changed

+334
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# SAM
2+
.aws-sam/
3+
samconfig.toml
4+
5+
# Node.js
6+
node_modules/
7+
package-lock.json
8+
npm-debug.log*
9+
yarn-debug.log*
10+
yarn-error.log*
11+
12+
# IDE
13+
.vscode/
14+
.idea/
15+
*.swp
16+
*.swo
17+
*~
18+
19+
# OS
20+
.DS_Store
21+
Thumbs.db
22+
23+
# Test outputs
24+
response.json
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Lambda Durable Function - REST API Call with Node.js
2+
3+
This pattern demonstrates a Lambda durable function that calls an external REST API using Node.js.
4+
5+
Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >>
6+
7+
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
8+
9+
## Requirements
10+
11+
* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
12+
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
13+
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
14+
* [AWS Serverless Application Model](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install.html) (AWS SAM) installed
15+
16+
## Deployment Instructions
17+
18+
1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
19+
```
20+
git clone https://github.com/aws-samples/serverless-patterns
21+
```
22+
1. Change directory to the pattern directory:
23+
```
24+
cd lambda-durable-rest-api-sam-js
25+
```
26+
27+
1. From the command line, use AWS SAM to deploy the AWS resources for the pattern as specified in the template.yml file:
28+
```
29+
sam build
30+
sam deploy --guided
31+
```
32+
1. During the prompts:
33+
* Enter a stack name
34+
* Enter the desired AWS Region
35+
* Allow SAM CLI to create IAM roles with the required permissions.
36+
37+
Once you have run `sam deploy --guided` mode once and saved arguments to a configuration file (samconfig.toml), you can use `sam deploy` in future to use these defaults.
38+
39+
1. Note the outputs from the SAM deployment process. These contain the resource names and/or ARNs which are used for testing.
40+
41+
## How it works
42+
43+
This pattern demonstrates AWS Lambda Durable Execution for calling external REST APIs. It uses the AWS Durable Execution SDK to create a durable function that can:
44+
45+
**AWS Durable Execution Features:**
46+
- **Automatic State Management**: AWS manages execution state across invocations
47+
- **Durable Steps**: The `context.step()` method marks functions that can be retried automatically
48+
- **Durable Waits**: Use `context.wait()` to pause execution without consuming CPU or memory
49+
- **Built-in Retry Logic**: Failed steps are automatically retried by AWS
50+
- **Execution History**: AWS tracks the complete execution history and state
51+
52+
The function uses the `withDurableExecution` wrapper to mark the Lambda handler as a durable execution workflow. All steps defined with `context.step()` are automatically retryable.
53+
54+
AWS Lambda Durable Execution automatically handles failures, retries, and state persistence without requiring external services like DynamoDB or Step Functions.
55+
56+
**Note**: This pattern requires Node.js 24.x runtime which has native support for durable execution.
57+
58+
## Testing
59+
60+
1. Get the function name from the stack outputs:
61+
```bash
62+
aws cloudformation describe-stacks --stack-name <your-stack-name> \
63+
--query 'Stacks[0].Outputs[?OutputKey==`FunctionName`].OutputValue' --output text
64+
```
65+
66+
2. Invoke the function with default URL:
67+
```bash
68+
aws lambda invoke \
69+
--function-name <function-name> \
70+
--payload '{}' \
71+
response.json && cat response.json
72+
```
73+
74+
3. Invoke with a custom URL:
75+
```bash
76+
aws lambda invoke \
77+
--function-name <function-name> \
78+
--payload '{"url": "https://jsonplaceholder.typicode.com/users/1"}' \
79+
response.json && cat response.json
80+
```
81+
82+
Example JSON Lambda test event:
83+
```json
84+
{
85+
"url": "https://jsonplaceholder.typicode.com/posts/1"
86+
}
87+
```
88+
89+
Expected response (success):
90+
```json
91+
{
92+
"statusCode": 200,
93+
"headers": {"Content-Type": "application/json"},
94+
"body": "{\"message\": \"API call successful\", \"url\": \"https://jsonplaceholder.typicode.com/posts/1\", \"data\": {...}}"
95+
}
96+
```
97+
98+
The execution is durable - if the API call fails, AWS Lambda will automatically retry the `callRestApi` step without re-executing the entire function.
99+
100+
## Cleanup
101+
102+
1. Delete the stack:
103+
```bash
104+
aws cloudformation delete-stack --stack-name <your-stack-name>
105+
```
106+
1. Confirm the stack has been deleted:
107+
```bash
108+
aws cloudformation list-stacks --query "StackSummaries[?contains(StackName,'<your-stack-name>')].StackStatus"
109+
```
110+
111+
----
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
{
2+
"title": "Lambda Durable Function - REST API Call (Node.js)",
3+
"description": "A Lambda function that calls an external REST API using AWS Durable Execution SDK for automatic retries and state management.",
4+
"language": "Node.js",
5+
"level": "200",
6+
"framework": "SAM",
7+
"introBox": {
8+
"headline": "How it works",
9+
"text": [
10+
"This pattern demonstrates AWS Lambda Durable Execution for calling external REST APIs.",
11+
"Uses the withDurableExecution wrapper to mark the Lambda handler as a durable workflow.",
12+
"Uses context.step() to make the REST API call automatically retryable.",
13+
"AWS automatically handles failures, retries, and state persistence without external services."
14+
]
15+
},
16+
"gitHub": {
17+
"template": {
18+
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-durable-rest-api-sam-js",
19+
"templateURL": "serverless-patterns/lambda-durable-rest-api-sam-js",
20+
"projectFolder": "lambda-durable-rest-api-sam-js",
21+
"templateFile": "template.yaml"
22+
}
23+
},
24+
"resources": {
25+
"bullets": [
26+
{
27+
"text": "AWS Lambda Durable Execution",
28+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/durable-functions.html"
29+
},
30+
{
31+
"text": "AWS Lambda Developer Guide",
32+
"link": "https://docs.aws.amazon.com/lambda/latest/dg/welcome.html"
33+
},
34+
{
35+
"text": "Node.js Fetch API",
36+
"link": "https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API"
37+
}
38+
]
39+
},
40+
"deploy": {
41+
"text": [
42+
"sam build",
43+
"sam deploy --guided"
44+
]
45+
},
46+
"testing": {
47+
"text": [
48+
"See the GitHub repo for detailed testing instructions."
49+
]
50+
},
51+
"cleanup": {
52+
"text": [
53+
"Delete the stack: <code>aws cloudformation delete-stack --stack-name STACK_NAME</code>."
54+
]
55+
},
56+
"authors": [
57+
{
58+
"name": "Theophilus Ajayi",
59+
"image": "https://drive.google.com/file/d/1hUrUxWk2JqDTbPhl0DgUeUVd2uFWnAby/view?usp=drivesdk",
60+
"bio": "Technical Account Manager @ AWS",
61+
"linkedin": "tolutheo"
62+
}
63+
]
64+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Lambda Durable Function - Calls REST API using AWS Durable Execution SDK
3+
* Note: Node.js 22.x has built-in durable execution support
4+
*/
5+
6+
/**
7+
* Lambda Durable Function - Calls REST API using AWS Durable Execution SDK
8+
*/
9+
import { withDurableExecution } from '@aws/durable-execution-sdk-js';
10+
11+
const DEFAULT_API_URL = process.env.API_URL || 'https://jsonplaceholder.typicode.com/posts/1';
12+
13+
export const handler = withDurableExecution(async (event, context) => {
14+
context.logger.info('Starting durable REST API call');
15+
16+
// Get API URL from event or use default
17+
const apiUrl = event.url || DEFAULT_API_URL;
18+
19+
context.logger.info(`Using API URL: ${apiUrl}`);
20+
21+
// Execute the REST API call as a durable step
22+
const result = await context.step('Call REST API', async (stepCtx) => {
23+
stepCtx.logger.info(`Calling REST API: ${apiUrl}`);
24+
25+
try {
26+
const response = await fetch(apiUrl, {
27+
method: 'GET',
28+
signal: AbortSignal.timeout(30000) // 30 second timeout
29+
});
30+
31+
if (!response.ok) {
32+
throw new Error(`HTTP error! status: ${response.status}`);
33+
}
34+
35+
const data = await response.json();
36+
37+
stepCtx.logger.info(`API call successful: ${response.status}`);
38+
39+
return {
40+
status: 'success',
41+
statusCode: response.status,
42+
data: data
43+
};
44+
45+
} catch (error) {
46+
stepCtx.logger.error(`API call failed: ${error.message}`);
47+
return {
48+
status: 'error',
49+
error: error.message
50+
};
51+
}
52+
});
53+
54+
// Pause for 2 seconds without consuming CPU cycles or incurring usage charges
55+
await context.wait({ seconds: 2 });
56+
57+
// Context logger is replay aware and will not log the same message multiple times
58+
context.logger.info('Waited for 2 seconds without consuming CPU.');
59+
60+
// Return response based on result
61+
if (result.status === 'success') {
62+
const response = {
63+
statusCode: 200,
64+
headers: { 'Content-Type': 'application/json' },
65+
body: JSON.stringify({
66+
message: 'API call successful',
67+
url: apiUrl,
68+
data: result.data
69+
})
70+
};
71+
return response;
72+
} else {
73+
const response = {
74+
statusCode: 500,
75+
headers: { 'Content-Type': 'application/json' },
76+
body: JSON.stringify({
77+
error: 'API call failed',
78+
url: apiUrl,
79+
details: result.error
80+
})
81+
};
82+
return response;
83+
}
84+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "lambda-durable-rest-api",
3+
"version": "1.0.0",
4+
"description": "Lambda Durable Function - REST API Call with Node.js",
5+
"type": "module",
6+
"main": "app.mjs",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1"
9+
},
10+
"dependencies": {
11+
"@aws/durable-execution-sdk-js": "^1.0.0"
12+
},
13+
"keywords": [
14+
"aws",
15+
"lambda",
16+
"durable",
17+
"rest-api"
18+
],
19+
"author": "",
20+
"license": "MIT-0"
21+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
AWSTemplateFormatVersion: '2010-09-09'
2+
Transform: AWS::Serverless-2016-10-31
3+
Description: Lambda Durable Function - Call REST API with Node.js
4+
5+
Resources:
6+
DurableFunction:
7+
Type: AWS::Serverless::Function
8+
Properties:
9+
CodeUri: src/
10+
Handler: app.handler
11+
Runtime: nodejs24.x
12+
Timeout: 300
13+
DurableConfig:
14+
ExecutionTimeout: 900 # 15 minutes - allows durablefunction to be invoked syncronously and asynchronously
15+
RetentionPeriodInDays: 7
16+
MemorySize: 512
17+
Architectures:
18+
- x86_64
19+
Environment:
20+
Variables:
21+
API_URL: https://jsonplaceholder.typicode.com/posts/1
22+
23+
Outputs:
24+
FunctionArn:
25+
Description: Lambda Function ARN
26+
Value: !GetAtt DurableFunction.Arn
27+
28+
FunctionName:
29+
Description: Lambda Function Name
30+
Value: !Ref DurableFunction

0 commit comments

Comments
 (0)