The guide to create (offline) multi tenant apps with Expo and AWS Amplify
Tags: aws, expo, multi, tenant
Published: 2020-11-04 19:06:22

*Cover photo by David Watkis on Unsplash*

#Multi tenant
A tenant is a group of users which have the same access rights. With multi tenant you design your application in a way that different tenants can make use of the same software without interfering each other and be in control about their own data assets without the risks that others can access this. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/ad9zxt0ugfxbc5hrxsa4.png)

#Software as a Service (SaaS)
There are different type of apps that we all use in our daily lives. One type of applications are Software as a Service (SaaS) applications. The characteristics of SaaS is that the hardware and software is abstracted away from you. You don't need to manage this and **only consume the features** that are provided. You make free use of these apps or you take a subscription model where you pay a monthly / yearly / whatever fee. 

These applications are designed in a way that there is a software version running where multiple users can make use of in a secure and isolated way.

##Slack
You can see Slack as an example of a Multi tenant Saas Solution. The software and hardware is running somewhere (in their private networks or public cloud). As a company you are able to get a subscription and add the users of your company. Your users can work together and share data in slack which is not accessible to other tenants. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sqf33834ni8780r4qj0n.png) 

#Purpose of this guide
In this guide I will explain how you can set up a multi tenant application with AWS Amplify like Slack. 

The power of AWS Amplify is that it connects to different front end frameworks. So, as soon as you have set up this backend you are able to build your front end application on top of the multi tenant backend. 

We will build an Expo app. This is easy to set up and test your app.

There are different possibilities of a multi tenant implementation:

1) AWS account per tenant
2) Same AWS account and different DynamoDB tables per tenant
3) Same AWS account and one table for all tenants

I will focus on **option 3** because this is the less complex implementation and still secure.

To achieve the implementation there are again different options:

#### The ideal implementation

The ideal implementation is the one where the server(less) side will check if the user is part of the tenant and it is in the right cognito group. This can be done via the VTL templates in AWS Appsync. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pb1djuzeu47yop14u9ht.png)


Let's assume we will create this model:

```javascript
type channel
  @model
  @key(
    name: "channelByTenant"
    fields: ["tenant"]
    queryField: "channelByTenant"
  )
  @auth(
    rules: [
      { allow: owner, ownerField: "owner" }
      { allow: owner, ownerField: "tenant", identityClaim: "custom:tenant" }
      { allow: groups, groups: ["user"], operations: [read] }
      {
        allow: groups
        groups: ["editors"]
        operations: [create, read, update, delete]
      }
    ]
  ) {
  id: ID!
  name: String!
  messages: [message] @connection(keyName: "messageByChannel", fields: ["id"])
  tenant: ID!
  owner: ID!
}
```

When create a schema like above many templates are generated for you out of the box. As you can see in this schema I apply 4 auth rules. 

The above model will allow owner, tenants and users in the group editors to **create, read, update and delete** and the users in the group users to **read**. 

The problem with the generated templates is that it is applying OR conditions on the rules, like:

```
#if( !($isStaticGroupAuthorized == true || $isDynamicGroupAuthorized == true || $isOwnerAuthorized == true) )
    $util.unauthorized()
  #end
```

which means

```
$isStaticGroupAuthorized => check cognito groups 
$isDynamicGroupAuthorized => check owners and tenants
$isOwnerAuthorized => not applicable now in the model
```

but we want this multi tenant validation:

```
// if is not part of tenant, set unauthorized
if( tenant !== custom.tenantid ) {
   unauthorized()
} 

// ELSE if is not the owner or part of the groups, set unauthorized
if( user !== owner || user is not in group users or editors) {
    unauthorized()
}

```

Of course it is possible to change all the VTL templates, but for one model there are many templates created (see next screenshot) and if you are going to adjust them manually then you will end up with maintainability issues. Imaging if you have more models in your schema.

Each time you adjust your model then your templates are generated again and you have to apply the adjustments again. 

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7pz2rw3u3trc3m7v0ufs.png)

**Github issue**
>There is an issue created where this will be solved: 
https://github.com/aws-amplify/amplify-cli/issues/317

If this is implemented by the Amplify or Appsync team we can maintain the rules and tenant set up from the schema and based on that the templates will be generated. 

Our model will look like this then:

