Expo and Express Web and Mobile Dev

Web and Mobile Dev with Expo and Express

John Boardman Articles, Mobile, React, React Native 1 Comment

So, you want to develop a new website with spiffy apps on Android and iOS, and you want that website and your APIs to run in Node Express. It sounds like a lot of work to write the website in React (or Flutter or whatever the language de jour is), the Android app with Android Studio, and the iOS app with Xcode.

The idea of React Native sounds nice, so you don’t have to have a codebase for each phone operating system. What if we went one step further and also used React Native Web? One codebase that covers the website plus both mobile platforms! That sounds great, but after reading about it (blogs, reviews, trying to wade through the docs, etc.), it appears to be a daunting task.

Fear not, for I have walked through that fire and come out on the other side, ready to share my experience and code with you so you don’t have to start from that very minimal create-expo-app. I’m also including the scripts and support necessary to deploy the react-native-web app to a Node Express server. Don’t get me wrong, it’s still not a cakewalk to get set up and running. However, I will say the effort is worth it, and hopefully, you will too after seeing this in action.

Getting Set Up

The public repo lives here. You should head there and clone the repo first. It has the MIT license, so don’t worry about any copyright issues.

Once you clone the repo, please read the README.md file. Especially if you are using Windows, there are some invaluable tips for setting up your machine to properly build the Android app.

Requirements

The requirements I set out to meet were actually pretty aggressive. Upgrading to the latest versions of Expo, React Native, and enabling the New Architecture were fairly difficult tasks involving a lot of calls to “npx expo-doctor” or figuring out why builds failed. Things move very fast in this world, so there are likely packages that are already out of date. The work I’ve done goes a long way toward making those upgrades less painful.

  1. A Node Express server suitable for deploying to a hosting site such as Heroku. The server must serve both the website and the APIs that will be called.
  2. A website, Android app, and iOS app all written using one codebase.
  3. Use the latest version of Expo (52.0.23 at the time this blog was written). This avoids having to immediately update everything after using create-expo-app since it has not yet been updated to the latest version.
  4. Use the latest version of React Native (0.76.6 at the time this blog was written).
  5. Use the New Architecture in React Native.
  6. All of the configuration needed to build the apps on Expo EAS. This is how you will be able to both give the apps to users for testing and deploy the apps to production. If you are developing on Windows, EAS will also allow you to build the iOS version of the app.

Server

The server I’ve included is intentionally minimal. I’ve left a lot of extra configuration in it past what is needed for the example because it can be difficult to learn how to do things like enable compression, extend the limit of JSON data, control caching, and coerce CORS into allowing you to test locally.

You’ll find all of this in app.ts, but the most important part is serving our React Native Web site. Obviously, you’ll need to connect a database and hook up models and all of that, but that was out of scope for this example. I recommend Postgres and Sequelize when the time comes and you need it.

const dirname = __dirname + "/../dist";

app.use(express.static(dirname));
app.get("/*", function (req, res) {
  res.setHeader("Content-Type", "text/html");
  fs.createReadStream(dirname + "/index.html").pipe(res);
});

This code tells Express where to find the distribution files for the website. When the client builds for production, it copies the distribution files over to the dist folder in the server. express.static() then serves those files as an SPA (single page application). We tell Express that we are serving HTML, then have the Node filesystem (fs) grab index.html and return it as the response. React Native Web takes over from there and runs the site.

Client

The client code has a lot more to it, and I’ve included extra goodies for you! Let’s take a look.

Packages and Configuration

You’ll find a lot more packages defined in package.json than are required to make this example work. I left them there as hints about the ones I have found useful.

For example, when using Expo to take a picture, different packages work better on different platforms. expo-image-picker works better on Android for taking pictures whereas expo-camera works better on iOS and the web. I’ve also left the proper permissions configurations in app.json to enable using the camera.

Luxon is the best package for working with dates and times. decimal.js should be used for any calculations involving currencies, as Javascript’s Number is very imprecise and cannot be counted on.

Theme

One cool thing about the Expo example is that it supports light and dark themes. React Native’s useColorScheme hook finds out the user’s current preference for light or dark mode. Once the app is running, try switching your device theme and watch the app or website dynamically switch along with it!

Expo took that idea and extended it, adding components that configure themselves based on the theme. In client/src/app/components you will find several more themed components than are provided by the Expo example, including a date picker and dropdown that work across web, Android, and iOS. These took a while to figure out, so I hope they are useful in your work!

Styles

Websites and apps wouldn’t look very good without styles, and React Native has them covered. In client/src/styles you’ll find an index.ts file. It decides which styles to load based on the current platform.

Web is straightforward and just loads the web.ts styles. For mobile, I noticed many of the styles for Android and iOS were the same, so I combined those into baseMobileStyles. After that, I load the styles that are different between the two.

Notice in baseMobileStyles, Android, and iOS, that the definitions are just objects, not React Native StyleSheets. That’s because I load them, and then I create the StyleSheet by using the Javascript spread operator.

The names of the styles must match between web, Android, and iOS. Then in the code, you just use the common name, for example styles.header. That makes one more way one codebase works for three different platforms.

Platform-Specific Modules

Sometimes it can be useful to create pages specific to one platform. Expo router supports this with what they call “Platform-specific Modules” (see Platform-specific Modules – Expo Documentation).

Basically, you name your file with an extension like myfile.web.ts. If you also have myfile.ts in the same directory, then the other platforms will load that file. Having tried them, I found that I forget about the specific web version and then can’t figure out why my changes to myfile.ts aren’t working on the web!

