Creating the UI & AWS API Gateway and Lambda Integration

Part 2 of Implementing an Authentication Flow Using react-native and redux

Introduction

This article builds on my previous post discussing implementing authentication in a react-native application. This posts walks through creating a react-native application that can send CRUD requests to an Amazon Web Services (AWS) API Gateway that passes the request to a Lambda function for execution. The code for this application is located at the step1_get_response_from_lambdas branch of this Github repo.

Design

My goal at this point is to create a proof of concept application that sends requests to an API Gateway endpoint. Next, that API Gateway endpoint will pass the request to a Lambda. Then, the Lambda receives the request and send s a response back to the client (through the gateway). Finally, the client will display an indication that it received a response from the server

Tools

This application will be built using react-native for this application. Additionally, I will use redux to maintain application state such a login status and request responses. I will augment redux with redux-saga and redux-promise-middleware in order to enable an application pattern that I’ve found useful. Additionally, to support an eventual cross platform deployment I’ll use create-react-native-app and Expo.io create and then build the application.

The application will communicate with a serverless backend consisting of API Gateway which will provide http endpoints for the client to access, and Lambda functions to create and send responses.

Starting to bring it all together

Implementation

It would take forever to detail how to use each of the tools I mentioned, so I recommend YouTube videos, Google searches, and repeatedly reading the documentation for each to get a thorough understanding of them.

Here’s a list of videos that I’ve used:

Expo.io: https://www.youtube.com/watch?v=IQI9aUlouMI

API Gateway Introduction: https://www.youtube.com/watch?v=ZFl3Qm-DBrg

Build and Manage API Gateway: https://www.youtube.com/watch?v=FcH8ovrNd9k&t=675s

Lambda: https://www.youtube.com/watch?v=fSUEk6iMW88

Lambda + API Gateway: https://www.youtube.com/watch?v=8U4RRw3PwGw

redux-saga and redux-promise-middleware

Combining redux-saga and redux-promise-middleware creates a useful pattern for dealing with asynchronous actions.

Redux-promise-middleware posts redux actions during “life-cycle” events in a promise function’s execution. A redux reducer can react to these actions to alter the redux store. redux-promise-middleware posts these events at certain points of the promise Lifecycle. For example, in response to a promise that runs in reaction to an action of type ‘FOO’, redux-promise-middleware will post a FOO_PENDING action when the promise starts. It will then post either a FOO_REJECTED action if the promise is rejected, or a FOO_FULFILLED or if the promise resolves successfully. These actions can be used to execute reducer functions that alter state.

Redux-saga is can dispatch one or more actions in response specified events. This functionality is the titular saga in redux-saga. I will integrate Login with Facebook functionality in the next post, but for now it provides the best example of using sequential actions. A successful Facebook Login using the Expo.io Facebook API does not include a URL to the user’s profile picture. However, it does include the user’s ID which can be used in a call to the Facebook Graph API to get the picture URL. Redux-saga can respond to respond to the a FACEBOOK_LOGIN_FULFILLED event and fire a GET_FACEBOOK_PROFILE_PICTURE event.

Flow for unsuccessful Promise completion
Flow for successful Promise completion

The code below is a saga that takes every message that ends in PENDING and and fires an action of type SHOW_PENDING_DIALOG that runs a reducer that changes the redux store. This saga results in a popup dialog box notifying the user that their request is being executed.

function* showPendingDialog(action) {
try {
// console.log("showPendingDialog");
let message = "";
switch (action.type) {
case 'POST_PENDING':
message = "performing post";
break;
case 'GET_PENDING':
message = "performing get";
break;
case 'PUT_PENDING':
message = "performing put";
break;
case 'DELETE_PENDING':
message = "performing delete";
break;
}
   yield put({
type: "SHOW_PENDING_DIALOG",
message: message
});
} catch (e) {
yield console.log('ERROR: showPendingDialog');
}
}
export function* sagaShowPendingDialog() {
yield takeEvery(['POST_PENDING', 'GET_PENDING', 'DELETE_PENDING', 'PUT_PENDING'], showPendingDialog);
}

The doPost function that sends a post request to an http endpoint event is shown below:

export const doPost = (service, userInfo) => {
/*
https://developer.mozilla.org/en- US/docs/Web/API/Fetch_API/Using_Fetch
The Promise returned from fetch() won’t reject on
HTTP error status even if the response is an HTTP 404 or 500.
Instead, it will resolve normally (with ok status set to false),
and it will only reject on network failure or if anything prevented
the request from completing.
*/
return fetch(appSecrets.aws.apiURL, {
method: 'POST',
body: JSON.stringify({
'bodyParam1': `you sent me to the server, and now I'm back!`,
})
})
.then((response) => {
if (response.status !== 200) {
// the handle errors function handles HTTP response error codes
let errorMessage = handleErrors(response.status);
return errorMessage;
}
else {
return response.text();
}
})
.then((response) => {
return handleResponse(response);
})
.catch(function (err) {
console.log("error: ", err);
})
}

Error handling is done two different ways. A fetch that experiences a network error results in a POST_REJECTED action being dispatched. On the other hand, completed responses from the API are returned in an object that includes a type property. Since fetch does not reject as long as it receives a response from the server, a type value of “error” is used to signify a response with a status code other than 200.

The handleErrors function that is used by doPost, doGet, doDelete, and doPut functions is shown below. It is called when a response has a status code other than 200 and is responsible for returning a response object with a type value of “error” and a response appropriate to the status code.

