Developing a Cross-Platform Mobile App on macOS: A Guide to Using React Native and Node.js Without Expo

When building a cross-platform mobile application from the ground up on macOS, it’s crucial to have a clear guide that navigates through the setup and development process without relying on Expo’s managed workflow. This comprehensive tutorial will walk you through creating a user authentication system using React Native for the frontend and Node.js for the backend, all set up and debugged directly on macOS.

Introduction:

This guide focuses on two main components: AwesomeProject for the React Native frontend and NodeAwesomeProject for the Node.js backend. The application includes essential features such as user registration, login, and a personalized home screen. Unlike Expo-based projects, this setup offers more control over the development environment and dependencies, crucial for complex applications.

All the code of frontend AwesomeProject, you can get it from below git repo:

https://github.com/mesepith/AwesomeProject

and all the code of backend NodeAwesomeProject, you can get it from below git repo:

https://github.com/mesepith/AwesomeProject

App Functionality

1. Registration: A well-designed screen collects the user’s name, email, and password. This data is securely sent to the Node.js backend for storage.

2. Login: Users authenticate with their email and password, which is validated by the Node.js backend against data in the MySQL database.

3. Home Screen: Successful login redirects to a personalized home screen displaying a welcome message with the user’s name.

4. Logout: A button ends the user’s session and returns them to the login screen..

Prerequisites:

Before you start, ensure the following tools and environments are installed and configured on your macOS:

  • Xcode: For iOS development and emulator support.
  • Android Studio: For Android development, including setting up virtual devices.
  • Node.js and npm: For running the Node.js server and managing packages.
  • React Native CLI: For creating and managing React Native projects without Expo.
  • CocoaPods: For managing iOS project dependencies.
  • Homebrew: Useful for installing packages like Watchman and other dependencies.

If you’re missing any of these tools, follow below article which has given all the instruction with detailed screenshot and all the commands :

Setting Up Your Development Environment

React Native Frontend

1. React Native Frontend:

Open Terminal and run the following command to create a new React Native project called AwesomeProject:

npx react-native init AwesomeProject

2. Install Navigation Libraries:

Move into your project directory (cd AwesomeProject) and install necessary libraries for navigation:

npm install @react-navigation/native @react-navigation/native-stack

Don’t forget to install dependencies specific to React Native:

npm install react-native-screens react-native-safe-area-context

3. Install AsyncStorage for storing token:

Upon successful login, your server should return a JWT. You can store this token using AsyncStorage in React Native for persistent storage across app.

npm install @react-native-async-storage/async-storage

4. iOS Dependency Installation:

Navigate to the ios folder within your project and run CocoaPods to install the iOS project dependencies:

cd ios
pod install

Return to the project root directory once installation is complete.

5. Change domain host:

Go to config/config.ts and search for API_BASE_URL and edit your host name

Node.js Backend

1. Create and Initialize the Backend Project:

Open a new Terminal window, create a directory for the Node.js project, and navigate into it:

mkdir NodeAwesomeProject && cd NodeAwesomeProject

Initialize a new Node.js project:

npm init -y

2. Install Backend Dependencies:

While in the NodeAwesomeProject directory, install Express for the server, MySQL for database interactions, Bcrypt for password encryption, Body-Parser for parsing incoming request bodies, and jsonwebtoken for creating and verifying JWTs :

npm install express mysql2 bcrypt body-parser jsonwebtoken

MySQL Database Setup

1. Create the awesome_project database.

2. Execute the provided SQL script to create the users table.

create database awesome_project;
use awesome_project;

CREATE TABLE `users` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `password` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

Directory Structure

After setting up both projects, your directory structure should resemble the following:

Frontend (AwesomeProject):

AwesomeProject/
├── App.tsx
├── android
├── config
│   └── config.ts
├── ios
├── screens
│   ├── HomeScreen.tsx
│   ├── LoginScreen.styles.ts
│   ├── LoginScreen.tsx
│   ├── RegistrationScreen.styles.js
│   └── RegistrationScreen.tsx
├── services
│   ├── ApiService.ts
│   └── AuthService.ts
├── utils
│   └── TokenService.js
  • App.tsx: The entry point of the application, setting up navigation. Use React Navigation for screen transitions
  • config/: Create a config.js file, to store your application’s configuration settings
  • screens/: Contains UI components for registration, login, and home screens.
  • services/: Holds the AuthService.ts for API calls related to authentication , ApiService.ts for all secured api calls
  • utils/: Holds the TokenService.js for Token Management Utility.

Key Components:

  • Registration and Login Screens: Handle user input for authentication and utilize AuthService.ts for backend communication.
  • Home Screen: Welcomes the user post-login and provides a logout option.
  • Navigation: Implemented using React Navigation, facilitating movement between screens.

Backend (NodeAwesomeProject):

NodeAwesomeProject/
├── controllers
│   └── authController.js
├── db.js
├── index.js
├── middleware
│   └── authMiddleware.js
├── models
│   └── userModel.js
└── routes
    └── authRoutes.js

  • authController.js: Handles the logic for user registration and login.
  • db.js: Connects to the MySQL database.
  • index.js: Sets up the Express server and base API route.
  • authMiddleware.js: To verify the JWT for protected routes, create middleware that checks the Authorization header for a valid token
  • userModel.js: Manages database interactions for user data.
  • authRoutes.js: Defines authentication routes (/register, /login).

Functionality Overview

  • User Registration and Login: Handled through authController.js, with password hashing via Bcrypt for security.
  • Database Interaction: Managed by userModel.js, facilitating user data storage and retrieval.

