Connecting all the sources that we need to get data for our frontend can sometimes be more complex than building the frontend. Your application may use third-party services for things like authentication, APIs like a headless CMS for content, while storing data in a data API service like Fauna. GraphQL combined with StepZen makes it easy to bring all the pieces together into a single, consolidated API endpoint.

In this post, I'll demonstrate how to build a login screen for a mobile app built using React Native with Google Sign-In, FaunaDB, and StepZen. If you'd like to see a live-code demo video looking at the project covered in this blog posts, check it out on YouTube. You can also see the code and clone the repository on GitHub.

Getting Set Up

Let's get you set up with the accounts and tools you'll need to build this project.

Setting Up StepZen

Create a StepZen account first, in order to get your API and admin keys (click the "Dashboard" button on the top right once you log in to see where they are).

Next, you'll need the StepZen CLI in order to deploy and test your StepZen GraphQL endpoint. To install the CLI, follow the instructions in the docs.

Setting up the Expo React Native App

Expo is an easy to use platform for building React Native apps that we will use for this project. First, install the CLI so we can jump start our app.

npm install --global expo-cli

Initialize the new app. When prompted, select tabs (TypeScript), several example screens and tabs using react-navigation and TypeScript. This will install many dependencies that are designed to improve the React Native navigation experience.

We'll need to

expo init faunadb-login-demo
cd faunadb-login-demo

Set Up a Fauna Database

Go to Fauna.com and click Sign Up. You will be taken to the Fauna dashboard.

Create a new database in Fauna named stepzen-fauna. Use the "Classic" region group and be sure to check the "Use demo data" option.

Once the database is created, open the GraphQL menu option within the Fauna Dashboard. You'll need the HTTP authorization header at the bottom of the Playground to use in your config.yaml file.

HTTP Headers

The schema below defines the user information we plan to store in Fauna. Copy the schema and save it as a .graphql file. Upload the file to the Fauna GraphQL Playground by selecting Merge Schema. This will allow us to write the users that log in to our application to the FaunaDB User Collection.

type User {
  email: String!
  familyName: String!
  givenName: String!
  id: String!
  name: String!
  photoUrl: String!
}

input UserInput {
  email: String!
  familyName: String!
  givenName: String!
  id: String!
  name: String!
  photoUrl: String!
}

type MutationUser {
  createUser(data: UserInput!): User!
}

type QueryUser {
  findUserByID(id: ID!): User
}

Now that we have Fauna set up, we need to tell our StepZen API how to connect to it using our Fauna API key. On your local machine, go to the root of your faunadb-login-demo project and create a StepZen folder.

mkdir stepzen && cd stepzen

Create a config.yaml file in the folder with the following contents (replacing Basic MY_FAUNA_KEY with the information you copied from the GraphQL Playground in the Fauna dashboard).

configurationset:
  - configuration:
      name: fauna_config
      Authorization: Basic MY_FAUNA_KEY

Now that we're done with the setup, we need to create our GraphQL schema code that builds our API within StepZen.

Creating Google Sign-In Client IDs

First, create a Google Cloud account. There are free trials available.

Once created, go to "Credentials" in the Google Cloud dashboard and create two oAuth clientIds for Android and iOS applications by clicking "Create Credentials" and then "OAuth client ID". Copy and save these. We'll need then when we are creating the React Native app and we install the dependency expo-google-app-auth.

Creating a FaunaDB Mutation in the StepZen Schema

Next, create a file named fauna.graphql within the stepzen directory containing your config.yaml and copy the code below into it.

The schema code for our GraphQL endpoint looks very similar to the schema we uploaded to Fauna. The key difference here is in the query and mutation code. Notice the @graphql? This is a StepZen custom directive for connecting a StepZen API to an existing GraphQL API. In our simple example, the two are almost identical, but, in most cases, we'd be connecting this schema with data coming from other sources like REST APIs or databases – and StepZen makes that part easy!

type User {
  email: String!
  familyName: String!
  givenName: String!
  id: String!
  name: String!
  photoUrl: String!
}

input UserInput {
  email: String!
  familyName: String!
  givenName: String!
  id: String!
  name: String!
  photoUrl: String!
}

type Mutation {
  createUser(data: UserInput!): User!
    @graphql(
      endpoint: "https://graphql.fauna.com/graphql"
      configuration: "fauna_config"
    )
}

