amazon-web-servicesflasknext.jshosting

Why do I always need to run my Next.js app after my Flask app for it too actually render objects from API calls?


I am creating a Next.js app that uses a Flask API for the backend. I have a Main component that displays fetched data from the Flask API and displays it. However, I struggle with one issue in the beginning. Whenever I deploy my Flask API after my Next.js app, the Next.js app calls render the object that is supposed to store the fetched data as null, meaning that nothing is returned from the API, causing an error. However, whenever I refresh the app afterwards or start my Flask API before Next.js app, it works fine. I think this is because the Flask API hasn't fully been deployed or hosted whenever I run it after my Next.js app but I don't know how to fix this issue. I am using AWS for depositing data into S3 buckets and DynamoDB for the database storage.

I tried running it afterwards with some time and just waiting before loading it, using a debouncer (which is still in my code because it helped with reducing API calls), and other things. I still don't know how to fix it.

import React, { useState } from 'react'
import axios from 'axios'
import Image from 'next/image'

import SectionColumn from './SectionColumn'
import Loading from './Loading'

const MainLayout = ({isLoading, menuData} : {isLoading: boolean, menuData: string | any}) => {

  console.log(menuData)

  if (isLoading) {
    return (
      <div className='flex justify-center items-center h-full'>
        <div className="loader"></div> {/* This is where your GSAP animation will be applied */}
      </div>
    );
  }
  if (!menuData || menuData === 'Unavailable' ) {
    return <div className='flex flex-col items-center justify-center h-full text-gray-200'>
      <Image src='/texas-cleared-logo.png' width={128} height={128} alt='Longhorns Logo'/>
      <h2>Sorry, but this is not open! Try again later!</h2>
    </div>
  } 

  return (
    <div className='w-3/4 my-12'>
      <div className='hidden lg:flex flex-row justify-between items-start gap-28 mx-4'>
        {Object.entries(menuData).map(([mealType, sections]: [string, any]) => (
          <SectionColumn key={mealType} sectionName={mealType} sections={sections} />
        ))}
      </div>

      <div className='lg:hidden flex flex-col justify-around items-center gap-12'>
      {Object.entries(menuData).map(([mealType, sections]: [string, any]) => (
          <SectionColumn key={mealType} sectionName={mealType} sections={sections} />
        ))}
      </div>
    </div>
    
    
  )
}

export default MainLayout
import axios from 'axios'

const API_BASE_URL = 'http://127.0.0.1:5000'

export const fetchDiningHalls = async () => {
  try {
    const response = await axios.get(`${API_BASE_URL}/return_location_halls`)
    return response.data.dining_halls
  } catch (error) {
    console.error(`Error fetching data`, error)
    return []
  }
}

export const fetchDatesForDiningHall = async (diningHall: string) => {
  try {
    const response = await axios.get(`${API_BASE_URL}/return_dates/${diningHall}`);
    return response.data.dates
  } catch (error) {
    console.error(`Error fetching dates for dining hall ${diningHall}:`, error)
    return []
  }
}

export const fetchMenuDataForJ2Dining = async () => {
  try {
    const response = await axios.get(`${API_BASE_URL}/return_meals/J2 Dining/Wednesday, June 31`);
    return response.data;
  } catch (error) {
    console.error(`Error fetching menu data for dining hall ${} on date ${}:`, error);
    return null;
  }
};

export const fetchMenuData = async (diningHall: string, date: string) => {
  try {
    const response = await axios.get(`${API_BASE_URL}/return_meals/${diningHall}/${date}`, {
      params: { hall: diningHall, date: date }
    });
    return response.data;
  } catch (error) {
    console.error(`Error fetching menu data for dining hall ${diningHall} on date ${date}:`, error);
    return null;
  }
};

export const handleUpvote = async (itemName: string, sectionName: string) => {
  
}

export const handleDownvote = async (itemName: string, sectionName: string) => {

}
from flask import Flask, jsonify
from flask_cors import CORS
import boto3
import os
from dotenv import load_dotenv

load_dotenv()

app = Flask(__name__)
cors = CORS(app)

TABLE_NAME = os.environ['TABLE_NAME']

db = boto3.resource('dynamodb')
table = db.Table(TABLE_NAME)

def get_latest_data():
  try:
    response = table.scan()
    items = response.get('Items', [])

    if not items:
      print('No items available!')
    
    items_sorted = sorted(items, key=lambda x: x['timestamp'], reverse=True)

    latest_data = list(items_sorted[0]['data'].values())[0]
    return latest_data
  except Exception as e:
    print(str(e))
    return None
  
