I have a Vue3 app in which I use the following approach to API calls:
api folder, inside which lie functions that directly make a call to the backend. For example:export async function getCourses(): Promise<Course[]> {
const response = await axios.get(`/courses/`);
return response.data;
}
export async function getCourse(courseId: string): Promise<Course> {
const response = await axios.get(`/courses/${courseId}/`);
return response.data
}
export async function getExercises(courseId: string): Promise<Exercise[]> {
const response = await axios.get(`/courses/${courseId}/exercises/`);
return response.data
}
there a Vuex store, whose actions invoke those functions in the api folders and commit data to the store's state
Vue components access the store state and dispatch actions to it in order to retrieve data
The problem I have with this approach is that the api functions get extremely repetitive. I have to define 4 different functions just to enable CRUD on an entity, and all of them take almost the same parameters (for example, in my app, all resources are sub resource of a course and hence require the courseId parameter for fetching) and have the same structure (await an axios call and return the response's data).
I'd much prefer a method where I declaratively define the routes and the parameters they expect (as far as url components such as id's), and then be able to type-safely do something like this:
getApiService().courses(courseId).delete() // deletes a course
getApiService().courses(courseId).exercises().get(exerciseId) // returns an exercise
getApiService().courses(courseId).exercises().post(payload) // creates and returns an exercise
getApiService().courses(courseId).exercises().get(exerciseId).choices().list() // returns the list of choices for an exercise
// and so on
What would be a good way to accomplish this? Are there any existing packages that do this for Vue3?
The current code is clean and reasonably DAMP, which makes it much easier to maintain than possible DRY solutions. The dangers of enthusiastic DRY approaches are covered in this popular talk and explained in this article.
If these functions are known to always return raw data , they could be replaced with some DRYer helper function:
const apiCall = <T extends any>(url: string, options?: AxiosRequestConfig<T>): Promise<T> => { const response = await axios<T>(url, options); return response.data }Passing the HTTP method through options makes it more flexible to use.