refactor: explore rethinking client-side fetching #780
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This pull request explores major changes to the way we fetch data in the CSXL app's frontend based on some lessons from the TypeScript world (from COMP 426) and some exploration I did over the summer with Dabblebase's stack (with a Python-based FastAPI server and Next.js / TypeScript frontend).
Rationale
One common pain point in the current fetching workflow between our Python-based backend and TypeScript-based frontend is the sharing of request / response types. These are defined in Pydantic models in the backend and TypeScript interfaces in the frontend. Any time one model is changed in the backend, the corresponding frontend model has to be changed.
Another common pain point that has arisen, especially as the app has grown in size (exhibit A: office hours feature) - we have some responses models that are shared between API endpoints, and custom
Summarymodels returned by other endpoints. This can cause confusion when updating models and, I argue, is an unnecessary tangling of concerns.Custom Request / Response Models Per Endpoint
To fix this immediate problem, I am thinking that we move away from having such groups of request / response Pydantic models and largely opt for defining separate models for each endpoint, one request (or more) and one response model. This allows for fine-tuning of data exposed from each endpoint helping to reduce data sent across the network to only what is necessary, ensure that changes to APIs remain isolated and do not accidentally affect other APIs, and provide easier naming (example,
GetSampleRequest,GetSampleResponse,CreateSampleRequestandCreateSampleResponseinstead of something likeSample,SampleDraft,SampleOverview).Client-side OpenAPI Type Generation
In addition, in this PR, I explore OpenAPI type generation for generating frontend types rather than manually defining our own interfaces in frontend models. Since FastAPI conforms to the OpenAPI standard, our endpoint exposes an
openapi.jsonfile (https://csxl.unc.edu/openapi.json) that describes exactly the shape of the models that we use on the backend, including request types and response types! OpenAPI TS is a great package that allows us to generate TypeScript types from an OpenAPI file, giving us the ability to sync our frontend types to match our backend types.This can be done with one command - when the development server is running:
This creates a single file,
schema.d.ts, which contains these types. This file can be found here.Full, Type-Safe Fetching + Improved DX
This new, generated schema provides us great DX improvements. Since we are using TypeScript and our types are defined, we can create abstractions that greatly improve how we fetch data. Imagine something like this:
where, as you type, the routes that match the
GETmethod auto-completes:where the returned data matches the type defined in the backend:
and where input parameters can be inputted directly into the HTTP client interface without any additional configuration will full type checking:
This is all possible - and demoed here. This PR includes an abstraction of Angular's
HTTPClient, calledOpenApiHTTPClient, that makes use of our generated OpenAPI schema to provide a full, type-safe API experience hereUsing TanStack Query (FKA React Query) with Angular
To even further improve fetching, we could consider adding in TanStack Query for Angular - a derivative of TanStack Query for React (formerly known as React Query). TanStack Query has great docs on what it is about, but it includes two core fetching niceties - 1) providing accessible fetching states, including
isLoading/isErroretc, allowing for better frontend design to react to these states - 2) caching of requests made to a specific endpoint. There are also many other features that are included, including managing optimistic updating, pagination, etc. I will provide the docs here.We use React Query in COMP 426, and it is become one of (if not the most) prevalent data-loading and caching libraries in the React world and is considered a must for large-scale projects there. I think that the Angular equivalent also provides some pretty nice features that we can use to improve UI and UX.
All Together
This PR contains an end-to-end example via the sample endpoint, accessible at
/api/sample:csxl.unc.edu/backend/api/sample.py
Lines 1 to 37 in d206194
Types were generated using:
Then on the frontend, we have the sample service:
csxl.unc.edu/frontend/src/app/sample/sample.service.ts
Lines 6 to 38 in d206194
Our page uses the TanStack Query example, called here in the component TS file:
csxl.unc.edu/frontend/src/app/sample/page/sample.component.ts
Line 21 in d206194
Once this query is called, we can access it on the frontend HTML like so:
csxl.unc.edu/frontend/src/app/sample/page/sample.component.html
Lines 1 to 15 in d206194
Notice how we have nice loading, error, and data states!
On the page itself, when we start loading for data, we get:
Once the data loads, we see:
Conclusion
While this would be quite a large refactor and likely adopted incrementally, I think we could get a great DX and UX improvement! This would potentially be great for the office hours feature.
Let me know your thoughts below! I am adding @jadekeegan for review here since this is an interesting connection to COMP 426.