@app.route('/')
def home():
  return jsonify({'message': 'Hello World!'})

@app.route('/return_dates/<string:dining_hall>', methods=['GET'])
def return_dates(dining_hall):
  data = get_latest_data()
  if data is None:
    return jsonify({'error': 'No data available'}, 200)
  
  dates = data[dining_hall]['dates']
  return jsonify({'dates': dates})


@app.route('/return_location_halls', methods=['GET'])
def return_location_halls():
  data = get_latest_data()
  if data is None:
    return jsonify({'error': 'No data available'}, 200)

  dining_halls = list(data.keys())
  return jsonify({'dining_halls': dining_halls})


@app.route('/return_meals/<string:dining_hall>/<string:date>', methods=['GET'])
def return_meals_dictionary(dining_hall, date):
  data = get_latest_data()
  if data is None:
    return jsonify({'error': 'No data available'}, 200)
  
  menu_items = data[dining_hall]['menus']
  for index, item in enumerate(menu_items):
    if list(item.keys())[0] == date:
      return jsonify({'menu_items': menu_items[index][date]})
  return jsonify({'menu_items': menu_items})

if __name__ == '__main__':
  app.run(debug=True)

@app.route('/handle_upvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_upvote(dining_hall, item_name):
  pass

@app.route('/handle_downvote/<string:dining_hall>/<string:item_name>', methods=['POST'])
def handle_downvote(dining_hall, item_name):
  pass

Solution

  • You can add a retry mechanism in your data fetching logic to make sure the API is available before making requests.

    1. Add a retry mechanism in the fetch functions to handle the case when the API is not yet available.
    2. Make sure the client waits longer for the API to be available.
    3. Ensure your Flask API is deployed and fully up before starting your Next.js app.

    Next.js fetch functions

    import axios from 'axios';
    
    const API_BASE_URL = 'http://127.0.0.1:5000';
    const MAX_RETRIES = 5;
    const RETRY_DELAY = 1000;
    
    const fetchWithRetry = async (url, retries = MAX_RETRIES) => {
      try {
        const response = await axios.get(url);
        return response.data;
      } catch (error) {
        if (retries > 0) {
          console.warn(`Retrying... (${MAX_RETRIES - retries + 1}/${MAX_RETRIES})`);
          await new Promise(res => setTimeout(res, RETRY_DELAY));
          return fetchWithRetry(url, retries - 1);
        } else {
          console.error('Max retries reached');
          throw error;
        }
      }
    };
    
    export const fetchDiningHalls = async () => {
      try {
        const data = await fetchWithRetry(`${API_BASE_URL}/return_location_halls`);
        return data.dining_halls;
      } catch (error) {
        console.error('Error fetching dining halls', error);
        return [];
      }
    };
    
    export const fetchDatesForDiningHall = async (diningHall) => {
      try {
        const data = await fetchWithRetry(`${API_BASE_URL}/return_dates/${diningHall}`);
        return data.dates;
      } catch (error) {
        console.error(`Error fetching dates for dining hall ${diningHall}:`, error);
        return [];
      }
    };
    
    export const fetchMenuData = async (diningHall, date) => {
      try {
        const data = await fetchWithRetry(`${API_BASE_URL}/return_meals/${diningHall}/${date}`);
        return data.menu_items;
      } catch (error) {
        console.error(`Error fetching menu data for dining hall ${diningHall} on date ${date}:`, error);
        return null;
      }
    };
    

    Flask API to Return Consistent Responses

    @app.route('/return_dates/<string:dining_hall>', methods=['GET'])
    def return_dates(dining_hall):
        data = get_latest_data()
        if data is None:
            return jsonify({'error': 'No data available', 'dates': []}), 200
        
        dates = data.get(dining_hall, {}).get('dates', [])
        return jsonify({'dates': dates})
    
    @app.route('/return_location_halls', methods=['GET'])
    def return_location_halls():
        data = get_latest_data()
        if data is None:
            return jsonify({'error': 'No data available', 'dining_halls': []}), 200
    
        dining_halls = list(data.keys())
        return jsonify({'dining_halls': dining_halls})
    
    @app.route('/return_meals/<string:dining_hall>/<string:date>', methods=['GET'])
    def return_meals(dining_hall, date):
        data = get_latest_data()
        if data is None:
            return jsonify({'error': 'No data available', 'menu_items': []}), 200
        
        menu_items = data.get(dining_hall, {}).get('menus', [])
        for item in menu_items:
            if date in item:
                return jsonify({'menu_items': item[date]})
        return jsonify({'menu_items': []})