
React Native is awesome, just like you. We all love it, but building apps without calling a bunch of APIs is impossible. AWS provides you with all the services like AWS Lambda, S3, API Gateway, DynamoDB and everything which your app might need.
Building APIs in a backend to interact with every service you need might be just too much work - when you can just use IAM and Cognito to directly authorize AWS services.
Why not use AWS SDK in React Native?
You’ll be asking - why not just use AWS SDK for JavaScript, .NET and Java like we do in our backend?
You definitely can, if you want to:
- Increase your app bundle size
- Slow AWS V4 signature generation
- Polyfill with CryptoJS
Just @aws-sdk/client-lambda is of 2 MB alone, forget all the dependencies that it has.
If you’re looking for something which can be used in browser and modern JS runtime, check out aws4fetch.
A better solution using native crypto modules
Polyfills don’t always give us the best performance compared to built-in functions. So we can take advantage of newly-created crypto module for React Native which is react-native-quick-crypto.
⚡️ A fast implementation of Node’s
crypto
module written in C/C++ JSI Up to 58x faster than all other solutions
Create React Native Project
We’ll be using bun as a package manager. Let’s create a new React Native project from scratch.
bunx react-native init Call_AWS_Services
Install libraries
Along with react-native-quick-crypto, we will need react-native-quick-aws4, which will help us generate an AWS v4 signature.
bun add react-native-quick-crypto react-native-quick-aws4
Make sure to follow the instructions on react-native-quick-crypto’s README.
Build and run the React Native app
Start the metro bundler and hit a
to build for Android and i
to build for iOS.
bun run start
If you get any error while building the library, check out issues page.
Create Lambda Function & User
To test this, we will create a simple Lambda function which will echo the request from itself. You can create all these resources using AWS console, but it’s better to use AWS CDK which will make this 10x faster.
Create a CDK project with TypeScript
Let’s use bunx
to initialize our CDK project.
Feel free to cancel npm install
, and manually run bun i
.
mkdir echo-lambda-cdk
bunx cdk init app --language typescript
Make sure you have AWS CLI setup for CDK deployment.
If you’re deploying with CDK for the first time, you’ll need to run bunx cdk bootstrap
.
To learn more about CDK, read our CDK tutorial for beginners.
CDK Stack for Echo Lambda, User & Access Keys
It creates the following resources:
- AWS Lambda function from
.ts
file withARM 64
architecture. - IAM user which will invoke the lambda.
- Access Key and Secret Access Key for IAM user
It grants the IAM user permission to invoke the Lambda as well.
import * as cdk from "aws-cdk-lib";
import { CfnOutput } from "aws-cdk-lib";
import { Architecture, FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction, OutputFormat } from "aws-cdk-lib/aws-lambda-nodejs";
import { Construct } from "constructs";
import { AccessKey, User } from "aws-cdk-lib/aws-iam";
export class EchoLambdaCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const lambdaFn = new NodejsFunction(this, "echo-lambda-fn", {
functionName: "echo-fn",
entry: "./lib/echo-fn.ts",
bundling: {
format: OutputFormat.ESM,
},
architecture: Architecture.ARM_64,
// prevent Lambda from DDoS
reservedConcurrentExecutions: 2,
});
// create user with permission to invoke lambda and access key
const echoLambdaUser = new User(this, "echo-fn-invoker-user");
lambdaFn.grantInvoke(echoLambdaUser);
const accessKey = new AccessKey(this, "echo-fn-invoker-user-access-key", {
user: echoLambdaUser,
});
new CfnOutput(this, "AccessKeyId", { value: accessKey.accessKeyId });
new CfnOutput(this, "AccessKeySecret", {
value: accessKey.secretAccessKey.unsafeUnwrap(),
});
}
}
Echo Lambda Function Code
Put echo-fn.ts
inside the lib
directory.
The Lambda code simply returns the event:
// NOTE: Don't forget to make it async
export const handler = async (e: string) => {
return e;
};
Get the complete CDK code on LearnAWS GitHub repo.
Setup AWS Client
Use secure react-native-keys instead of hard coding accessKeyId
and secretAccessKey
.
import {AwsClient} from 'react-native-quick-aws4';
const awsClient = new AwsClient({
accessKeyId: 'xxx',
secretAccessKey: 'xxx',
region: 'ap-south-1', // replace it with your region
});
Code to invoke AWS Lambda Function
Now we define the payload for our API call where we pass these parameters:
functionName
- The name of functioninvocationType
- PassRequestResponse
for synchronous invocation andEvent
for async invocationpayload
- JSON stringified event which will be sent to Lambda
Learn more about request parameter on API docs.
The endpoint for AWS Lambda is:
POST /2015-03-31/functions/FunctionName/invocations?Qualifier=Qualifier HTTP/1.1
Thus, we make a function which makes the POST request using fetch
method from the client and sets the response into a state.
const invokeLambda = async () => {
const payload = {
functionName: 'echo-fn',
invocationType: 'RequestResponse',
payload: JSON.stringify(
`Hello from LearnAWS.io\nSent at: ${new Date().toTimeString()}`,
),
};
const lambdaRes = await awsClient.fetch(
`https://lambda.${awsClient.region}.amazonaws.com/2015-03-31/functions/${payload.functionName}/invocations`,
{
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: payload.payload,
},
);
setRes(await lambdaRes.json());
};
React Native Component
You can create the desired UI for your app - here we’ve simply added a button and some text.
import {Button, StyleSheet, Text, View} from 'react-native';
import React, {useState} from 'react';
import {AwsClient} from 'react-native-quick-aws4';
const awsClient = ...
export default function App() {
const [res, setRes] = useState('');
const invokeLambda = asyn ()=> ...
return (
<View style={styles.container}>
<Text style={styles.text}>{res}</Text>
<Button title="Invoke Lambda" onPress={invokeLambda} />
</View>
);
}
const styles = StyleSheet.create({
container: {margin: 12},
text: {
fontSize: 18,
fontFamily: 'monospace',
paddingVertical: 12,
},
});
Full code can be found on LearnAWS-io/ReactNative-Invoke-Lambda repo.
Application Demo
Initially the response is empty. When we click on Invoke Lambda, the Lambda gets invoked, and we see the response with the timestamp which we are sending in the payload.
What more can be done?
By having AWS IAM access - anything can be done, but should you do it? If you’re building something which is going to be accessible to everyone you should avoid embedding access key and secrets in your application. Build APIs which have authorization and rate limit, then only let the user access it.
AWS Services which can be used for public access
If you are an IAM expert and can set permissions with least privilege, you can use some of the AWS services like:
- AWS Lambda
- AWS Lex (chat bot)
- API Gateway
- AWS Cost Explorer
Can we use all the services for internal applications?
Yes and No. You might want to skip building APIs and directly interact with AWS services from your frontend application itself.
For example if you’re building an S3 file manager for your organization, it might be a good idea to just call the Amazon S3 APIs directly from your frontend. Just make sure you’re giving the least permission to the user who will use the Access Key and Secret.
If your organization is big - and many people would have access to the application, you can:
- Create individual IAM account for each user with Access Key and Secret
- Use Amazon Cognito with identity-based policy (S3 example)
Creating so many IAM users with Access Key and Secret for every employee can be tedious, so you can just use Amazon Cognito with Identity pool to let people access AWS services and resources.
You can retrieve the Access Key and Secret from the Cognito itself, learn more on the docs.
If a company decides to let go someone they can just disable or delete the account from the Cognito user pool and they won’t have access to AWS services any longer.