-
Notifications
You must be signed in to change notification settings - Fork 0
API client
For the web client, we made a custom system for creating api requests. The systems aims to be flexible & versatile whilst creating a type-safe API that is written once and can be used everywhere.
Terms:
-
command- Sending any type of API request to the server.
The API client is based around 1 central component, the RequestProcessor. The processors provide a type-safe way to send requests, they also send, process and translate different types of API responses to an easy-to-use output meant for other components to hook into. But most important of all, they provide a static contract to the endpoints of the API-gateway.
The Request Processor can be used on their own, although this is not recommended. There are 2 hooks that use the processors: the useApi hook and the useApiComponent.
The useApi provides an easy-to-use way to send commands via request processors. This hook manages the whole state of a request, so you don't have to. The useApi returns an object with the following propeties:
{
loading, // See if a request is currently getting send
data, // The returned data of the object
error, // If an error happened, gets set to the latest error
runCommand // A function to run the request processor
}It is recommended to name this API the same as the name of the command, e.g. const getCurrentUser = useApi(ScrumdappAPI.getCurrentUser())
Here's an example usage of the hook:
export function SomeComponent({groupId}: {groupId: number}) {
const getGroup = useAPI(ScrumdappAPI.getGroup())
useEffect(() => {
getGroup.runCommand(groupId)
}, [getGroup.runCommand])
if (getGroup.loading) {
return <span>Loading...</span>
}
return (
<span>
{getGroup.data.name}
</span>
)
}Technically speaking, this isn't a hook. It's used as a hook to make it easier to work with the types, but lets refer to it as a hook for now.
The useApiComponent hook returns a React component that wraps the useApi function in an easy-to-use and type safe component. The main advantage to this component is that it automatically fetches the data once when it renders, it also automatically shows the loading & error screens without requiring any further work. For naming the variable, I recommend putting the command name in pascal case and adding the Component suffix.
Here's an example on how to use it
export function SomeComponent({groupId}: {groupId: number}) {
const GetGroupComponent = useApiComponent(ScrumdappAPI.getGroup())
return (
<GetGroupComponent input={[groupId]}>
{group => (
<span>
Group: {group.name}
</span>
)}
</GetGroupComponent>
)
}Creating a request processor is quite simple. The first thing you need to do is discuss with a back-ender what routes are needed. Then, when an agreement has been met. Make the interfaces, see the files in src/js/models for examples on how to make them. Next, to keep all the commands in 1 place, under the namespace ScrumdappApi in src/js/hooks/api/scrumdappApi, make a new function and set the types and routes correctly to how it was discussed with the back-ender.
This Api will be made simpler, whilst making this I see this is rather complex :/
As an example of a request processor.
export function updateGroup(): RequestProcessor<[groupId: number, newData: PatchGroup], Group> {
// (^ This section is the inputs) (^ The ouput type)
return (([groupId, newData]) => {
// ^ The types provided by the RequestProcessor inputs ^
return makeApiRequest("PATCH", "/groups/{group.id}", {
body: newData // Optioneel -- geeft de body aan bij een POST/PUT/PATCH request
params: { "{group.id}": groupId } // Optioneel -- Assign path variables in de URL. b.v. group.id.
query: { "features": "checkins,checkins.presence" } // Optioneel -- Voeg query parameters to aan de url. (resultaat: /groups/123?features=checkins,checkins.presence)
})
})
}
export function getCurrentUser(): RequestProcessor<never, User> {
return (() => {
return makeApiRequest("GET", "/users/@me") // As you can see, the third argument is completely optional and can be removed if you don't need a body, path variables or query variables
})
}