Track flights with Express and React

swislokdev

Swislok-Dev

Posted on August 22, 2022

Track flights with Express and React

I recently built an application that is able to have a user input a flight number and return information about scheduled takeoff and landing times, as well as location, airport name, and the flight status.

Backend

The server is where all API calls are to be made. A server is not needed necessarily for this purpose, but allows for a database to be added at a later time with minimal conversion.

This will be a Node server using the Express.js framework to make things a little easier. If you have never used Express before, I made a bit of a starter guide to help out.

This app involves an API to source the flight information, you may choose a variety of different APIs which do similar things, but for this project I'll be using Flight Aware's API which requires a personal key.

Start of with creating a .env file in the root directory to set your API key.

Once you have your key stored, go over the documentation as there is quite a lot of information to get through to understand what data you should target.

Routes and Actions

The controller action will be named getFlightInfo, you haven't done so already, use npm install dotenv axios express to have the necessary packages installed.

flightsController.js

const axios = require("axios");
require("dotenv").config();

const getFlightInfo = (req, res) => {
  const ident = req.params.ident.toUpperCase();
  axios.get("https://aeroapi.flightaware.com/aeroapi/flights/" + ident, {
    headers: {
      Accept: "application/json; charset=UTF-8",
      "x-apikey": process.env.aeroapiKey,
    },
  })
  .then((resp) => {
    res.status(200).send(resp.data["flights"]);
  })
  .catch(() => {
    res.status(400).send({ message: "The flight nubmer is invalid" });
  });
};

module.exports = {
  getFlightInfo,
};
Enter fullscreen mode Exit fullscreen mode

flightsRoutes.js

const express = require("express")
const router = express.Router()
const { getFlightInfo } = require("../controllers/flightsController")

router.get("/:ident", getFlightInfo)

module.exports = router
Enter fullscreen mode Exit fullscreen mode

Endpoint "/flights/{:ident}"

This action will be able to find out info for a given flight number. The example that's used on the documentation site to test usage for the many endpoints is UAL4 which is the operator code followed by the flight number. In this case the ident being "UAL4" or "UA4", will give data about the flight from London, England to Houston, Texas.

NOTE:

This information will need to be parsed through as this flight flies once a day, EACH day (which is something I learned about flight numbers), in order to find the next or en route flight.

ASIDE
Flight numbers are static to the flight origin and destination, and with what airline is flying the route.

With this all up and configured, test it with a REST client to verify information is coming back successfully.

Frontend

Create a React directory or use the wonderful npx create-react-app <frontend-directory-name> to get React all ready to go. After the setup completes use npm install axios react-redux @reduxjs/toolkit to have axios, redux, and reduxjs/toolkit in the package files.

Call the API

Before making a call to the API, the next flight needs to be determined beforehand so that we have relevant data. I've created a features directory which holds a generic functions.js file, but you may want to place this in a helper directory of choice.

functions.js

const now = new Date();

export const convertISOTimeTDateTime = (time) => {
  return new Date(time);
};

export const findNextFlight = (flightsArray) => {
  let currentFlight;

  for (let i = 0; i < flightsArray.length; i++) {
    let flightTime = convertISOTimeToDateTime(flightsArray[i].scheduled_off);
    let nextFlight = flightsArray[i + 1];
    if (flightTime > now && nextFlight.status !== "Scheduled") {
      if (nextFlight.status.includes("En Route")) {
        currentFlight = nextFlight;
        return currentFlight;
      }
      currentFlight = flightsArray[i];
    } else {
    }
  }
  return currentFlight;
};
Enter fullscreen mode Exit fullscreen mode

Every date object returned from the API will be in ISO 8601 format, and for human readability, a conversion will be needed.

flightsService.js

export const getFlightInfo = async (flightNumber) => {
  const response = await axios.get(`/api/flights/${flightNumber}`)
  return response.data
}
Enter fullscreen mode Exit fullscreen mode

This will call the backend to fetch current flights based on the flight number used.

We'll create a slice function to use this call and load the redux store with all the flights data.

flightsSlice.js

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; 
import flightsService from "./flightsService";

const initialState = {
  message: "",
  flights: [],
};

export const getFlights = createAsyncThunk(
  "flights/:ident",
  async (flightNumber, thunkAPI) => {
    try {
      return await flightsService.getFlightInfo(flightNumber);
    } catch (error) {
      const message =
        (error.response &&
          error.response.data &&
          error.response.data.message) ||
        error.message ||
        error.toString();
      return thunkAPI.rejectWithValue(message);
    }
  }
);

export const flightsSlice = createSlice ({
  name: "flight",
  initialState,
  reducers: { reset: () => initialState},
  extraReducers: (builder) => {
    builder
      .addCase(getFlights.fulfilled, (state, action) => {
        state.flights = action.payload;
      })
      .addCase(getFlights.rejected, (state, action) => {
        state.message = action.payload;
      });
  },
});