Running and Debugging the Application:

1. Start the Backend Server:

Inside NodeAwesomeProject, run npm start to launch the Node.js server.

2. Run the React Native App:

Open a new Terminal tab, navigate to AwesomeProject, and run the app:

iOS:

npx react-native run-ios

Android:

Ensure the emulator is running or a device is connected, then execute:

npx react-native run-android

Errors and Solution:

1. If you face below mentioned errors while running android app:

(base) zahir@Zahirs-MacBook-Pro AwesomeProject % npx react-native run-android
(node:5245) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
info Launching emulator...
info Successfully launched emulator.
info Installing the app...
> Task :react-native-async-storage_async-storage:compileDebugJavaWithJavac FAILED

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.3/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
46 actionable tasks: 2 executed, 44 up-to-date

info 💡 Tip: Make sure that you have set up your development environment correctly, by running npx react-native doctor. To read more about doctor command visit: https://github.com/react-native-community/cli/blob/main/packages/cli-doctor/README.md#doctor 


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':react-native-async-storage_async-storage:compileDebugJavaWithJavac'.
> Could not resolve all files for configuration ':react-native-async-storage_async-storage:androidJdkImage'.
   > Failed to transform core-for-system-modules.jar to match attributes {artifactType=_internal_android_jdk_image, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}.
      > Execution failed for JdkImageTransform: /Users/zahir/Library/Android/sdk/platforms/android-34/core-for-system-modules.jar.
         > Error while executing process /opt/homebrew/Cellar/openjdk/21.0.2/libexec/openjdk.jdk/Contents/Home/bin/jlink with arguments {--module-path /Users/zahir/.gradle/caches/transforms-3/0fc11a0ad5b57d5570bbba85d9e4a925/transformed/output/temp/jmod --add-modules java.base --output /Users/zahir/.gradle/caches/transforms-3/0fc11a0ad5b57d5570bbba85d9e4a925/transformed/output/jdkImage --disable-plugin system-modules}

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 4s
error Failed to install the app. Command failed with exit code 1: ./gradlew app:installDebug -PreactNativeDevServerPort=8081 FAILURE: Build failed with an exception. * What went wrong:
Execution failed for task ':react-native-async-storage_async-storage:compileDebugJavaWithJavac'.
> Could not resolve all files for configuration ':react-native-async-storage_async-storage:androidJdkImage'. > Failed to transform core-for-system-modules.jar to match attributes {artifactType=_internal_android_jdk_image, org.gradle.libraryelements=jar, org.gradle.usage=java-runtime}. > Execution failed for JdkImageTransform: /Users/zahir/Library/Android/sdk/platforms/android-34/core-for-system-modules.jar. > Error while executing process /opt/homebrew/Cellar/openjdk/21.0.2/libexec/openjdk.jdk/Contents/Home/bin/jlink with arguments {--module-path /Users/zahir/.gradle/caches/transforms-3/0fc11a0ad5b57d5570bbba85d9e4a925/transformed/output/temp/jmod --add-modules java.base --output /Users/zahir/.gradle/caches/transforms-3/0fc11a0ad5b57d5570bbba85d9e4a925/transformed/output/jdkImage --disable-plugin system-modules} * Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org. BUILD FAILED in 4s.
info Run CLI with --verbose flag for more details.
(base) zahir@Zahirs-MacBook-Pro AwesomeProject % ,

A: As of now React Native(RN) version 0.73.5 is using Gradle 8.3, which is not compatible with JDK 21.

This is not related to Async Storage, but your setup – best to downgrade JDK, 17 is fine

2. How can I check my React Native version :

A: Go to your terminal and type below command which will display your react native version

npx npm view react-native version

3. How can I check my Gradle version:

A: Type below command from your react project:

cd android
cat gradle/wrapper/gradle-wrapper.properties 

Look for the line that starts with distributionUrl=. This line specifies the Gradle version being used.

Example: distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 

…indicates you’re using Gradle version 8.3.

4. How can I know my JDK version:

A: Go to your terminal and type below command, which will display openjdk version:

java -version

5. How can I downgrade to JDK 17

A: Go to your terminal and type below command :

brew install openjdk@17

Execute the below command in your terminal to create the necessary symlink which we can get while installing openjdk@17. Check above screenshot for preference

sudo ln -sfn /opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk /Library/Java/JavaVirtualMachines/openjdk-17.jdk

Update your .zshrc file: in your terminal type vim ~/.zshrc and update :

# Set Java Home to JDK 17
export JAVA_HOME=$(/usr/libexec/java_home -v17)
# Include JAVA_HOME bin directory in PATH
export PATH="$JAVA_HOME/bin:$PATH"

Apply the Changes: To apply the changes made to your .zshrc file, run:

source ~/.zshrc

And check your java version by typing : java --version

Important Considerations:

  • API Configuration: Ensure API_URL (in AuthService.ts) points to your backend.
  • Native vs. Expo: Since you’re avoiding Expo, be prepared to handle certain dependencies and configurations that Expo typically automates.
  • Error Handling: Implement robust error handling on both sides.
  • Security: Prioritize security best practices.
  • Testing: Write tests!

Conclusion

Building a React Native application with a Node.js backend on macOS offers a flexible and powerful approach for developing cross-platform mobile apps. This guide provides a roadmap from setting up your development environment to running and debugging your application, laying a solid foundation for adding more complex features and functionalities.

Leave a Reply

Your email address will not be published. Required fields are marked *