Chapter 7: API Data Fetching#
Welcome back! In Chapter 5: Main App Logic, we saw how our MainView
component acts as the central conductor, managing the application's state and coordinating different parts. In Chapter 6: Data Models, we learned about the blueprints (interfaces) that define the structure of the data used throughout our project, like metrics or gallery image details.
But where does some of this crucial data actually come from? We handle user file uploads (Chapter 2: User Input Handling (Files & Interaction)), but information about buildings, a list of available photos in a gallery, or details about a specific photo often doesn't live directly in our application or on the user's device. This data often resides on external servers.
What's the Problem?#
Imagine you've uploaded a photo, and our application has extracted the exact GPS coordinates where it was taken. To show you the 3D model of the building in that location (Chapter 3: 3D Geographic Visualization) and display its details (Chapter 4: Building Information Display), our app needs to know things like:
- What are the precise geographic boundaries (GeoJSON) of the building at those coordinates?
- What are its known properties, like height?
Similarly, if you want to see a gallery of all photos related to buildings, our app needs a list of those photos, their locations, and maybe preview images β data that is managed centrally on a server.
Our application can't just magically know this information. It needs a way to ask another computer (a server) for it.
What is API Data Fetching?#
API Data Fetching is the process of sending requests from our application (running in your browser) to a server somewhere else on the internet to retrieve data.
Think of it like this:
- Your Application (Client): You, sitting at a table in a restaurant, ready to order.
- The Server (API): The kitchen and the menu at the restaurant. It knows how to prepare different dishes (provide different types of data).
- The Request: You telling the waiter what you want to order ("I'd like the building data for coordinates X, Y, Z" or "Show me the list of all photos").
- The Response: The waiter bringing your food (the data) back from the kitchen.
An API (Application Programming Interface) is essentially the "menu" and the set of rules the server provides, telling you what kinds of requests you can make and what kind of responses you'll get back.
In web applications, these requests are typically made using the HTTP protocol, the same one your browser uses to load web pages. We send requests to specific URLs (addresses) provided by the server's API. The server then sends back data, often formatted as JSON (JavaScript Object Notation), which is easy for our JavaScript code to understand and use.
Our project uses standard web technologies to perform API data fetching.
How it Works in This Project: Using fetch
#
In modern web development, the built-in way to make HTTP requests from JavaScript is the fetch
API. It's like the standard tool for asking the server for data.
Let's look at two examples from our project: fetching building information and fetching gallery information.
Example 1: Fetching Building Information (src/api/fetch-building.ts
)#
This file contains the fetchBuilding
function, which is responsible for querying an external service to get details about a building near a given location and direction.
// src/api/fetch-building.ts (Simplified)
const API_URL = "https://api.buildingshistory.co.uk"; // The server's address
export const fetchBuilding = async (
lat: string,
lon: string,
camAltitude: string, // Not directly used in the request URL snippet shown
camDirection: string
) => {
// Prepare the URL with query parameters (lat, lon, direction)
const url = `${API_URL}/api/v1/building-part/nearest?latitude=${parseFloat(lat)}&longitude=${parseFloat(lon)}&imagedirection=${camDirection}`;
try {
// Use the fetch API to send a GET request to the server
const response = await fetch(url);
// Check if the request was successful (e.g., HTTP status 200-299)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// Parse the JSON data from the response body
const data = await response.json();
// Check if the response contains the expected building data
if (
data?.data?.building_part?.length > 0 &&
data.data.building_part[0]?.geojson
) {
// If data found, return a structured object (matching a simplified Data Model)
return {
geojson: data.data.building_part[0].geojson, // The building's GeoJSON
cameraGPSData: [ // Camera info derived from input
{
coordinates: [parseFloat(lon), parseFloat(lat), parseFloat(camAltitude)],
bearing: parseFloat(camDirection),
altitude: parseFloat(camAltitude),
},
],
};
}
// If no building data found in the response
return null;
} catch (error) {
// Handle any errors during the fetch process (network issues, server errors)
console.error("Failed to fetch building:", error);
// Maybe re-throw or return a specific error indicator
return null; // Return null or throw new Error("Fetch failed") for handling elsewhere
}
};
Let's break down this simplified code:
const API_URL = "...";
: This is the base address of the API server we want to talk to.async (lat, lon, ...)
: This is anasync
function, which makes working withfetch
(which is "asynchronous", meaning it takes time to complete and doesn't block the rest of your code) much easier usingawait
.const url = \
...`;: We build the specific URL for this request. Notice how we include the
lat,
lon, and
camDirectionvalues as "query parameters" (the stuff after the
?in the URL). This is how we send information *to* the server in a GET request. We also parse the string inputs (
lat,
lon) into numbers using
parseFloat` before putting them in the URL.const response = await fetch(url);
: This is the corefetch
call! It sends a GET request to the constructed URL and waits for the server's response.if (!response.ok) { ... }
: After getting a response, we check if it was successful (response.ok
is true for HTTP status codes 200-299). If not, we throw an error.const data = await response.json();
: The server sends the data in the body of the response, usually as JSON text.response.json()
reads that text and parses it into a JavaScript object.await
is used because reading and parsing the body also takes time.if (data?.data?.building_part?...) { ... }
: We check if the receiveddata
object has the structure we expect (Chapter 6: Data Models) and contains the building's GeoJSON. The?.
is "optional chaining", a safe way to access nested properties that might be missing without causing errors.return { geojson: ..., cameraGPSData: ... };
: If we found the data, we create a structured object (matching our internal data model) and return it.return null;
: If no building was found or the response structure was unexpected, we returnnull
.try { ... } catch (error) { ... }
: This block handles potential errors, like the server being down or a network problem. We log the error to the console.
This fetchBuilding
function encapsulates the entire process of asking the server for building data.
Example 2: Fetching Gallery Information (src/api/fetch-gallery.ts
)#
This file contains functions for interacting with a different API endpoint to manage photos in a gallery, including fetching lists of photos.
// src/api/fetch-gallery.ts (Simplified)
let endpoint = "https://pic2bim.co.uk/"; // Another server address
export const get_unassigned_photos = async (user_id: number, bearerToken: string) => {
console.log("Fetching unassigned photos..."); // Log for debugging
try {
const response = await fetch(
`${endpoint}comm_unassigned?user_id=${user_id}`, // URL with user ID
{
method: "POST", // This request uses the POST method
headers: {
"Content-Type": "application/json", // Tell the server we're sending JSON
"Authorization": `Bearer ${bearerToken}`, // Send the user's login token
},
// For POST, you might also send a request body, but this endpoint doesn't show one in the snippet.
}
);
let res: any = await response.json(); // Parse the JSON response
if (!response.ok) {
throw new Error(`Network response was not ok, status: ${response.status}`);
}
// Check the structure of the response data (referencing Data Models)
if (res?.photos_ids) {
return res.photos_ids; // Return the array of photo IDs
} else {
return []; // Return an empty array if no IDs found
}
} catch (error) {
console.error("Failed to fetch unassigned photos:", error);
return []; // Return empty array on error
}
};
export const get_photo = async (photo_id: number, bearerToken: string) => {
console.log(`Fetching photo details for ID: ${photo_id}`); // Log
try {
const response = await fetch(
`${endpoint}comm_get_photo?photo_id=${photo_id}`, // URL with photo ID
{
method: "POST", // This request also uses POST
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${bearerToken}`, // Requires authentication
},
}
);
let res: any = await response.json(); // Parse JSON response
if (!response.ok) {
throw new Error(`Network response was not ok, status: ${response.status}`);
}
// Check response structure (referencing Data Models)
if (res?.photo) {
// res.photo should match the GalleryImage structure simplified in Chapter 6
return res.photo; // Return the photo details object
} else {
return null; // Return null if photo not found
}
} catch (error) {
console.error(`Failed to fetch photo ${photo_id}:`, error);
return null; // Return null on error
}
};
Key points in these gallery fetching functions:
- Different Endpoint: They talk to a different
endpoint
URL (https://pic2bim.co.uk/
). An application often interacts with multiple APIs. method: "POST"
: UnlikefetchBuilding
which uses the default GET method (good for just retrieving data based on URL parameters), these functions explicitly use the POST method. POST is often used when sending more complex data to the server or when the request modifies something on the server (though here it's used for retrieval, possibly for historical reasons or specific API design).headers
: We includeheaders
in thefetch
options object."Content-Type": "application/json"
: This tells the server that if we were sending a request body (which isn't shown in these simplified snippets, but is common with POST), it would be in JSON format."Authorization": \
Bearer ${bearerToken}`: This is very important! Many APIs require authentication. After a user logs in ([Chapter 1: Application Views](01_application_views_.md)),
MainViewstores a
bearerToken([Chapter 5: Main App Logic](05_main_app_logic_.md)). We include this token in the
Authorization` header of subsequent requests. This tells the server who is making the request and confirms they are allowed to access the data. If the token is missing or invalid, the server will likely return an error (like status 401 Unauthorized).
- Parsing Response: Again,
await response.json()
is used to get the data from the server's response. - Returning Data: The functions return the relevant part of the response data (
res.photos_ids
orres.photo
) or an empty array/null on failure, matching the expected structure defined in our Data Models (Chapter 6: Data Models).
These functions demonstrate how our application makes specific requests to different API servers to get the data it needs for various features like displaying a single building or a photo gallery.
Connecting Fetching to Main App Logic#
As discussed in Chapter 5: Main App Logic, the MainView
component is the conductor that calls these API fetching functions.
When the handleImage
function in MainView
processes an image and extracts GPS coordinates, it calls a function like getPolygon
(which in turn calls fetchBuilding
). When the user navigates to the Showcase view, MainView
(or the MapShowcaseView
component it renders) might call get_unassigned_photos
to get the list of image IDs to display.
Here's a simplified flow showing API fetching triggered by image processing:
sequenceDiagram
participant User
participant PhotoView
participant MainView
participant API Fetching Function (e.g., fetchBuilding)
participant External Server
User->PhotoView: Uploads image
PhotoView->MainView: Calls onImageChange prop (setSelectedImg)
MainView->MainView: Updates selectedImg state
MainView->MainView: useEffect triggers image processing (handleImage)
MainView->MainView: handleImage extracts GPS data, calls getPolygon
MainView->API Fetching Function (fetchBuilding): Calls fetchBuilding(lat, lon, ...)
API Fetching Function (fetchBuilding)->External Server: Sends HTTP Request (GET URL)
External Server->External Server: Looks up building data
External Server-->API Fetching Function (fetchBuilding): Sends HTTP Response (JSON data)
API Fetching Function (fetchBuilding)->API Fetching Function (fetchBuilding): Parses JSON, checks data
API Fetching Function (fetchBuilding)-->MainView: Returns structured building data (or null)
MainView->MainView: Updates geo state, changes activeLayout to RESULT
MainView->MapResultView: Renders MapResultView with geo data
MapResultView-->>User: Displays 3D building on map
This sequence highlights how MainView
initiates the API call via the dedicated fetching function, the fetching function communicates with the external server, and the result comes back to MainView
to update the application's state and trigger view changes and rendering.
Error Handling#
Network requests are inherently unreliable β the server might be down, there might be internet issues, or the server might return an error indicating that the request was bad (e.g., wrong parameters) or unauthorized (missing token).
Both fetchBuilding
and the gallery functions use try...catch
blocks. The try
block contains the code that might fail (the fetch
call and processing the response). If an error occurs at any point within the try
block, the code inside the catch(error)
block is executed.
The catch
block typically logs the error (console.error
) so developers can see what went wrong. In a production application, you might do more sophisticated error handling, like showing an error message to the user (Chapter 5: Main App Logic manages an errMsg
state potentially for this) or retrying the request. For this project, logging and returning null
or an empty array on error allows the calling code (MainView
) to check the result and handle the lack of data gracefully (e.g., show a "No data found" message).
Checking response.ok
is also a basic but important form of error handling at the HTTP level, distinguishing between a successful request (even if the data inside isn't what we hoped for, like no building found) and a failed request (like a server error).
Conclusion#
In this chapter, we learned about API Data Fetching, the process by which our application communicates with external servers to retrieve necessary information like building details and photo gallery data. We saw how modern JavaScript uses the fetch
API to send HTTP requests (like GET and POST) to specific server URLs, how to include important details like query parameters and authentication tokens in headers, and how to receive and parse the JSON responses using await response.json()
. We also touched upon basic error handling with try...catch
.
These fetching functions act as dedicated modules (src/api/fetch-building.ts
, src/api/fetch-gallery.ts
), providing a clean way for our MainView
component (Chapter 5) to get external data and update the application state accordingly.
Now that we know how to get data from APIs and user input, the next chapter will focus on the crucial step of Geographic Data Processing β taking that raw or fetched data and transforming it into the formats and calculations our application needs for visualization and display.
Next Chapter: Geographic Data Processing
Generated by AI Codebase Knowledge Builder. References: 1(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/api/fetch-building.ts), 2(https://github.com/buildvoc/mapbox-gl_deck.gl_turf.js-ts/blob/3d8a4a53d878db3324af6466e0f99e5fb072bbe7/src/api/fetch-gallery.ts)