export default flightsSlice.reducer;
Enter fullscreen mode Exit fullscreen mode

Display flight info

With all functions set and data ready create a home page or some place for the container.

Flights.jsx

import React, { useState } from "react";
import { getFlights } from "../features/flights/flightsSlice.js";
import { useDispatch, useSelector } from "react-redux";
import * as functions from "../features/functions";
import FlightItem from "../components/FlightItem";

function Flights() {
  const dispatch = useDispatch();
  const { flights, isLoading } = useSelector((state) => state.flightsState);
  const [flightNumber, setFlightNumber] = useState("");

  const listFlights = () => {
    const nextFlight = functions.findNextFlight(flights);
    return <FlightItem nextFlight={nextFlight} flightNumber={flightNumber} />;
  };

  const onChange = (e) => {
    setFlightNumber(e.target.value);
  };

  const onSubmit = (e) => {
    e.preventDefault();
    dispatch(getFlights(flightNumber));
  };

return (
    <section id="flights-page">
      <form id="flight-search" onSubmit={onSubmit}>
        <input
          type="text"
          name="flightNumber"
          autoFocus
          value={flightNumber}
          onChange={(e) => onChange(e)}
          placeholder="Enter flight number"
        />
        <input type="submit" value="Find Flight" />
      </form>
      <div className="info-text">
        {flights.length <= 0 ? <h2>Find a Flight</h2> : null}
      </div>
      {flights.length > 0 ? listFlights() : null}
    </section>
  );
}

export default Flights;
Enter fullscreen mode Exit fullscreen mode

This, with the algo to find the next flight, will present the FlightItem component.

FlightItem.jsx

import React from "react";

export default function FlightItem({ nextFlight, flightNumber }) {
  const convertDate = (date) => {
    if (date) {
      const toDateString = new Date(date);
      const newDate = toDateString.toUTCString().split("GMT")[0];
      return newDate;
    }
  };

  const displayFlightNumber = (flightNumber) => {
    return flightNumber.toUpperCase();
  };

  const placeDate = () => {
    let date = new Date();
    return convertDate(date);
  };

  return (
    <>
      <div className="grid-container flight-item-grid">
        <div className="flight-heading">
          <h2>
            Flight
            <br />
            {displayFlightNumber(flightNumber)}
          </h2>

          <h3>{nextFlight.status}</h3>
          <h4>{placeDate()} UTC</h4>
        </div>

        <div className="flight-grid-item">
          <h3 className="departure-heading">Departure</h3>
          <h3 className="arrival-heading">Arrival</h3>
          <table className="departure-table">
            <tbody>
              <tr>
                <td>Scheduled Takeoff</td>
                <td>{convertDate(nextFlight.scheduled_off)}</td>
              </tr>
              <tr>
                <td>Actual Takeoff</td>
                <td>{convertDate(nextFlight.actual_off)}</td>
              </tr>
              <tr>
                <td>Airport</td>
                <td>{nextFlight.origin.code_iata}</td>
              </tr>
              <tr>
                <td>Terminal</td>
                <td>{nextFlight.terminal_origin}</td>
              </tr>
              <tr>
                <td>Gate</td>
                <td>{nextFlight.gate_origin}</td>
              </tr>
            </tbody>
          </table>

          <table className="arrival-table">
            <tbody>
              {nextFlight.scheduled_on == nextFlight.estimated_on ? (
                <tr>
                  <td>Scheduled Arrival</td>
                  <td>{convertDate(nextFlight.scheduled_on)}</td>
                </tr>
              ) : (
                <tr>
                  <td>Estimated Arrival</td>
                  <td>{convertDate(nextFlight.estimated_on)}</td>
                </tr>
              )}
              <tr>
                <td>Actual Arrival</td>
                <td>{convertDate(nextFlight.actual_on)}</td>
              </tr>
              <tr>
                <td>Airport</td>
                <td>{nextFlight.destination.code_iata}</td>
              </tr>
              <tr>
                <td>Terminal</td>
                <td>{nextFlight.terminal_destination}</td>
              </tr>
              <tr>
                <td>Gate</td>
                <td>{nextFlight.gate_destination}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

There is a little bit a refactoring needed for the table of information displayed, but now there will be scheduled times of departure, scheduled (or estimated if the flight is delayed) time of arrival, and the airport information for anyone looking to find what terminal your ride would need to meet you (or any other reverse situation).

Feel free to leave comments or questions in case I have missed something along the way!

Happy flight tracking!

💖 💪 🙅 🚩
swislokdev
Swislok-Dev

Posted on August 22, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related