Personally, I would rather make use of the Platform component in React-Native. This allows you to decide within one file how to write code that is specific to each platform. You’ll see it used within the repo. ThemedDatePicker has a good example.

Header

It can be tricky to put buttons and menus in the Expo header, so I created an example that you can find in client/src/app/(app)/_layout.tsx. Using a Pressable and some CSS allows you to create a nice dropdown menu that can be easily expanded with more options.

The important piece here is that the menu (see the Collapsible on line 79) is rendered before the Expo stack. If the components are not ordered in this way, the menu will be hidden behind the rest of the screen.

If you are wondering why the folders are named strangely, you can find out why in the Expo docs. Basically, any files in and under the (app) folder require the user to be authenticated. Files in the client/src/app folder are for unauthenticated users. These include login, registration, and error pages.

Register/Sign-In

I created dummy register and sign-in pages to show how authentication is done in Expo. The registration page also gives me a chance to show how to use all those themed controls we talked about earlier.

After gathering and validating data, functions in the React Context are called to process the registration or login. These just set state variables, but by using MMKV, the state is persisted whether on the web, Android, or iOS.

Try registering or logging in, closing the browser window, and then reopening it. You’re still logged in! Obviously, you can use whatever state management you want (Redux, etc.), but for this exercise, I kept it simple and just went with the built-in stuff.

I also left examples of how to make API calls to the Express backend. These are commented out in the code. One thing that may look strange is the use of setTimeout around the API calls. When making calls to the context, we shouldn’t rely on any returned result. Instead, we set variables in the context that our application should react to.

The first thing we need to react to is setIsLoading, which tells the code in client/src/app/(app)/_layout.tsx to display the “Loading…” components instead of the normal screen. That state change cannot happen if we are “await”ing the result of an API call because the thread hangs until the result is obtained.

By using setTimeout with a little delay, the loading page always appears since React’s state management is not held up by the API call. Once the call does return, we set the variables related to the call in the Context and flip isLoading back to false.

Local Testing

For the server, it’s pretty easy to get a debug session going. Using VSCode, select the server/src/app.ts file. Hit F5, select “Node Debugger,” and a debug session will start. You can set breakpoints directly in VSCode and do all of your debugging right there.

The client has several different areas to contend with. We want to test on web, Android, and iOS. Let’s look at each one separately.

A few things apply to all clients:

  1. Be sure the server is already running before starting the client.
  2. Fast refresh is on, so updates will be live whenever you save files. This makes for very fast development and testing.

Note for mobile deployment:
The eas.json and app.json files are already mostly configured for you if you decide to create an EAS account. The only things you need to do are (1) go into the app.json file at the bottom and add your EAS project ID and (2) change the owner to your EAS user ID (not your email address).

Developer builds will use the “development” or “development-simulator” configurations. These builds require downloading to a machine that has Metro available, which is basically for sharing early builds between developers.

Preview builds are for running on your phone and hitting a real server. This is the build for your testers, and it uses the “preview” configuration.

Prod uses the “production” configuration and is suitable for pushing to the app stores.

web

Running “yarn web” will compile the web version and deploy it to Metro. The code is also set up for debugging; right-click in Chrome and select “Inspect,” go to “localhost:8081” and work your way down to the code.

Android

Running “yarn android” will build the Android app and deploy it to Metro. It will also ask which Android device you want to deploy to, so make sure you’ve already created an Android simulator in Android Studio. Once running in the Metro window, pressing “j” will open a Chrome-powered debug window, which makes it very similar to debugging on the web.

Code doesn’t build? Make sure you’ve followed the instructions in the README.md file, especially if you are on Windows.

A free developer account at expo.dev will also allow you to build for Android. A bonus here is that no other account is necessary – you can make Android development builds and load them on your devices for no cost.

Deploying to production is another matter of course, and it requires a Google Play developer account. Some helpful instructions are here.

iOS

Running “yarn ios” will build the iOS app and deploy it to Metro. It will also ask which iOS device you want to deploy to.

Note that deploying to physical devices isn’t currently working (at least for me), so if you want to do that, you’ll need to create a free project at expo.dev. However, you will need to have an Apple developer account (costs about $100) to be able to actually build and deploy. There are instructions here to help you through the process.

Deployment

The script names “build” and “start” aren’t there by accident. They happen to be the scripts Heroku looks for when deploying the web server. Separating the build from the startup is critical on Heroku because there is a 60-second timeout on startup, and if it goes over, Heroku shuts it down.

Therefore, we need our build to be able to run as long as it needs to, so we move all of that step over to the “build” script. The “build” script builds both the server and the React Native Web site. The client build has the additional code to copy the built files over to the server, so it knows where to find them when it starts up.

There are a couple of things to know about the configuration in order to make this work. First, in app.json, under the “web” section, the output is set as “single.” This is what tells the Metro bundler in Expo to produce a static site as an SPA. The index.html file that is produced is then all Express has to worry about loading, and React Native Web does the rest to make the site function.

Conclusion

By using Expo and React Native + Web plus an Express server, we were able to build and test a website and mobile apps using a single codebase. This is a powerful way to speed up development while maintaining quality. Let me know what you build!

I hope something along the way has been informative, new, and/or interesting. Feel free to take this code and use it however you wish to create your own websites and apps, and be sure to subscribe to the Keyhole Dev Blog for more valuable tips and tricks.

0 0 votes
Article Rating
Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments