Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

A guide to native routing in Expo for React Native

6 min read 1696

A guide to native routing in Expo for React Native

A decade ago, if anyone told you that you could build native mobile applications using JavaScript without compromising UX, you wouldn’t believe it, right? Then React Native came along and made it possible.

A few years ago, if anyone told you that you could build cross-platform apps using JavaScript without heavy emulators/developer environments such as Android Studio or Xcode, you wouldn’t imagine this working, either, right? Then Expo was created.

Expo and React Native are often interchanged for each other. This is understandable because they have similar features, but there are a few differences between them:

  • Expo builds a layer of tools on top of React Native, allowing developers to build applications without writing any native code
  • Expo has a CLI (expo cli) that allows developers to create projects, deploy them, and open apps on their devices
  • Expo has a client app, Expo Go, where you can open your projects without needing Android Studio or Xcode

However, Expo and React Native have shared one major feature: navigation. Routing in React Native applications is implemented using React Navigation, which wraps your app with a navigator component that manages the navigation history and presentation of screens in the app.

This worked great, but it had its problems:

  • You had to install peer dependencies such as react-native-screens, react-native-safe-area-context. This adds extra weight to your app
  • JavaScript developers love file-based routing. Next.js’s file-based routing style works well, and it has quickly become like the current standard for routing in JavaScript applications

This led Evan Bacon, creator of Expo, to build a new library called Expo Router. Expo Router works similarly to the Next.js router. They both generate nested navigation and deep links based entirely on a project’s file structure.

In this article, we’ll look into some pros and cons of Expo Router. Expo Router is currently in beta, but we will build a React Native application using Expo Router for navigation to demonstrate its core concepts.

Jump ahead:

Core features of Expo Router

These are the core features of the new Expo Router:

  • Offline-first and fast: Native apps must handle incoming URLs without an internet connection. Expo Router enables this by implementing these features across the entire framework
  • Error Handling: You can set up React error boundaries on each route
  • Layout routes: Most screens in your app will share the same layout components. Expo Router allows you to create a parent layout component in the app directory
  • Next.js-like linking and dynamic and static routing: Similar to the Next.js router, you can link routes using the Link component
import { Link } from "expo-router";