type Query {
  findUserByID(id: ID!): User
    @graphql(
      endpoint: "https://graphql.fauna.com/graphql"
      configuration: "fauna_config"
    )
}

Note that the endpoint https://graphql.fauna.com/graphql may change based on the location you selected. If you selected classic, then that should be endpoint used. Be sure to add the correct endpoint from your FaunaDB GraphQL Explorer.

Add the fauna.graphql schema to an index.graphql file in the same stepzen directory.

schema @sdl(files: ["fauna.graphql"]) {
  query: Query
}

Our StepZen folder should have the file structure below.

.
├── stepzen
    ├── index.graphql
    ├── fauna.graphql
    └── config.yaml

Using the StepZen CLI, run the following command.

stepzen start

The CLI will suggest a name for the endpoint (in the format FOLDER_NAME/ENDPOINT_NAME) but we can specify any name we want. Let's use demo/native-login. Note that the CLI will generate a file, stepzen.config.json, that tells StepZen the name the API endpoint so that it can skip this step on subsequent requests.

Creating our React Native App

First, in the root of our project, let's install some dependencies. You can use npm or yarn.

yarn add @apollo/client graphql expo-google-app-auth expo-blur react-native-reanimated

In our App.tsx file, we need to use the @apollo/client to add our StepZen endpoint and admin key to the frontend project.

import {
  ApolloClient,
  ApolloProvider,
  createHttpLink,
  InMemoryCache,
} from '@apollo/client'
import 'react-native-gesture-handler'
import { StatusBar } from 'expo-status-bar'
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'

import useCachedResources from './hooks/useCachedResources'
import useColorScheme from './hooks/useColorScheme'
import Navigation from './navigation'

const client = new ApolloClient({
  link: createHttpLink({
    credentials: 'same-origin',
    headers: {
      Authorization: `Apikey {{your_key}}`,
    },
    uri: 'https://youraccountname.stepzen.net/demo/native-login/__graphql',
  }),
  cache: new InMemoryCache(),
})

export default function App() {
  const isLoadingComplete = useCachedResources()
  const colorScheme = useColorScheme()

  if (!isLoadingComplete) {
    return null
  } else {
    return (
      <ApolloProvider client={client}>
        <SafeAreaProvider>
          <Navigation colorScheme={colorScheme} />
          <StatusBar />
        </SafeAreaProvider>
      </ApolloProvider>
    )
  }
}

It's that simple to connect a StepZen API to a React Native frontend project. FaunaDB in our StepZen API is ready to be used in the React Native app. Running expo start at this point will display "Tab One" and "Tab Two" from the ./screens folder.

In the App.tsx, we are going to be navigating between the LoginScreen and UserScreen which we'll be creating later, so we need to add a <NavigationContainer> for both pages.

export default function App() {
  const isLoadingComplete = useCachedResources()

  if (!isLoadingComplete) {
    return null
  } else {
    return (
      <ApolloProvider client={client}>
        <SafeAreaProvider>
          <NavigationContainer>
            <Stack.Navigator initialRouteName="Login">
              <Stack.Screen
                name=" "
                component={LoginScreen}
                options={{
                  headerTransparent: true,
                  headerTitle: 'Login Demo',
                  headerBackground: () => (
                    <BlurView tint="light" intensity={100} />
                  ),
                }}
              />
              <Stack.Screen
                name="Home"
                component={UserScreen}
                options={{
                  title: 'Golf-Austin',
                  headerTransparent: true,
                  headerTitleAlign: 'center',
                }}
              />
            </Stack.Navigator>
          </NavigationContainer>
        </SafeAreaProvider>
      </ApolloProvider>
    )
  }
}

And update the imported packages to reflect the <NavigationContainer>.

import {
  ApolloClient,
  ApolloProvider,
  createHttpLink,
  InMemoryCache,
} from '@apollo/client'
import { BlurView } from 'expo-blur'
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import useCachedResources from './hooks/useCachedResources'
import { createStackNavigator } from '@react-navigation/stack'
import { NavigationContainer } from '@react-navigation/native'

// The next steps will create these screens
import LoginScreen from './screens/LoginScreen'
import UserScreen from './screens/UserScreen'

const Stack = createStackNavigator()