```
@auth(rules: [
    { and: [
        { allow: owner, ownerField: "tenant", identityClaim: "custom:tenant" } 
        { or: [
            { allow: owner, ownerField: "owner" }   
            { allow: groups, groups: ["user"], operations: [read] }
            { allow: groups groups: ["editors"] operations: [create, read, update, delete] }
        ]}
    ]}
])
```

####Ok ... what to do now?
I am very happy that since two weeks ago datastore is offering selective sync. Which means that we only sync data between the cloud and our app that is part of the tenants. The other tenant data we will leave in the cloud and therefor it is not available to the clients. 

#AWS Services
I use different AWS services to accomplish this Multi tenant set up. 

###AWS Cognito
When a tenant creates an account will it be stored in Cognito. This is a service from AWS that handles user and access management for you. Cognito will create the user with an unique user id. This user id will be the **tenant ID**. 

###AWS Amplify
AWS Amplify is a library that acts as the glue between front end tools and the backend on AWS. You can use AWS Amplify for setting up your API's, storage, authentication and authorization, database, data store and more. Via AWS Amplify we will design who can access what data and deploy the cloud backend.  

A great feature of AWS Amplify is datastore. This is a capability that enables offline data access, manage the syncing of data between clients and cloud and take care of syncing conflicts.

###AWS Appsync
AWS AppSync is a fully managed service that makes it easy to develop GraphQL APIs by handling the heavy lifting of securely connecting to data sources like AWS DynamoDB, Lambda, and more. 

When you make an API requests the logic of the request is in the resolvers. A resolver has a request mapping and response mapping template. Via the response mapping template we will apply logic that checks if the user is in the right auth group to access the data and if he is part of the right tenant. 

###AWS DynamoDB
DynamoDB is a NoSQL key-value pair database. It is database that is completely managed for you and that will be deployed via the Appsync schema. 

## Getting Started
We are going to create a **mini Slack app**. As a tenant you can add users and channels. The users in your tenant can create messages, only, in the channels which are part of the tenant. 

### Set up AWS Amplify
We first need to have the AWS Amplify CLI installed. The Amplify CLI is a command line tool that allows you to create & deploy various AWS services.

To install the CLI, we'll run the following command:

```
$ npm install -g @aws-amplify/cli

```

Next, we'll configure the CLI with a user from our AWS account:

```
$ amplify configure

```

>For a video walkthrough of the process of configuring the CLI, [click](https://www.youtube.com/watch?v=fWbM5DLh25U)

### Set up React Native

First, we'll create the React Native application we'll be working with.
Run these commands in the **root dir** (so not in your reactJS dir)


```
$ npx expo init multitenantAPP

> Choose a template: blank

$ cd multitenantAPP

$ npm install aws-amplify aws-amplify-react-native @react-native-community/netinfo

```


### Init your Amplify Project

Now we can initialize a new Amplify project from within the root of our React Native application:

```
$ amplify init
```

Here we'll be guided through a series of steps:
* Enter a name for the project: **multitenantapp** (or your preferred project name)
* Enter a name for the environment: **dev** (use this name, because we will reference to it)
* Choose your default editor: **Visual Studio Code** (or your text editor)
* Choose the type of app that you're building: **javascript**
* What javascript framework are you using: **react-native**
* Source Directory Path: **src**
* Distribution Directory Path: **build**
* Build Command: **npm run-script build**
* Start Command: **npm run-script start**
* Do you want to use an AWS profile? **Y**
* Please choose the profile you want to use: **YOUR_USER_PROFILE**
* 
Now, our Amplify project has been created & we can move on to the next steps.

### Add Auth to your project

```
Amplify add auth
```

Follow these steps:
* Do you want to use the default authentication and security configuration? **Manual configuration**
* User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features for images or other content, Analytics, and more)
* Please provide a friendly name for your resource that will be used to label this category in the project: **Enter**
* Please enter a name for your identity pool: **Enter**
* Allow unauthenticated logins? (Provides scoped down permissions that you can control via AWS IAM) **No** 
* Do you want to enable 3rd party authentication providers in your identity pool? **No**
* Please provide a name for your user pool: **<Enter>**
* How do you want users to be able to sign in? **Username**
* Do you want to add User Pool Groups? **Yes**
* Provide a name for your user pool group: **users**
* Do you want to add another User Pool Group: **Y**
* Provide a name for your user pool group: editors
* Do you want to add another User Pool Group: **N**
* Sort the user pool groups in order of preference: **Enter**
* Do you want to add an admin queries API? **No**
* Multifactor authentication (MFA) user login options: **OFF**
* Email based user registration/forgot password: **Enabled(Requires per-user email entry at registration)**
* Please specify an email verification subject: **Enter**
* Please specify an email verification message: **Enter**
* Do you want to override the default password policy for this User Pool? **Enter**
* What attributes are required for signing up? **Select email and name by pressing space bar and press enter when finished**
* Specify the app's refresh token expiration period (in days): **Enter**
* Do you want to specify the user attributes this app can read and write? **Y**
* Do you want to enable any of the following capabilities? **Enter**
* Do you want to enable any of the following capabilities? **Select Add user to group**
* Do you want to use an OAuth flow? **No**
* Do you want to configure Lambda Triggers for Cognito? **N**
* Enter the name of the group to which users will be added. **users**
* Do you want to edit your add-to-group function now? **N**

