Skip to content

API client

Jeroen edited this page Mar 16, 2026 · 6 revisions

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.

Using the useApi hook.

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>
    )
}

Using the useApiComponent hook

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

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
    })
}

Clone this wiki locally