Moving on to the individual screens of our app, let's rename TabOneScreen.tsx to LoginScreen.tsx and add the following imports in the file.

import React, { useState } from 'react'
import { StyleSheet, View, Button, ImageBackground, Image } from 'react-native'
import * as Google from 'expo-google-app-auth'
import { useMutation } from '@apollo/client'
import { CREATE_USER } from '../mutations/create-user'

Everything being imported is a dependency package except for CREATE_USER, which is the FaunaDB mutation that will create our user. Let's go create that mutation file and add the following mutation.

In the root of your project

mkdir mutations

Create file create-user.tsx

import { gql } from 'graphql-tag'

export const CREATE_USER = gql`
  mutation CreateUser(
    $name: String!
    $photoUrl: String!
    $email: String!
    $familyName: String!
    $givenName: String!
    $id: String!
  ) {
    createUser(
      data: {
        name: $name
        photoUrl: $photoUrl
        email: $email
        familyName: $familyName
        givenName: $givenName
        id: $id
      }
    ) {
      email
      givenName
      familyName
      name
      photoUrl
    }
  }
`

The mutation is ready to be used in the log in process. Referring back to the client IDs created earlier in Google Cloud, we can write out our LoginScreen as below. Add your keys in the Google.logInAsync.

const LoginScreen = ({ navigation }: any) => {
  const [createUser, { data }] = useMutation(CREATE_USER)

  const [user, setUser] = useState(null)
  const [accessToken, setAccessToken] = useState()
  const signInAsync = async () => {
    try {
      const { type, accessToken, user }: any = await Google.logInAsync({
        iosClientId: `{{ add key }}`,
        androidClientId: `{{ add key }}`,
      })

      if (type === 'success') {
        setUser(user)
        setAccessToken(accessToken)
        createUser({
          variables: {
            name: user.name,
            email: user.email,
            familyName: user.familyName,
            givenName: user.givenName,
            id: user.id,
            photoUrl: user.photoUrl,
          },
        })
        navigation.navigate('Home', { user })
      }
    } catch (error) {
      console.log('LoginScreen.js 19 | error with login', error)
    }
  }

  const goBack = () => {
    navigation.navigate('Home', { user })
  }

  const image = {
    uri: 'https://images.unsplash.com/photo-1587174486073-ae5e5cff23aa?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=750&q=80',
  }

  return (
    <>
      <ImageBackground source={image} style={styles.image} blurRadius={0.3}>
        <View style={styles.buttonContainer}>
          <Image
            style={styles.tinyLogo}
            source={{
              uri: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/120px-Google_%22G%22_Logo.svg.png',
            }}
          />
          {!user && <Button title="Login with Google" onPress={signInAsync} />}
          {user && <Button title="Go Back" onPress={goBack} />}
        </View>
      </ImageBackground>
    </>
  )
}

export default LoginScreen

Then add some styling at the end of the file.

const styles = StyleSheet.create({
  buttonContainer: {
    flex: 1,
    alignItems: 'center',
    padding: 60,
    backgroundColor: 'rgba(100,200,100,0.2)',
  },
  image: {
    flex: 1,
    resizeMode: 'cover',
    justifyContent: 'center',
  },
  tinyLogo: {
    width: 50,
    height: 50,
    marginBottom: 20,
    marginTop: 50,
  },
  profilePic: {
    flex: 0,
    width: 90,
    height: 90,
    // marginTop: 0,
    borderRadius: 250,
  },
})

Let's break it down what's happening in this function and then run the Expo app.

OnPress of the Login Button, <Button title="Login with Google" onPress={signInAsync} />, The signInAsync takes the client ID from your Google Cloud account and returns a user that we assign to the state, setUser(user) and the same goes for assigning the accessToken.

The StepZen mutation with FaunaDB occurs when we assign all the variables of user to createUser, the mutation that is written in our import folder, import { CREATE_USER } from "../mutations/create-user".

const [createUser, { data }] = useMutation(CREATE_USER)

const [user, setUser] = useState(null)
const [accessToken, setAccessToken] = useState()