const handleErrors = (responseStatus, resolve) => {
switch (responseStatus) {
case 500:
resolve({
type: 'error',
response: 'server error'
});
case 403:
resolve({
type: 'error',
response: 'access forbidden'
});
default:
resolve({
type: 'error',
response: 'unknown error'
});
}
}

Similarly, returning responses for promise resolution is handled by the handleResponse function which first checks to see if the response already has a type property (which would have been assigned by the handleError function). If so, the response is an object created by the handleError function and is returned immediately. If not, a new response object is built with type of success, and the server’s response as the response value.

To summarize:

· redux-promise-middleware dispatches actions in response to promise lifecycle events.

· redux-saga can interact with specified actions and dispatch additional actions in response.

Building the UI

I’m going to assume anybody reading this post is familiar with using react/react-native with redux, and that they know how to connect a button’s press event so that a redux action is dispatched resulting in changes to the redux store. Additionally, they know how to connect a text input so that it displays a value kept in a store. There are tutorials, documentation, and videos to help anybody unfamiliar with those concepts.

Given that prerequisite knowledge, the first step is to create a UI with the following 7 buttons:

  1. Post
  2. Put
  3. Get
  4. Delete

All of the buttons dispatch an action that return a type and a payload that calls a function that returns a promise similar to the one below for the doPost action:

doPost: function (service, userInfo) {
return {
type: 'POST',
payload: apiTestUtils.doPost(service, userInfo)
}
},

At this point, you may be wondering why the payload is a function. This is the first step in the pattern created by using redux-saga and redux-promise-middleware. This action calls in the doPost function shown previously.

A successful doPost dispatches a POST_FULFILLED action which invokes the following saga that dispatches three actions that do the following:

  1. Closes the pop-up modal indicating the event is pending
  2. Displays a pop-up modal indicating the request succeeded
  3. Updates the redux store to include the API’s response.
unction* handleAPISuccess(action) {
try {
let message = "";
switch (action.type) {
case 'POST_FULFILLED':
message = "successful post";
break;
case 'GET_FULFILLED':
message = "successful get";
break;
case 'DELETE_FULFILLED':
message = "successful delete";
break;
case 'PUT_FULFILLED':
message = "successful put";
break;
}
yield put({
type: "CLOSE_PENDING_DIALOG",
});
yield put({
type: "SHOW_SUCCESS_DIALOG",
message: message
});
yield put({
type: 'UPDATE_RESPONSE_MESSAGE',
responseType: action.payload.responseType,
responseMessage: action.payload.responseMessage
});
} catch (e) {
yield console.log('ERROR: showSuccessDialog');
}
}

As shown previously, a successful Facebook login triggers another request to the Facebook Graph API to retrieve the user profile picture URL. Regardless of the login method, once the profile picture URL is available it is saved in the redux store with the key profilePicture. An Image component within an Animatable.View is has its source set to the profilePicture so that it displays the profile picture once the URL is available.

Setting Up API Gateway and Lambdas

Setting up and using AWS services can be pretty intimidating at first glance. AWS provides a multitude of different services, and one might be tempted to believe that they need to understand how to use everything. Fortunately, that is not the case. Additionally, there is an excellent video series walking through how to employ the two services this project utilizes; API Gateway and Lambda. Combining these two services provides a fairly simple technique to create a serverless backend that the application can communicate with.

YouTube user Savjee has an excellent series of videos that go over creating Lambdas and connecting them to API Gateway. I recommend them highly. I did do one thing that was not shown in the video and selected “Use Lambda Proxy Integration” when setting up each resource. I set up four API Gateway resources combining Savjee’s techniques and Lambda Proxy Integration: POST, PUT, GET, andDELETE . Each of the resources receive requests from the four corresponding buttons on application as previously described. The resources then pass the request to a corresponding Lambda function. Each Lambda returns a response using the same techniques shown in Savjee’s videos.

The Lambdas I created for this project are very simple since the primary goal is to complete the proof of concept application. The Lambda shown below responds to POST requests from the client.

'use strict';
var util = require('util');    // util enables deep looks into an object
exports.handler = (event, context, callback) => {
// print out the event object to view what was sent
//console.log(util.inspect(event, { showHidden: true, depth: null }));
  console.log(event.body);
  // must respond according to this documentation   https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html#api-gateway-simple-proxy-for-lambda-output-format
  // since Lamda Proxy integration was enabled when setting up this method in API Gateway the data sent resides in the event.body object
  callback(null, {
// return a status code indicating success or failure
"statusCode": 200,
// make the response object string using JSON.stringify
"body": JSON.stringify({
msg: "Hello from apiTestPost. You sent the following object: ",
received: event.body})
});
};

API Gateway presents an API endpoint to use when the API is deployed. Accessing these endpoint using fetch or your favorite http library results in the Lambdas running and returning a response upon completion.

Summary

It is becoming an increasingly acknowledged fact that react-native enables rapid application development. This post shows how to combine it with redux, redux-saga, and redux-promise-middleware to create an application that has multiple reactions to asynchronous network requests.

Serverless architecture is one of the trendiest concepts in development today. However, it’s not just another pretty buzzword. It can empower a developer to create a server application without worrying about development operations or the “care and feeding” of hardware.

This post showed that a serverless architecture and react-native can be combined to create a simple, easily extensible client application to access a serverless backend.

To be continued…

My next post will add social login to Facebook and Google, and how to use API Gateway custom authorizers to control access to the Lambda functions that make up the API’s backend.

Reginald Johnson has maintained his passion for coding throughout his 20+ year career as an Officer in the United States Navy. He enjoys applying his training and experience in programming, Systems Engineering, and Operational Planning towards programming. Follow him Twitter @reginald3.