Track flights with Express and React
Tags: node, react, webdev, tutorial
Published: 2022-08-22 02:30:58

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](https://dev.to/swislokdev/what-is-expressjs-c6) 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](https://flightaware.com/commercial/aeroapi/documentation2.rvt) 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

```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,
};
```

#### flightsRoutes.js

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

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

module.exports = router
```

#### 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

```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;
};
```

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

#### flightsService.js

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

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
```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;
```

### Display flight info

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

#### Flights.jsx

```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;
```

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

#### FlightItem.jsx

```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>
    </>
  );
}
```

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!