How I integrated the Instagram Private API in React Native | by Jeff Flynt | Towards Data Science

Being a developer, I always try to automate tasks wherever I can. One such place that seemed interesting to automate was Instagram. I stumbled across this repo on Github: https://github.com/dilame/instagram-private-api. It provides a NodeJS Instagram private API client. I created a new npm project with this library locally, ran it, and opened the app on my phone to see if it would like posts automatically. Sure enough, it did!
The private API client allows you to like, follow, upload, basically everything you can do in the native Instagram app. I was having success working with my script locally, and I wondered would it be possible to use the client in React Native.
My initial thought was just to try and import the library directly in React Native. This was not successful because the library has some dependencies that require the use of native modules, commonly referred to as C++ Addons.
I then started looking into how I could run Node.js in React Native. I came across this post, which went into more detail about the challenges surrounding Node.js native modules for mobile. The author of that post worked on creating a modified version of node-gyp “that is able to target the right OS and CPU architecture and include the Node.js headers from the Node.js for Mobile Apps release.”
Using the Node.js library along with React Native, I was able to successfully build an app that allowed users to automate functions that they would normally have to do manually.
The alternative to this would have been to install this on a server and let users make the calls from the app. The downside to this approach is that all the API calls to Instagram would have come from the same IP as the server and this would have lead to the service being blocked. Having the calls come directly from each user’s device, would result in Instagram seeing the requests come from the user’s usual IP address and the service would be safe to use.
I first created a new react-native project, using the command:
npx react-native init reactNativeInsta
I opted to use React Native CLI instead of Expo because of the native code required to properly run the client library.
Once inside my project folder, I installed the Node.js library for React Native.
npm install nodejs-mobile-react-native
You will want to take a look at the getting started guide for more details specific to Android & iOS.
The Node.js library includes a React Native bridge module that facilitates communication between the Node.js code and the React Native code. The library will generate a folder top-level called nodejs-assets. Inside this folder is a sub-folder called nodejs-project. This is where you will install any node libraries and write your node code. Below is how it looks in VSCode.
The BUILD_NATIVE_MODULES.txt is present, so I can bypass the automatic detection of native modules. This is entirely optional.
In the console, you would want to navigate to the nodejs-project folder and install the Instagram Private API library.
npm install instagram-private-api
I installed two additional libraries, sleep-promise & bluebird. sleep-promise is used to pause the script for a set amount of time; this is important, so you don’t fire off multiple requests at once and risk getting blocked from Instagram. bluebird is used because it improves upon the .catch() function to work more like catch-clauses in Java or C#.
For the node code, I require these libraries at the top.
const sleep = require("sleep-promise");const { IgApiClient, IgCheckpointError } = require("instagram-private-api");const Bluebird = require("bluebird");const rn_bridge = require("rn-bridge");const { promises: fs } = require("fs");let ig = new IgApiClient();
IgApiClient — the class used to interact with the API.
IgCheckpointError — checkpoint exception class (handle checkpoint flow).
rn_bridge — bridge to communicate with React Native.
fs — access the filesystem to save the user’s state.
To allow a user to interact with the client library (Node code), we need to add a function to process these messages. The flow for the code below goes through a check to see if the user is authenticated, and the data object includes their credentials to go through the authentication flow. Each message sent across the bridge is serialized to JSON. I parse each message upon the function call.
*A quick note on Instagram creds. I opted to store the user/password in localStorage vs. on the backend server to protect the user’s information. Each call to the Node code, I pull creds from localStorage and send along with the data object.
To perform any actions, we need to add functionality to allow a user to authenticate with Instagram. I created a function called loginFlow that checks if the session exists. I will cover how I save and load this session below.
If the session exists, I call the loadSession function. This function retrieves the session json file from the phone’s file system and restore’s the user’s session. I return the user’s id as some functions I call will require the id to be sent along with the call to the API.
If the session does not exist, I call the newLogin function that handles logging in the user with their creds and storing the session to the phone’s filesystem.
Here is a saveSession function that I use to write to the file system.
const savePath = rn_bridge.app.datadir();const saveSession = async (data) => {await fs.writeFile(savePath + "/igSessionStore.json", JSON.stringify(data), "utf-8");};
I store the datadir() to a const variable as this may differ between iOS & Android. More information about this can be found at the Github repo.
Once the user is logged in, I call the function the user is requesting. Here is a sample function that allows a user to like posts via tags.
I keep a counter to keep track of how many posts the user has liked and send this back to the user in an in-app notification toast. The mediaArray variable calls getMediaFromTags, which takes the comma-separated array of tags the user has entered to be liked and pushes the primary key of the post to be liked.
Since the array will sometimes consist of arrays within arrays, I call a utility function to flatten the array. Since the javascript version will differ across platforms, I didn’t use array.flat().
const flatten = (arr) => {for (let i = 0; i < arr.length; (Array.isArray(arr[i]) && arr.splice(i, 1, ...arr[i])) || i++) {}return arr;};
Once I have an array of posts to like, I send this array to a function called likeMedia, which likes the post for the user. I randomize the sleep function here so as not to get blocked from Instagram.
The call immediately before the return is to call a backend server function that keeps track of the likes for a user. The app kept statistics and displayed them on the first screen after logging in.
In my index.js file for React Native, I import nodejs from “nodejs-mobile-react-native.” This allows me to start the main.js script using Node.js. I also add an event listener that will process messages sent from Node.js.
When a message comes back that is either a notification or an error notification; I display the message to the user.
The code below shows a function that allows the user to like posts based on hashtags. Each time I want to send a message to the Node side, I import nodejs from “nodejs-mobile-react-native.”
Since the user is entering a comma-separated list or a single value, I opt to trim the list of white spaces and remove any duplicates before sending it to the Node side. If userHashTags is empty, I alert the user to enter a hashtag or hashtags. This function is called when the user hits submit on this particular screen. While the Node code is calling Instagram’s API, I set setLoading to true to show the user a spinning loading indicator and also to prevent the user from calling the API multiple times until the initial call is complete. Once the like function on the Node side completes, it sends a message to the React Native side, and the event listener sets loading to false and resets some of the local state values.
DeviceEventEmitter.addListener("changeLoading", (event) => {  setLoading(false);  setUserHashTags();  setUserLimitPost("0");});
A few years ago I launched this app on Google’s Play Store & released it for desktop using NW.js. Time constraints kept me from keeping up with the updates needed to remain consistent with Instagram and the user experience suffered. Hopefully this post can help others who are looking to do something similar.
Some of the code examples shown here have been modified for brevity and some functionality such as calls to the backend were removed.
If you have any questions or comments, feel free to leave them below!