Implementing serverless architecture in React Native apps
Tags: reactnative
Published: 2023-11-07 16:51:27

**Written by [Clara Ekekenta](https://blog.logrocket.com/author/claraekekenta/)✏️**

Application development methodologies are continually evolving. Among the most groundbreaking shifts we've seen recently is the transition toward serverless architecture. 

At its core, serverless architecture enables developers to focus on writing code without the overhead of managing server infrastructure. Concerns like allocating resources, maintaining server health, or scaling based on demand, are handed over to cloud service providers. The result is a more streamlined, efficient, and cost-effective development process. 

React Native brings the "write once, run anywhere" magic, while serverless offers nimble deployments without the fuss of server management. Together, they're changing the game, offering speed, scalability, and cost savings. 

This tutorial will explore how these two technologies intersect and why this combination is more relevant now than ever. We'll explore using serverless architecture and React Native to handle data synchronization and enable offline functionality Let’s get started and see how to harness the collective strsngths of these technologies in your next project! 

Jump ahead:

*   [Prerequisites](#prerequisites)
*   [Understanding serverless architecture](#understanding-serverless-architecture)
*   [Benefits of serverless architecture in React Native](#benefits-serverless-architecture-react-native)
*   [React Native serverless tutorial](#react-native-serverless-tutorial)
    *   [Setting up a new React Native app](#setting-up-new-react-native-app)
    *   [Building the screens and components](#building-screens-components)
    *   [Creating the navigation](#creating-navigation)
    *   [Configuring serverless with React Native](#configuring-serverless-react-native)
    *   [Integrating cloud functions with React Native](#integrating-cloud-functions-react-native)
    *   [Building data management and synchronization](#building-data-management-synchronization)
    *   [Adding offline functionality](#adding-offline-functionality)

## Prerequisites

To get the most out of this tutorial, you’ll need:

*   An active [AWS Free Tier account](https://signin.aws.amazon.com/signin)
*   Node.js installed on your machine and a basic understanding of npm for package management
*   Familiarity creating and running React Native apps, using components like `View`, `Text`, and `StyleSheet`
*   A working development environment for React Native, including tools like the React Native CLI, Android Studio (for Android), and Xcode (for iOS)
*   [Amplify CLI](https://docs.amplify.aws/cli/) version 4.21.0 or later installed; you can install it by running: `npm install -g @aws-amplify/cli`
*   A basic understanding of cloud services, how they operate, and their benefits

The code for this tutorial is available on [GitHub](https://github.com/Claradev32/React-Native-Serverless) repository; feel free to clone it to follow along with the tutorial.

## Understanding serverless architecture

Before we dive deeper into this tutorial, let's demystify the [concept of serverless](https://blog.logrocket.com/going-serverless-node-js-apps/). "Serverless" isn't about ditching servers but reshaping our interaction with them. Like living in a serviced apartment, developers focus on living (coding), while cloud giants like AWS or Azure handle the maintenance (server management). 

The motto? Code, deploy, and let the cloud do the heavy lifting. 

At the heart of serverless is the reaction to events. Imagine a photo upload in a React Native app that springs a serverless function into action, resizing and storing the image. It's like a chef waiting for an order; they cook upon request and then patiently wait for the next. 

Forget paying for idle server time; with serverless, you're billed for active compute time only. It’s a pay-as-you-go model, like paying for a cab only when it’s moving, ensuring efficiency, scalability, and genuine cost savings.

## Benefits of serverless architecture in React Native <a name="benefits-serverless-architecture-react-native">

Serverless architecture offers several advantages, especially when paired with React Native:

*   **Scalability**: As a React Native application gains traction and more users, the last thing a developer wants to worry about is whether the backend can keep up. Serverless takes the guesswork out of scaling. It dynamically adjusts its resources, ensuring the app remains responsive, regardless of user count
*   **Cost efficiency**: Traditional server setups often come with overhead costs. There are charges for uptime, even if the resources aren't fully utilized. Serverless flips this model on its head. The mantra here is simple: "If it's not running, you're not paying”
*   **Rapid development**: Cutting down backend infrastructure chores means developers can hit the ground running. Iteration and deployment becomes significantly faster, leaving more time to focus on crafting an impeccable user experience
*   **Enhanced productivity**: Dealing with server maintenance can be a drain, both on time and energy. Serverless removes these operational burdens, resulting in fewer interruptions and a streamlined workflow and proving more time for innovating
*   **Flexibility**: The modular nature of serverless architecture is a developer's dream. Need to integrate third-party services or tap into cutting-edge technologies like machine learning? Serverless helps make React Native apps both robust and future-ready

## React Native serverless tutorial

Now that we have an understanding of what we are building, it's time to roll up our sleeves and get started. We’ll set up the React Native app, build the screens and components, and then create the navigation. Then, we’ll configure the serverless architecture, integrate the cloud functions, and add offline data management and synchronization.

### Setting up a new React Native app <a name="setting-up-new-react-native-app">

To start, we’ll initiate our React Native project. There are several approved methods for this, but for this tutorial, we'll harness the simplicity and efficiency of the [Expo](https://expo.dev/) package:

```bash
npx create-expo-app --template
```

The above command will prompt us to select the preferred template for the project. Let’s select the **Blank** option and wait while Expo installs the required packages to run the application: ![Using Expo Create React Native App](https://blog.logrocket.com/wp-content/uploads/2023/10/using-expo-create-react-native-app.png) Once the installation is completed, `cd` into the project folder and use the below commands to run the project:

```bash
- cd myserverlessapp
- npm run android
- npm run ios
- npm run web
```

![React Native Project](https://blog.logrocket.com/wp-content/uploads/2023/10/react-native-project.jpeg) Now that our React Native project is up and humming, let's familiarize ourselves with its folder structure:

```plaintext
📦myserverlessapp
┣ 📂src
 ┃ ┣ 📂components
 ┃ ┃ ┣ 📜BookList.js
 ┃ ┃ ┗ 📜Navbar.js
 ┃ ┣ 📂screens
 ┃ ┃ ┣ 📜AddBookScreen.js
 ┃ ┃ ┗ 📜HomeScreen.js
 ┣ 📜App.js
 ┣ 📜app.json
 ┣ 📜babel.config.js
 ┣ 📜metro.config.js
 ┣ 📜package-lock.json
 ┗ 📜package.json
```

Now, let’s install the following packages:

```bash
npm install @react-navigation/native-stack react-native-vector-icons
```

We’ll use the `@react-navigation/native-stack` and `react-native-vector-icons` packages to add navigation to our React Native project and add icons to our React Native Views, respectively.

### Building the screens and components <a name="building-screens-components">

For our tutorial, we'll build a digital bookstore. To kick things off, let’s structure our project's components and screens. 

In our project's root directory, we create a new folder named `components`. Within this folder, we create two component files: `BookList.js` and `Navbar.js`. 

In the same root directory, we set up another folder, `screens`. Inside that folder, we create two screen files: `AddBookScreen.js` and `HomeScreen.js`. 

Now, let’s add the below code snippets to the `components/Navbar.js` file:

```javascript
import { Button, StyleSheet, Text, View } from "react-native";
import Icon from "react-native-vector-icons/FontAwesome";
export default Navbar = ({ navigation }) => {
  return (
    <View style={styles.navbar}>
      <Text style={styles.navIcon}>
         <Icon name="shopping-cart" size={25} color="#fff" />
      </Text>
      <Text style={styles.storeName}>Bookstore</Text>
      <Button
        style={styles.addbtn}
        title="Add New"
        onPress={() => navigation.navigate("AddBook")}
      ></Button>
    </View>
  );
};
const styles = StyleSheet.create({
  navbar: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    padding: 10,
    backgroundColor: "rgb(34, 117, 150)",
    height: 50,
  },
  addbtn: {
    border: "1px solid white",
    color: "rgb(255, 255, 255)",
  },
});
```

In the above `Navbar` component, we create a view for our app’s navbar. We accept `navigation` props; we’ll set these up later in this tutorial. We also add an `onPress` event to the `Add New` button, enabling us to navigate to the `AddBook` screen by calling the `navigate` method from the `navigation` props. 

Next, let’s add the below code snippet to the `screens/HomeScreen.js` file:

```javascript
import React from "react";
import { View } from "react-native";
import Navbar from "../components/Navbar"; 
const HomeScreen = ({ navigation }) => {
  return (
    <View style={{ flex: 1 }}>
      <Navbar navigation={navigation} />
    </View>
  );
};
export default HomeScreen;
```

Now, we’ll update the `App.js` file to import and render the `HomeScreen`:

```javascript
import { StatusBar } from "expo-status-bar";
import { StyleSheet, View, SafeAreaView } from "react-native";
import HomeScreen from "./screens/HomeScreen";

export default function App() {
  return (
    <SafeAreaView>
      <View>
        <HomeScreen />
        <StatusBar style="auto" />
      </View>
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});
```

Next, let’s add the below code to the `AddBookScreen.js` file:

```javascript
import React, { useState } from "react";
import { View, TextInput, Button, StyleSheet } from "react-native";
import Navbar from "../components/Navbar";

const AddBookScreen = () => {
  const [name, setName] = useState("");
  const [price, setPrice] = useState("");
  const [author, setAuthor] = useState("");
  const [image, setImage] = useState("");
  return (
    <>
      <Navbar />
      <View style={styles.container}>
        <TextInput
          style={styles.input}
          placeholder="Name"
          value={name}
          onChangeText={setName}
          placeholderTextColor="#666"
        />
        <TextInput
          style={styles.input}
          placeholder="Price"
          value={price}
          onChangeText={setPrice}
          keyboardType="numeric"
          placeholderTextColor="#666"
        />
        <TextInput
          style={styles.input}
          placeholder="Author"
          value={author}
          onChangeText={setAuthor}
          placeholderTextColor="#666"
        />
        <TextInput
          style={styles.input}
          placeholder="Image URL (optional)"
          value={image}
          onChangeText={setImage}
          placeholderTextColor="#666"
        />
        <View style={styles.buttonContainer}>
          <Button title="Add Book" color="#34A853" />
        </View>
      </View>
    </>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 16,
    backgroundColor: "#F5F5F5",
  },
  input: {
    padding: 12,
    marginBottom: 10,
    backgroundColor: "#fff",
    borderRadius: 5,
    borderWidth: 1,
    borderColor: "#E5E5E5",
    fontSize: 16,
  },
  buttonContainer: {
    marginTop: 12,
  },
});
export default AddBookScreen;
```

In the above code snippets, we also imported the `Navbar` component. We need the navigation bar to render in all our app’s screens: ![React Native App With Navbar](https://blog.logrocket.com/wp-content/uploads/2023/10/react-native-app-with-navbar.jpeg)

### Creating the navigation <a name="creating-navigation">

Now that we’ve created the components and screen, let’s add navigation to our project so users can navigate the screens. To start, we’ll update the `App.js` file with the below code:

```javascript
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import AddBookScreen from "./screens/AddBookScreen";
import HomeScreen from "./screens/HomeScreen";
const Stack = createNativeStackNavigator();
const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator initialRouteName="AddBook">
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="AddBook" component={AddBookScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
};
export default App;
```

Here, we set up a native stack navigation structure for our React Native app using React Navigation. Within the navigation container, we define two screens, `Home` and `AddBook`, with `HomeScreen` and `AddBookScreen` components, respectively. We set `AddBook` as the initial screen to be displayed.

### Configuring serverless with React Native <a name="configuring-serverless-react-native">

Now we’re getting into the interesting part of this tutorial: configuring and integrating AWS cloud functions into the project. To get started, let’s use Amplify to initialize a project with the command below:

```bash
amplify init
```

This command prompts us to choose the configurations for the project. For this tutorial, we’ll select the following options: ![Using Amplify Configure Serverless React Native](https://blog.logrocket.com/wp-content/uploads/2023/10/using-amplify-configure-serverless-react-native.png) Now, let’s create a new serverless API, like so:

```bash
amplify add api
```

This command prompts us to select our preferred service, **GraphQL**, and our schema template: ![Amplify Selecting Preferred Service GraphQL](https://blog.logrocket.com/wp-content/uploads/2023/10/amplify-selecting-preferred-service-graphql.png) For the `Do` `you want to edit the schema now?` prompt, we select `Y` to open the schema file. Next, let’s update the schema file with following `Book` schema:

```javascript
type Book @model {
  id: ID!
  name: String!
  price: Float!
  author: String!
  image: String
}
```

Now, let’s deploy the serverless function:

```bash
amplify push
```

During the deployment process, we’re prompted to answer a number of questions. The answer that we’ll select for each prompt is provided below:

*   `Do you want to generate code for your newly created GraphQL API?` select **Yes**
*   `Choose the code generation language target` select **JavaScript**
*   `Enter the file name pattern of GraphQL` `queries, mutations,` `and subscriptions src/graphql/**/*.js` click **Enter**
*   `Do you want to generate/update all possible GraphQL operations - queries, mutations,` `and subscriptions?` select **Y**
*   `Enter maximum statement depth (increase from default if your schema is deeply nested)` enter **2**

When deployment is completed, a **GraphQL endpoint** and **GraphQL API KEY** are generated for our project and a `graphql` folder is created within the `src` folder with the following files:

```plaintext
📦graphql
 ┣ 📜mutations.js
 ┣ 📜queries.js
 ┣ 📜schema.json
 ┣ 📜subscriptions.js
 ┗ 📜types.js
```

Here are some additional details about these files:

*   `mutations.js`: Contains [GraphQL mutations](https://blog.logrocket.com/defining-types-for-your-graphql-api/) that are used to modify or create data on the server
*   `queries.js`: Holds GraphQL queries that are used to retrieve data from the server
*   `schema.json`: Typically contains the schema for the GraphQL API. It defines the types of data that can be queried and the structure of these queries
*   `subscriptions.js`: Contains GraphQL subscription queries that enable real-time data updates by subscribing to specific events or changes on the server
*   `types.js`: Defines custom GraphQL types or type-related logic for the application

### Integrating cloud functions with React Native <a name="integrating-cloud-functions-react-native">

At this point, our serverless GraphQL API functions are ready. Let’s integrate them into our React Native project. 

First, we install the following dependencies:

```bash
npm install aws-amplify @react-native-async-storage/async-storage @react-navigation/native-stack
```

Then, we update the root `App.js` file to import and configure AWS `Amplify` in our project:

```javascript
...
import awsExport from "./src/aws-exports"
import { Amplify } from 'aws-amplify'
Amplify.configure(awsExport);
...
```

Here, we import the `aws-exports` file, which is generated by `Amplify` when generating our GraphQL API. This file contains the configurations and keys that allow our React Native application to interact seamlessly with AWS serverless functions and other resources. 

Next, add the following code snippets to the `components/BookList.js` file to fetch the books from our serverless function and render them in our application:

```javascript
import { StyleSheet, FlatList, View, Image, Text, Button } from "react-native";
import { useEffect, useState } from "react";
import { API, graphqlOperation } from "aws-amplify";
import { listBooks } from "../src/graphql/queries";
import { onCreateBook } from '../src/graphql/subscriptions';

export default BookList = () => {
  const [books, setBooks] = useState([]);
  useEffect(() => {
    const fetchInitialBooks = async () => {
      try {
        const bookData = await API.graphql(graphqlOperation(listBooks));
        const fetchedBooks = bookData.data.listBooks.items;
        setBooks(fetchedBooks);
      } catch (error) {
        console.error("Error fetching books:", error);
      }
    };
    fetchInitialBooks();
  }, []);
  // Setting up the subscription for new books
  useEffect(() => {
    const subscription = API.graphql(graphqlOperation(onCreateBook)).subscribe({
      next: (bookData) => {
         setBooks(prevBooks => [...prevBooks, bookData.value.data.onCreateBook]);
      }
    });
    return () => subscription.unsubscribe();  // Cleanup subscription on unmount
  }, []);
  return (
    <FlatList
      data={books}
      renderItem={({ item }) => (
        <View style={styles.bookItem}>
          <Image source={{ uri: item.image }} style={styles.bookImage} />
          <View style={styles.bookDetails}>
            <Text style={styles.bookTitle}>{item.name}</Text>
            <Text style={styles.bookAuthor}>{item.author}</Text>
            <Text style={styles.bookPrice}>${item.price}</Text>
            <Button title="Buy Now" onPress={() => {}} />
          </View>
        </View>
      )}
      keyExtractor={(item) => item.id}
    />
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#F5F5F5",
  },
  storeName: {
    fontSize: 18,
    color: "#FFF",
    fontWeight: "bold",
  },
  bookItem: {
    flexDirection: "row",
    padding: 10,
    borderBottomWidth: 1,
    borderColor: "#E0E0E0",
    backgroundColor: "#FFF",
  },
  bookImage: {
    width: 80,
    height: 110,
  },
  bookDetails: {
    marginLeft: 10,
    flex: 1,
    justifyContent: "space-between",
  },
  bookTitle: {
    fontSize: 16,
    fontWeight: "bold",
  },
  bookAuthor: {
    color: "#666",
  },
  bookPrice: {
    color: "#E91E63",
    marginBottom: 10,
  },
});
```

There are two primary components in the `BookList` component. `FlatList` is responsible for rendering the list of books, and several sub-components like `View`, `Image`, and `Text`. `Button` is used to display and interact with book details. 

In the above code, the first `useEffect` Hook fetches the initial list of books when the component loads by making an API call with GraphQL. The second `useEffect` Hook sets up a real-time subscription to listen for new book creations. When a new book is created, it updates the list of books with the newly added book data, ensuring that the component remains synchronized with the backend data. 

Now let’s update the `AddBook.js` screen to allow users to add new books to the store. To do this, we’ll create a handler function that makes an API call to our GraphQL serverless function to create a new book:

```javascript
...
import { API, graphqlOperation } from "aws-amplify";
import { createBook } from "../src/graphql/mutations";

const AddBookScreen = () => {
...
 const addBook = async () => {
    try {
      if (name === "" || price === "" || author === "") return;
      const input = {
        name,
        price: parseFloat(price),
        author,
        image,
      };
      const result = await API.graphql(graphqlOperation(createBook, { input }));
      if (result.data.createBook) {
        alert("Book added successfully!");
        setName("");
        setPrice("");
        setAuthor("");
        setImage("");
      } else {
        alert("Error saving book. Please try again.");
      }
    } catch (error) {
      console.error("Error saving book:", error);
      alert("Error saving book. Please try again.");
    }
  };
...
return (
    ...
    <View style={styles.buttonContainer}>
          <Button title="Add Book" onPress={addBook} color="#34A853" />
    </View>
    ...
)
```

Now we can add new books to our application!

### Building data management and synchronization <a name="building-data-management-synchronization">

The AWS [Amplify DataStore](https://docs.amplify.aws/lib/datastore/getting-started/q/platform/js/#writing-to-the-database) offers a streamlined approach to offline data management and synchronization. Instead of directly querying the GraphQL API, we can seamlessly access data offline by querying the local DataStore. This allows our application to function without an internet connection. 

When our app regains internet connectivity, any changes made to the local data, whether offline or online, are automatically synchronized with the backend through [AppSync](https://docs.aws.amazon.com/appsync/latest/devguide/what-is-appsync.html). This synchronization process ensures that our data remains up to date and consistent across devices. 

In the event of conflicting changes, AppSync employs our defined conflict resolution strategy, such as `AUTOMERGE` or `OPTIMISTIC_CONCURRENCY`, to intelligently resolve discrepancies and maintain data integrity. This robust synchronization mechanism simplifies data management and enhances the offline user experience while preserving data consistency.

### Adding offline functionality

To add offline functionality to our React Native application, we need to generate a model based on our [GraphQL schema](https://blog.logrocket.com/building-android-app-graphql/#define-graphql-schema-queries), like so:

```bash
amplify codegen models
```

The above command creates a models folder in the `src` directory and generates the following files:

```plaintext
📦models
 ┣ 📜index.d.ts
 ┣ 📜index.js
 ┣ 📜schema.d.ts
 ┗ 📜schema.js
```

Here are some additional details about each of the files:

*   `index.d.ts`: This TypeScript declaration file provides type definitions for the models generated from our GraphQL schema. It allows us to work with these models in a type-safe manner when writing TypeScript code
*   `index.js`: This JavaScript file exports generated models, making them accessible within our JavaScript code. We can import and use these models to interact with our GraphQL API and perform CRUD (create, read, update, delete) operations on our data
*   `schema.d.ts`: This TypeScript declaration file provides type definitions specifically for our GraphQL schema. It defines the structure of our GraphQL types, queries, mutations, and subscriptions, enabling type checking and autocompletion when working with GraphQL operations in TypeScript
*   `schema.js`: This JavaScript file exports our GraphQL schema definition. It contains the schema that defines the structure of our GraphQL API, including the types, queries, mutations, and subscriptions. This file is crucial for setting up the GraphQL server and client

These files are essential for developing our React Native application with GraphQL data access and synchronization, ensuring type safety and consistency throughout the codebase. 

Next, let’s update the `fetchInitialBooks` handler and the `useEffect` Hooks in the `BookList.js` component to fetch the data from the Amplify DataStore:

```javascript
...
import { DataStore, Predicates } from "aws-amplify";
import { Book } from "../src/models";
...
export default BookList = () => {
    ...
    const fetchInitialBooks = async () => {
        const items = await DataStore.query(Book, Predicates.ALL);
        setBooks(items);
      };
      useEffect(async () => {
            await this.loadBooks();
            DataStore.observe(Book).subscribe(fetchInitialBooks);
      }, []);
...
}
```

Here we use the AWS Amplify library to interact with a local DataStore and retrieve a list of books. When the component mounts, it uses the `DataStore.query` method with a `Predicates.ALL` filter to fetch all records of the `Book` model. It also sets up a subscription to observe changes in the `Book` model, ensuring that the list of books is updated whenever a new `Book` is created or modified. 

Now let’s modify the `addBook` handler in the `AddBookScreen.js` screen to save new books to the `DataStore`:

```javascript
 const addBook = async () => {
    try {
      if (name === "" || price === "" || author === "") return;
      const input = {
        name,
        price: parseFloat(price),
        author,
        image,
      };
      await DataStore.save(new Book(input));
      setName("");
      setPrice("");
      setAuthor("");
      setImage("");
    } catch (error) {
      console.error("Error saving book:", error);
      alert("Error saving book. Please try again.");
    }
  };
```

We have successfully migrated our React Native application to work both online and offline.

## Conclusion

Implementing serverless architecture for React Native applications offers a slew of advantages, including the ability to scale easily, sidestep the complexities of infrastructure management, and keep operational costs in check. 

Serverless technology helps React Native developers create more robust, adaptable, and efficient mobile applications that meet the demands of today's dynamic digital landscape. By seamlessly integrating cloud functions from providers like [AWS Lambda](https://blog.logrocket.com/building-serverless-app-typescript/#what-aws-lambda) or [Azure Functions](https://azure.microsoft.com/en-us/products/functions), developers can tap into a world of possibilities to enrich their mobile apps. 

In this article, we explored effective strategies for handling data synchronization and enabling offline functionality, both of which are important for delivering a seamless user experience.

---

## [LogRocket](https://lp.logrocket.com/blg/react-native-signup): Instantly recreate issues in your React Native apps.

[![LogRocket Signup](https://blog.logrocket.com/wp-content/uploads/2021/10/react-native-plug_v2-2.png)](https://lp.logrocket.com/blg/react-native-signup)

[LogRocket](https://lp.logrocket.com/blg/react-native-signup) is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — [try LogRocket for free](https://lp.logrocket.com/blg/react-native-signup).