So that is it. We need to add a custom tenantid field now. Go to:
``` Amplify > backend > auth > multitenant...... ```

open the parameters.json and replace this code:

```javascript
 "userpoolClientWriteAttributes": [
        "email",
        "name"
    ],
 "userpoolClientReadAttributes": [
        "email",
        "name"
    ],

```

with this code:

```javascript
 "userpoolClientWriteAttributes": [
        "email",
        "name",
        "custom:tenantid"
    ],
 "userpoolClientReadAttributes": [
        "email",
        "name",
        "custom:tenantid"
    ],
```
open the .....cloudformation-template.yml and replace this code:

```javascript
 Schema: 
        
        -
          Name: email
          Required: true
          Mutable: true
        
        -
          Name: name
          Required: true
          Mutable: true
```

with this code:

```javascript
 Schema: 
        
        -
          Name: email
          Required: true
          Mutable: true
        
        -
          Name: name
          Required: true
          Mutable: true

        - 
          Name: tenantid
          Mutable: true
          AttributeDataType: String
```

push to the cloud first:

```
Amplify push
```

###Let's create 4 users

>*Note: make sure you replace certain values with your own values. Do not apply quotes to string values.*

####Make a the tenant user
Go to the terminal and apply this command:

```
aws cognito-idp admin-create-user --profile <profileName you made in the Set up AWS Amplify chapter > \
    --user-pool-id <CognitoUserPoolID> \
    --username <EMAIL> \
    --temporary-password temppass \
    --user-attributes 
 Name=email,Value=<EMAIL> \
    --message-action SUPPRESS
```

> make a note of the sub value, something like this: 0cc05b9c-9edf-4be8-87b6-25d7c4e750b6

This ID of the first user, the tenant, will be used when creating the next tenant users. 

####Make a tenant user in the group **users**

 

```
aws cognito-idp admin-create-user --profile <profileName you made in the Set up AWS Amplify chapter > \
    --user-pool-id <CognitoUserPoolID> \
    --temporary-password temppass \
    --username <EMAIL> \
    --user-attributes 
 Name=email,Value=<EMAIL> 
\
    --message-action SUPPRESS
```

Now update the attributes:
```
aws cognito-idp admin-update-user-attributes --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --user-attributes Name=custom:tenantid,Value=<ID THAT YOU HAVE NOTED> Name=name,Value=<A NAME>
```

Add this user to the "users" group
```
aws cognito-idp admin-add-user-to-group --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --group-name users
```

####Make a tenant user in the group **editors**
```
aws cognito-idp admin-create-user --profile <profileName you made in the Set up AWS Amplify chapter > \
    --user-pool-id <CognitoUserPoolID> \
    --temporary-password temppass \
    --username <EMAIL> \
    --user-attributes 
 Name=email,Value=<EMAIL> 
\
    --message-action SUPPRESS
```

Now update the attributes:
```
aws cognito-idp admin-update-user-attributes --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --user-attributes Name=custom:tenantid,Value=<ID THAT YOU HAVE NOTED> Name=name,Value=<A NAME>
```

Add this user to the "users" group
```
aws cognito-idp admin-add-user-to-group --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --group-name editors
```

