Search code examples
djangoreact-nativedjango-rest-frameworkexpoexpo-go

How to connect Expo GO App to Django Rest Framework Backend on localhost?


I am implementing a react native application using Expo and testing it on my iOS device using Expo Go. I have a Django rest framework backend running on my local machine that I can access using my browser via http://localhost:8000 - using localhost in my react native app does not work during my fetch request. For instance:

 let response = await fetch(BACKEND_URL + "/auth/token/obtain/", {
      method: "POST",
      body: JSON.stringify(data),
      headers: {
        "Content-Type": "application/json",
      },
    });

returns

Network request failed
at node_modules\whatwg-fetch\dist\fetch.umd.js:null in setTimeout$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in _allocateCallback$argument_0
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in _callTimer
at node_modules\react-native\Libraries\Core\Timers\JSTimers.js:null in callTimers
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __callFunction
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __guard$argument_0
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in __guard
at node_modules\react-native\Libraries\BatchedBridge\MessageQueue.js:null in callFunctionReturnFlushedQueue

I have tried setting BACKEND_URL to localhost:8000 and my public IP via expo-constants

import Constants from "expo-constants";

const { manifest } = Constants;

const uri = `http://${manifest.debuggerHost.split(":").shift()}:8000`;

But neither seems to work. I have enabled the Corsheaders middleware in my DRF project and placed the @csrf_exempt decorator on my APIView's dispatch method, and this error persists. I also added localhost:19000 to my CORS whitelist, which is where Expo seems to host its local server. What could be the problem here? Both the Expo server and the Django server are running locally on my laptop, and otherwise, the API works in Django tests.

Using curl on my API endpoints via localhost also works, though the external IP returned by expo constants does not—but this may be because I am sending from localhost.


Solution

  • I found a fix using information from these two posts: this stack overflow and this blog. Neither of these solutions worked for me out of the box but a little networking logic makes it all come together. The core issue here is that the Django server is hosted locally but the Expo Go App is running on a separate mobile device (despite the expo server being hosted locally as well). I've tested each of the following steps to make sure they are all necessary.

    1. Set up Django CORS using the corsheaders app and the corresponding middleware, there are guides on how to do this but basically pip install django-cors-headers then add it to your INSTALLED_APPS and Middleware in settings.py
    INSTALLED_APPS = [
        ...,
        'corsheaders',
    ]
    
    # make sure CorsMiddleware comes before CommonMiddleware
    MIDDLEWARE = [
        ...,
        'corsheaders.middleware.CorsMiddleware',
        'django.middleware.common.CommonMiddleware',
        ...,
    
    ]
    

    Also use the @csrf_exempt decorator on the views you must be able to access (there are security implications to this but I am using JWT authentication anyways for this mobile app so this is okay in my case - I would do my due diligence here). There are a few ways to do this for function and class views:

    from django.utils.decorators import method_decorator
    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt
    def my_view():
    ...
    
    # or for class based views
    class my_view(APIView):
      @csrf_exempt
      def dispatch(self, request, *args, **kwargs):
        return super().dispatch(self, request, *args, **kwargs)
    
    # or my preferred way for easy copy paste
    @method_decorator(csrf_exempt, name='dispatch')
    class my_view(APIView):
    ...
    
    
    1. Expose the Django server over LAN, this step is necessary so that your device can interface with the Django server over the local network - may not be necessary if using an emulator (though I'm aware that the Android emulator for instance accesses localhost using a specially designated IP address so I would check for this).
    # specify this IP to expose on LAN
    python manage.py runserver 0.0.0.0:8000
    
    1. Find your local machine's IP address - the blog above uses the mobile device's IP but that did not work for me, which makes sense given that both servers are running on my laptop. For windows I opened the command prompt and entered ipconfig then found the IPv4...... line which contains my address.

    2. Use this IP to direct your API calls and add it to your CORS whitelist. For example if your localmachine IP is 10.0.0.1 and Expo is on port 19000 while Django is on port 8000 we add the following.

    # Django settings.py
    ALLOWED_HOSTS = ['10.0.0.1',]
    
    CORS_ORIGIN_WHITELIST = [
        'http://10.0.0.1:19000',
    ]
    
    // Wherever you define the backend URL for the frontend, in my case config.js
    export const BACKEND_URL = "http://10.0.0.1:8000"
    
    // Then make requests 
    fetch(BACKEND_URL + "/api/endpoint/", ...