Welcome to the final chapter of the Assistant project tutorial!
In the previous chapter, Session Authorization Context, we learned how to securely authenticate our requests using a reusable "wristband" context.
Now we have secure access, and we can fetch data. But there is one final problem: The API speaks a different language than our Application.
In this chapter, we will build the Data Normalization Layer.
Imagine you just landed at an airport in a foreign country. You have money in your pocket, but it is in your home currency. If you try to buy a coffee, the shopkeeper won't accept it.
You need to visit the Currency Exchange Booth. You hand them your money, and they hand back the local currency. Now, you can buy anything in the city without worrying about exchange rates or doing math in your head at every store.
The Technical Problem:
snake_case (words separated by underscores, like first_id or has_more).camelCase (capitalized words, like firstId or hasMore).The Solution: We create a Normalization Layer right at the "border" (immediately after the network request). It renames and reformats the data once, so the rest of our application can work with clean, native JavaScript objects.
The goal is to take the "Raw" data from the server and turn it into "Clean" data for the app.
| Raw API Data (Server) | Clean App Data (Client) |
|---|---|
first_id |
firstId |
has_more |
hasMore |
data |
events |
last_id |
(Ignored/Not needed) |
By doing this, we ensure our UI components never have to write code like props.first_id. That would look out of place in a TypeScript project!
As a user of our history module, you benefit from this automatically. You don't have to do anything!
When you call the functions we built in Latest Event Anchoring or Reverse Pagination Strategy, the data you receive is already clean.
// 1. Fetch the data
const historyPage = await fetchLatestEvents(ctx);
// 2. Use the data naturally
if (historyPage.hasMore) {
// We use 'hasMore', NOT 'has_more'
console.log("There is more history to load!");
}
console.log(historyPage.firstId); // NOT 'first_id'
Why is this better?
If the API changes its field name from has_more to is_there_more_data next year, we only have to change our code in one place (the Normalization Layer). The rest of your application won't break.
Let's look at the flow of data as it crosses the border from the Internet into our Application.
axios.
This logic is embedded inside the fetchPage function in sessionHistory.ts. It acts as the "Exchange Booth."
1. Defining the Raw Format
First, we define what the API sends us. This acts as our contract with the server.
// File: sessionHistory.ts
// This matches the JSON coming from the server EXACTLY
type SessionEventsResponse = {
data: SDKMessage[]
has_more: boolean // Snake case
first_id: string | null // Snake case
last_id: string | null // We might receive this, even if we don't use it
}
2. Defining the Clean Format
Next, we define what our application wants to use.
// File: sessionHistory.ts
// This is the clean structure our UI expects
export type HistoryPage = {
events: SDKMessage[] // Renamed from 'data' to be more descriptive
firstId: string | null // Camel case
hasMore: boolean // Camel case
}
3. Performing the Exchange
Finally, inside fetchPage (our Defensive API Wrapper), we perform the conversion right before returning.
// File: sessionHistory.ts -> inside fetchPage function
// ... (Network request happens above) ...
// NORMALIZATION HAPPENS HERE:
return {
// 1. Rename 'data' to 'events' and ensure it is an array
events: Array.isArray(resp.data.data) ? resp.data.data : [],
// 2. Map snake_case to camelCase
firstId: resp.data.first_id,
// 3. Map snake_case to camelCase
hasMore: resp.data.has_more,
}
data. That is very generic. We rename it to events so the code is easier to read. We also add a safety check (Array.isArray) to ensure we never crash if the API sends null.first_id becomes firstId.You have completed the Data Normalization Layer.
By treating the API response as "raw material" and refining it immediately, we protect our application from inconsistent naming and messy data structures.
Congratulations! You have built a robust, production-grade chat history loader. Let's review what you have accomplished:
You now possess the tools to build a seamless, resilient chat experience. Happy coding!
Generated by Code IQ