const signInAsync = async () => {
  try {
    const { type, accessToken, user }: any = await Google.logInAsync({
      iosClientId: `{{ add key }}`,
      androidClientId: `{{ add key }}`,
    })

    if (type === 'success') {
      setUser(user)
      setAccessToken(accessToken)
      createUser({
        variables: {
          name: user.name,
          email: user.email,
          familyName: user.familyName,
          givenName: user.givenName,
          id: user.id,
          photoUrl: user.photoUrl,
        },
      })
      navigation.navigate('Home', { user })
    }
  } catch (error) {
    console.log('LoginScreen.js 19 | error with login', error)
  }
}

Here is an example of the fields returned from Google Sign-In that we assign as variables to the StepZen mutation, createUser.

{
  "email": "sam@stepzen.com",
  "familyName": "Hill",
  "givenName": "Samuel",
  "id": "1234",
  "name": "Samuel Hill",
  "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gi3fCa_eM-k0vLZK5z1gChGA0RS_3C-OhyIp8ml=s96-c"
}

We still need to do is create the UserScreen.tsx so the user can see they successfully logged in. Rename the TabTwoScreen.tsx to UserScreen.tsx and copy the following code.

import React from 'react'
import { StyleSheet, Text, View, ImageBackground, Image } from 'react-native'

const UserScreen = ({ route }: any) => {
  const { user } = route.params

  const backgroundImage = {
    uri: 'https://images.unsplash.com/photo-1595841055318-943e15fbbe80?ixid=MnwxMjA3fDB8MHxzZWFyY2h8MTgzfHxnb2xmfGVufDB8fDB8fA%3D%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=400&q=60',
  }

  return (
    <>
      <ImageBackground source={backgroundImage} style={styles.image}>
        <View style={styles.container}>
          <View>
            <Image
              style={styles.profilePic}
              source={{
                uri: `${user.photoUrl}`,
              }}
            />
          </View>
          <Text style={styles.banner}>Welcome {user.name}</Text>
        </View>
      </ImageBackground>
    </>
  )
}

export default UserScreen

Let's add some styling at the end of the file.

const styles = StyleSheet.create({
  container: {
    flex: 1,
    flexDirection: 'column',
    justifyContent: 'space-between',
    alignItems: 'center',
    textAlign: 'center',
    height: '100%',
  },
  image: {
    flex: 1,
    resizeMode: 'contain',
    justifyContent: 'center',
  },
  profilePic: {
    flex: 0,
    width: 90,
    height: 90,
    marginTop: 80,
    borderRadius: 250,
  },
  banner: {
    flex: 1,
    fontSize: 38,
  },
})

Heading back over to our LoginScreen, we can now see two functions that we put in earlier should work to perfection when we have a successful login.

This navigate function will redirect the user on successful login.

const signInAsync = async () => {
  try {
    if (type === "success")
      navigation.navigate("Home", { user });
  }
};

The goBack function runs when pressing the <Button title="Go Back" onPress={goBack} button on the Login page.

const goBack = () => {
  navigation.navigate('Home', { user })
}

Run the App

Now with a fully created App.tsx with our StepZen endpoint and apikey, a LoginScreen with our Google client IDs, and a UserScreen for successful logins, we can run the app!

In your terminal, run the following command.

expo start

Download the expo app on your phone, and scan the QR code provided at http://localhost:19002/ on your computer.

Select the "Login with Google" button on the app, and when prompted, login with your Google credentials. On login, you should be redirected to the UserScreen, and the FaunaDB User Collection should be updated with the user details!

Here is an example of a user added to FaunaDB.

{
  "ref": Ref(Collection("User"), "306547471891300420"),
  "ts": 1628605300720000,
  "data": {
    "familyName": "Hill",
    "name": "Sam Hill",
    "givenName": "Sam",
    "photoUrl": "https://lh3.googleusercontent.com/a-/AOh14Gi3fCa_eM-k0vLZK5z1gChGA0RS_3C-OhyIp8ml=s96-c",
    "email": "sam@stepzen.com",
    "id": "1234"
  }
}

Now you successfully have a Google sign-in with a database that can store all the users. This demonstrates the ease of adding a FaunaDB to your single StepZen endpoint that can be combined with any other data source. Writing the data of the user to more than one database or API can easily be added to this configuration in the StepZen schema.

Where To Go From Here

To learn more on how to use FaunaDB and mutations, check out our GraphQL mutation docs.

The StepZen docs also hold information on connecting other backends to your endpoint like REST APIs and databases like PostGresQL.

If you've got more questions, hit us up on Discord, we'd love to chat.