export default function Page() {
return (


The only major tradeoff with the new Expo router so far is that it is limited and opinionated. Some developers may appreciate the Expo Router features ,but want to rearrange the file directory structure. Unfortunately, due to its limitations, you might not be able to customize your Expo Router instance to fit your preferred structure just yet.

Building a React Native contacts app with Expo Router

Let’s go ahead and build a contacts directory app that uses the Expo Router to navigate between screens.

To follow through the tutorial, ensure you have the following:

  • Node.js ≥v14 installed
  • Knowledge of JavaScript and React
  • AWS Amplify CLI installed:
    npm install -g @aws-amplify/cli
  • AWS Amplify configured:
    amplify configure
  • Expo CLI installed:
    npm install -g expo-cli
  • Expo Go (installed from your mobile play store)

This is the GitHub repo with the complete code for the demo.

Getting started

Let’s get started by scaffolding a new Expo app. Run this command on your terminal:

npx create-react-native-app -t with-router

This will create a new React Native application with Expo Router configured. Change the directory, initialize Amplify, and install peer dependencies using these commands on your terminal:

cd ReactNativeContactExpoApp
npx [email protected]
npm install aws-amplify @react-native-community/netinfo @react-native-async-storage/async-storage

To run the app, run yarn start or npm run start.

Starting Metro Bundler
█ ▄▄▄▄▄ █▄▄███▀ ███ ▄▄▄▄▄ █
█ █   █ █ ▀█ ▄ █▄██ █   █ █
█ █▄▄▄█ █▄ ▄▄▀▀█▄██ █▄▄▄█ █
█▄▄▄▄▄▄▄█▄▀▄▀▄█ █ █▄▄▄▄▄▄▄█
█▄▄▄ ▀▀▄█ ▀████▀██▄██▄ ▄▀▄█
█▀▄▀▄▄▄▄▀▄██▀▀▀▄█▄█ ▀██▀███
█▄▀█▀ ▄▄▀▄▄▀ ▄ █ ▄█ ▄ █ █▀█
█▀▄▀██▀▄█ ▀ █▀███ ▀▀█▀▀█ ▀█
███▄▄▄▄▄█ ▄▀ ▄  ▄ ▄▄▄ ▀▄█▀█
█ ▄▄▄▄▄ ███ ▀▄███ █▄█ █▄  █
█ █   █ █▀▀█▀ ▀▀▀▄▄   █▀▀ █
█ █▄▄▄█ █  ▄▄█▀ ▀▄▀▄█▄▄ ▄██

› Metro waiting on exp://
› Scan the QR code above with Expo Go (Android) or the Camera app (iOS)

› Web is waiting on http://localhost:19000

› Press a │ open Android
› Press i │ open iOS simulator
› Press w │ open web

› Press j │ open debugger
› Press r │ reload app
› Press m │ toggle menu

› Press ? │ show all commands

Scan the QR code on your mobile Expo Go app and it’ll compile.

Creating the contact model with AWS Amplify

Just like with every other database, DataStore requires a model. To generate this model, head to the Amplify Directory > Backend > API > (Amplify project name) > schema.graphql. Modify the schema.graphql with these lines of code:

type Contact @model {
  id: ID!
  name: String!
  phone: String!
  email: String!
  address: String
  message: String

The contact model has id, name, title, phone, and email as fields. Let’s now go ahead to generate the model with this command on your terminal:

npm run amplify-modelgen

This will create a src/models folder with the model and GraphQL schema.

Initializing the backend environment

We need to initialize an Amplify backend environment for the application. To do this, run this command on your terminal:

amplify init
? Enter a name for the environment dev
? Choose your default editor: Visual Studio Code
Using default provider  awscloudformation
? Select the authentication method you want to use: AWS profile

This will create a configuration file in src/aws-config.js. Next, we will deploy the backend and create AWS resources using this command:

amplify push
Do you want to generate code for your newly created GraphQL API? No

This might take some time to deploy based on the quality of your internet connection.

Configuring the app with DataStore

Let’s go ahead and configure the Expo app to work with Amplify. Create an app/index.js and add these lines of code:

import config from '../src/aws-exports';
import { DataStore, Amplify } from 'aws-amplify';


Creating contacts using Amplify Studio

Let’s add some contacts to our DataStore. To achieve this, head to the AWS Amplify console in your browser. The shortcut is running amplify console in your terminal and selecting AWS Console.

To create contacts with Amplify Studio, go to the Amplify dashboard and click Launch Studio:

Launch your studio in Amplify Studio

You should see something like this:

Click the Data menu and then save and deploy

Click the Data menu by the left sidebar of the dashboard and click Save and Deploy.

The Data modeling page in Amplify Studio

You can now see the schema we created and edit it using the UI. Deploying the data model might take few minutes, depending on your internet connection.

Our successfully deployed data model

When it’s done deploying, open the Content menu.

The Content menu in Amplify Studio

Click the Create contact button to create a new contact. You can also auto-generate seed data by clicking the Actions dropdown and selecting Auto-generate data.

That’s it! We’ve successfully created contacts for the application.

Rendering contacts with a static route

Let’s render the contacts. Update the app/index.js with these lines of code:

import { View, Text, Button } from "react-native";
import { Link, Stack } from "expo-router";
import config from '../src/aws-exports';
import { DataStore, Amplify } from 'aws-amplify';
import { Contact } from '../src/models'
import { useState } from "react";


export default function Home() {
    const [contacts, setContacts] = useState([])

    async function fetchContacts() {
        const allContacts = await DataStore.query(Contact)


    return (
        <View style={container}>
            <Stack.Screen options={{ title: "Contacts" }} />
                contacts.map(contact => (
                    <View key={contact.id} style={contactBox}>

                            <Text style={textStyleName}>{contact.name}</Text>
                            <Text style={textStyle}>{contact.phone}</Text>

                        <Link href={`contacts/${contact.id}`}>
                                title="View contact"

Here, we query the contacts from DataStore by passing the model to the DataStore .query() method.


To get more details for each contact, we used the new Expo Router link component and passed the [contact.id](<http://contact.id>) to the href attribute. The dynamic route will be contacts/[contactId].

Rendering single contacts with a dynamic route

Let’s create the screen for contact details. In the app directory, create a contacts folder and an [id].js file.

import { Text, View } from "react-native";
import { Stack } from "expo-router";
import { useEffect, useState } from "react";
import { Contact } from "../../src/models";
import { DataStore } from "aws-amplify";

export default function SingleContact({ route }) {
    const [contact, setContact] = useState('')
    useEffect(() => {
        if (!route.params?.id) {
        DataStore.query(Contact, route.params.id).then(setContact)
    }, [route.params?.id])
    return (
        <View style={container}>
            <Stack.Screen options={{ title: contact.name }} />
            <View style={contactBox}>
                <Text style={textStyleName}>{contact.name}</Text>
                <Text style={textStyle}>{contact.phone}</Text>
                <Text style={textStyle}>{contact.email}</Text>
                <Text style={textStyle}>{contact.address}</Text>
                <Text style={textStyle}>{contact.message}</Text>

If you’ve worked with Next.js, this code should look familiar. The major difference is that the Expo Router makes the route object available without importing it.

In DataStore, to query a single item by its ID, you pass the model and ID you want to query. In our case, we pass the params ID. Awesome! Run your app, and you should get something like this:

Our final contacts app, build with the new Expo Router
Our final contacts app, build with the new Expo Router


This article taught us about the Expo Router, how it works, its core features, and its tradeoffs. We built a React Native app with Expo and data from Amplify DataStore.

File-based routing is the future of smooth navigation experience for mobile applications. Expo Router implements this solution into its library. Although it’s only in its beta release, the Expo Router features are impressive and will definitely excite React Native developers.

LogRocket: Instantly recreate issues in your React Native apps.

LogRocket 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 — .

Emmanuel John I'm a full-stack software developer, mentor, and writer. I am an open source enthusiast. In my spare time, I enjoy watching sci-fi movies and cheering for Arsenal FC.

Leave a Reply