####Make a user in another tenant in the group **editors**
```
aws cognito-idp admin-create-user --profile <profileName you made in the Set up AWS Amplify chapter > \
    --user-pool-id <CognitoUserPoolID> \
    --temporary-password temppass \
    --username <EMAIL> \
    --user-attributes 
 Name=email,Value=<EMAIL> 
\
    --message-action SUPPRESS
```

Now update the attributes:
```
aws cognito-idp admin-update-user-attributes --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --user-attributes Name=custom:tenantid,Value=1 Name=name,Value=<A NAME>
```

Add this user to the "users" group
```
aws cognito-idp admin-add-user-to-group --profile <profileName> --user-pool-id <CognitoUserPoolID> --username <EMAIL> --group-name editors
```

Great job! You have set up your authentication and you have created a tenant with two end users and a complete other tenant user. We will first create some data via the Appsync console and validate our implementation before we are going to build the front end application. But before we can do that we first need to deploy our API.

### Add Graphql API to your project

```
Amplify add api
```

These steps will take place:
* Select **Graphql**
* Enter a name for the API: **multitenantapp** (your preferred API name)
* Select an authorisation type for the API: **Amazon Cognito User Pool** ( Because we are using this app with authenticated users only, but you can choose other options)
* Select at do you want to use the default authentication and security configuration: **Default configuration**
* How do you want users to be able to sign in? **Username** (with this also the AWS Amplify Auth module will be enabled)
* Do you want to configure advanced settings? **Yes, I want to make some additional changes**
* Configure additional auth types? **N**
* Configure conflict detection? **Y**
* Select the default resolution strategy **Auto merge**
* Do you want to override default per model settings? **N**
* Do you have an annotated GraphQL schema? **n**
* Choose a schema template: **Single object ...**
* Do you want to edit the schema now?: **n**

Your API and your schema definition have been created now. You can find it in you project directory:

``` Amplify > backend > api > name of your api ```

Open the schema.graphql file and replace the code with this code. 


```javascript
type channel
  @model
  @key(
    name: "channelByTenant"
    fields: ["tenant"]
    queryField: "channelByTenant"
  )
  @auth(
    rules: [
      { allow: owner, ownerField: "owner" }
      { allow: groups, groups: ["users"], operations: [read] }
      {
        allow: groups
        groups: ["editors"]
        operations: [create, read, update, delete]
      }
    ]
  ) {
  id: ID!
  name: String!
  messages: [message] @connection(keyName: "messageByChannel", fields: ["id"])
  tenant: ID!
  owner: ID!
}

type message
  @model
  @key(
    name: "messageByChannel"
    fields: ["channel"]
    queryField: "messageByChannel"
  )
  @auth(
    rules: [
      { allow: owner, ownerField: "owner" }
      { allow: groups, groups: ["users"], operations: [read, create] }
      {
        allow: groups
        groups: ["editors"]
        operations: [read, update, create, delete]
      }
    ]
  ) {
  id: ID!
  channel: ID!
  user: ID!
  username: String!
  message: String!
  tenant: ID!
  owner: ID!
}



```

Your backend is set up and can be pushed to the cloud, please run:
```
amplify push
```
Follow these steps:
* Are you sure you want to continue? **Y**
* Do you want to generate code for your newly created GraphQL API? **Y**
* Choose the code generation language target? **Javascript**
* Enter the file name pattern of graphql queries, mutations and subscriptions **ENTER**
* Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions **Y**
* Enter maximum statement depth [increase from default if your schema is deeply nested]? **2**

Now create the datastore models which you use in your app. Run this code in the root of your app:
```
amplify codegen models
```

###Test via the Appsync console
Please log in to the console. Go to the Appsync console, select your project and click on queries.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/nlnw53j9sxslk05eg9vb.png)

If you first login with the user from the editor group and create a channel and a few message. Then logout and log in with the user from the other tenant. You will notice that you can see the data of the other tenant. 

This is because we don't have the right validation in place in our VTL templates like I mention before. I will update this section as soon a that github issue has been solved. 

So for now we will set up this tenant filter via our application. I wanted to show this because although we set multi tenant via the app, everybody still has access if they want.

### Set up your React Native App
As you are used from me, I don't focus much on the UX of an application during my blogs. I just want you to show how the basic functionality is working.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/n63cpcrfgie1nfqah7kw.png)

Run first 
```
yarn add @react-navigation/native @react-navigation/stack
```
and
```
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view
```
Make a file **metro.config.js** in the root of your project and past this code. 

