Restful Data Fetching

We recommend implementing the fetch logic and state management, as an independent component, separated from any specific UI component. This will make this component, as well as its future dependent UI components, more reusable and maintainable.

This page demonstrates the steps to creating a demo consuming a RESTful API. For a full example scope, see learnbit-vue.data-fetching

To make the whole RESTful demo works, we need to create the following components:

  1. An entity component for your data
  2. A reusable Vue composable to consume data via the RESTful API
  3. A Vue component to eventually render the data

If you want to go straightforwardly to see the final result in advance, run the following to generate a workspace with the components in this tutorial:

$bit
Copiedcopy

Create an entity component

Before creating Vue composables, we recommend creating an entity component for the data you will be fetching. This component serves a a source-of-truth for any component handling that type of data, client-side and server-side.

You can create a new entity component using the following command:

$bit
Copiedcopy

Implement the entity:

// type definition
export type BreweryType = {
  name: string;
  [x: string]: any;
};

// util functions
export function getNameSize(brewery: BreweryType): number {
  return brewery.name.length;
}
CopiedCopy

Most of the time, the type definition and necessary util functions would be enough.

It also would be great if you create some mocked data in brewery.mock.ts for further usage:

import { BreweryType } from "./brewery";

export const mockedBreweryList: BreweryType[] = [
  { name: "Brewery 1" },
  { name: "Brewery 2" },
];
CopiedCopy

See example component

Another common way to implement an entity is to use an ES class. In this way, you can couple the entity with its methods. However, this is not our focus in this guide.

Create a Vue composable

After having the entity component, you can create a Vue composable to fetch the data. Here is an example:

$bit
Copiedcopy

To implement this composable:

import { ref, Ref } from 'vue';
import { BreweryType } from '@learnbit-vue/data-fetching.entities.brewery';

export const BREWERIES_URL = 'https://api.openbrewerydb.org/breweries?by_city=san_francisco';

export function useBreweries(): Ref<BreweryType[] | undefined> {
  const breweries = ref<BreweryType[]>();

  const fetchBreweries = async () => {
    const response = await fetch(BREWERIES_URL);
    breweries.value = await response.json();
  };

  fetchBreweries();

  return breweries;
}
CopiedCopy

Testing your Vue composable

To test your Vue composable, you need create a composition.

<script setup lang="ts">
import { useBreweries } from "./brewery";
const breweries = useBreweries();
</script>

<template>
  <div>
    <div v-for="brewery in breweries" :key="brewery.id">
      {{ brewery.name }}
    </div>
  </div>
</template>
CopiedCopy

Run the dev server to see this composition rendered in your workspace UI:

$bit
Copiedcopy

Head over to your breweries.spec.ts file to create a programmatic test. Use the previously created composition to reduce the necessary setup needed for your test. And it would be great if you can use mock the fetch call with the data you mocked previously. So you can test it without relying on the network.

import { render } from "@testing-library/vue";
import { mockedBreweryList } from "@learnbit-vue/data-fetching.entities.brewery";
import { BREWERIES_URL } from "./brewery";
import { BasicBrewery } from "./brewery.composition";

// mock fetch API in test
globalThis.fetch = (url) =>
  Promise.resolve(
    {
      json: () => Promise.resolve(
        url === BREWERIES_URL
          ? mockedBreweryList
          : undefined
      ),
    } as Response);

describe("BasicBrewery", () => {
  it("should render", async () => {
    const { findByText } = render(BasicBrewery);

    expect(await findByText("Brewery 1")).toBeTruthy();
    expect(await findByText("Brewery 2")).toBeTruthy();
  });
});
CopiedCopy

See example component

Use the Vue composable as a dependency

Create a new component:

$bit
Copiedcopy

Implement the component. Use the Vue composable to implement data fetching and state management:

<script setup>
import { getNameSize } from '@learnbit-vue/data-fetching.entities.brewery';
import { useBreweries } from '@learnbit-vue/data-fetching.composables.brewery';

const breweries = useBreweries();
</script>

<template>
  <ul>
    <li v-for="brewery in breweries" :key="brewery.id">
      {{brewery.name}} | the name's length is {{getNameSize(brewery)}}
    </li>
  </ul>
</template>
CopiedCopy

See example component