```javascript
module.exports = {
  resolver: {
    blacklistRE: /#current-cloud-backend\/.*/
  }
};
```

This will prevent metro to identify duplicate packages and cause errors. 

Go to the **app.js** in the React Native directory and paste this code.

>This file will manage the navigation and logout actions

```javascript
import React from "react";
import { View, Button } from "react-native";
import Amplify, { Auth } from "aws-amplify";
import { withAuthenticator } from "aws-amplify-react-native";
import { DataStore } from "@aws-amplify/datastore";
import Channels from "./src/channels";
import ChannelMessages from "./src/channelMessages";
import LoadAuth from "./src/auth";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import awsconfig from "./src/aws-exports"; // if you are using Amplify CLI

//Amplify.configure(awsconfig);
Amplify.configure({
  ...awsconfig,
  Analytics: {
    disabled: true
  }
});

const Stack = createStackNavigator();

async function logout() {
  await DataStore.clear();
  Auth.signOut();
}

function Appstart() {
  return (
    <View
      style={{
        marginTop: 40,

        display: "flex",
        flex: 1
      }}
    >
      <NavigationContainer>
        <Stack.Navigator>
          <Stack.Screen name="Auth" component={LoadAuth} />
          <Stack.Screen
            name="Channel"
            component={Channels}
            options={{
              headerLeft: null,
              headerTitle: "Channels",
              headerRight: () => (
                <Button onPress={() => logout()} title="Logout" color="#000" />
              )
            }}
          />
          <Stack.Screen name="Messages" component={ChannelMessages} />
        </Stack.Navigator>
      </NavigationContainer>
    </View>
  );
}

export default withAuthenticator(Appstart);

```

Create this file **auth.js** in the src directory. 

> This file identify if a user is authenticated and if so set the datastore selective sync with the tenantid of the user and redirect to the channel overview.

```javascript
import { Auth } from "aws-amplify";
import { DataStore, syncExpression } from "@aws-amplify/datastore";
import { Channel, Message } from "./models";
import { useNavigation } from "@react-navigation/native";

let tenantid = "";

// use for development purposes, to reset the datastore during each app start
DataStore.clear();
DataStore.configure({
  syncExpressions: [
    syncExpression(Channel, () => {
      return c => c.tenant("eq", tenantid);
    }),
    syncExpression(Message, () => {
      return c => c.tenant("eq", tenantid);
    })
  ]
});

function Datastore() {
  const navigation = useNavigation();
  const initDS = async () => {
    await Auth.currentAuthenticatedUser()
      .then(async result => {
        if (result !== "not authenticated") {
          tenantid = result.attributes["custom:tenantid"];
          await DataStore.start();
          navigation.navigate("Channel");
        }
      })
      .catch(err => {
        console.log(err);
      });
  };

  initDS();

  return null;
}

export default Datastore;

```

and create a file **channels.js** in the src directory and paste this code:

```javascript
import React, { useEffect, useState } from "react";
import {
  View,
  Text,
  SafeAreaView,
  FlatList,
  StyleSheet,
  StatusBar,
  TouchableHighlight,
  TouchableOpacity
} from "react-native";
import { DataStore } from "@aws-amplify/datastore";
import { Auth } from "aws-amplify";
import { Channel } from "./models/";
import { useNavigation } from "@react-navigation/native";

const Item = ({ id, name, navigateToMessage }) => (
  <TouchableOpacity onPress={() => navigateToMessage(id, name)}>
    <View style={styles.item}>
      <Text style={styles.title}>{name}</Text>
    </View>
  </TouchableOpacity>
);

export default function Channels() {
  let subscription;
  const [channels, setChannels] = useState([]);
  const navigation = useNavigation();

  const navigateToMessage = (id, name) => {
    navigation.navigate("Messages", { id, name });
  };

  const loadChannelArray = async () => {
    const result = await DataStore.query(Channel);

    setChannels(result);
  };

  useEffect(() => {
    loadChannelArray();
    subscription = DataStore.observe(Channel).subscribe(() => {
      loadChannelArray();
    });
    return function cleanup() {
      setChannels([]);
      subscription.unsubscribe();
    };
  }, []);

  const renderItem = ({ item }) => (
    <Item
      name={item.name}
      id={item.id}
      navigateToMessage={() => navigateToMessage(item.id, item.name)}
    />
  );

  const onSubmit = async () => {
    const auth = await Auth.currentAuthenticatedUser();

    const identifier = new Date();

    await DataStore.save(
      new Channel({
        name: "New channel " + identifier.getSeconds(),
        owner: auth.signInUserSession.accessToken.payload.sub,
        tenant: auth.attributes["custom:tenantid"]
      })
    );

    loadChannelArray();
  };

  return (
    <SafeAreaView style={styles.container}>
      <View
        style={{
          alignItems: "flex-end"
        }}
      >
        <TouchableHighlight onPress={() => onSubmit()}>
          <Text
            style={{
              fontSize: 16,
              alignItems: "flex-end"
            }}
          >
            Add new channel
          </Text>
        </TouchableHighlight>
      </View>
      <FlatList
        data={channels}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0
  },
  item: {
    backgroundColor: "#dead31",
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16
  },
  title: {
    fontSize: 32
  }
});

```

create a file **channelMessages.js** in the src directory and paste this code:

>This file will manage the messages of a channel

```javascript
import React, { useEffect, useState } from "react";
import {
  View,
  Text,
  SafeAreaView,
  FlatList,
  StyleSheet,
  StatusBar,
  TouchableHighlight
} from "react-native";
import { DataStore } from "@aws-amplify/datastore";
import { Auth } from "aws-amplify";
import { Message } from "./models/";

const Item = ({ username, message }) => (
  <View style={styles.item}>
    <Text style={styles.title}>Name {username}</Text>
    <Text style={styles.title}>{message}</Text>
  </View>
);

export default function Messages(props) {
  const id = props.route.params.id;

  const [messages, setMessages] = useState([]);

  async function loadMessagesArray() {
    const result = await DataStore.query(Message, c => c.channel("eq", id));
    setMessages(result);
  }

  useEffect(() => {
    const loadMessages = async () => {
      loadMessagesArray();
    };

    loadMessages();
  }, []);

  const renderItem = ({ item }) => (
    <Item username={item.username} message={item.message} />
  );

  const onSubmit = async () => {
    const auth = await Auth.currentAuthenticatedUser();

    const identifier = new Date();

    await DataStore.save(
      new Message({
        channel: id,
        user: auth.signInUserSession.accessToken.payload.sub,
        username: auth.attributes.name,
        message: "This is a new message " + identifier.getSeconds(),
        owner: auth.signInUserSession.accessToken.payload.sub,
        tenant: auth.attributes["custom:tenantid"]
      })
    );

    loadMessagesArray();
  };

  return (
    <SafeAreaView style={styles.container}>
      <View
        style={{
          alignItems: "flex-end"
        }}
      >
        <TouchableHighlight onPress={() => onSubmit()}>
          <Text
            style={{
              fontSize: 16,
              alignItems: "flex-end"
            }}
          >
            Add new message
          </Text>
        </TouchableHighlight>
      </View>
      <FlatList
        data={messages}
        renderItem={renderItem}
        keyExtractor={item => item.id}
      />
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0
  },
  item: {
    backgroundColor: "#5d90e3",
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16
  },
  title: {
    fontSize: 18
  }
});

```

You have set up your complete application now. If your run

```
expo start 
``` 
Press **I** when loaded, this will start Expo on your IOS simulator. Login with the different users and notice the difference between tenants and between users in difference groups. 

#Repo
You can find my repo on github: https://github.com/rpostulart/multitenant

#Conclusion
I have shown you how with tools like React Native (Expo) and AWS Amplify you can set up Multi tenant applications. 

Of course this is not the ideal set up yet, because of the conditional checks are missing from the VTL templates. We can manually add those, but from a maintainability perspective we only want to do this from the schema. As soon as there is an update, I will update this blog.

I hope this was useful to you and I am looking forward for your feedback.

#About me
I am a doing (side)projects with technology like AWS Amplify, ReactJS, React Native and EXPO. I am an AWS community builder. I am a huge fan of AWS Amplify and blog about certain topics that come along with it. If you have any questions related to the framework, React of React Native then you can always reach out to me.

# Twitter 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/lblyu9t9fmgvw3g3xu8z.png)

Do you want to be updated about new blogs?
Follow me on twitter: https://twitter.com/ramonpostulart