• Consume RESTful APIs with fetch in Vue

    Consume RESTful APIs with fetch in Vue

    Dec 24, 2020Updated on Sep 19, 2021

    It's always fine to use feature rich HTTP clients such as axios to consume APIs for vue apps, but if you are stack size conscious to these extra dependencies to your app (for example axios 13.4kB minified) you can always use Javascript's own fetch API to carry out simple API calls.

    Fetch is as powerful as most HTTP clients enabling you to carry out what you mostly do with clients such as axios with minor differences here and there, as stated on Mozzila's page:

    "Fetch provides a generic definition of Request and Response objects (and other things involved with network requests). This will allow them to be used wherever they are needed in the future, whether it’s for service workers, Cache API, and other similar things that handle or modify requests and responses, or any kind of use case that might require you to generate your responses programmatically.

    It also defines related concepts such as CORS and the HTTP Origin header semantics, supplanting their separate definitions elsewhere.

    For making a request and fetching a resource, use the WindowOrWorkerGlobalScope.fetch() method. It is implemented in multiple interfaces, specifically Window and WorkerGlobalScope. This makes it available in pretty much any context you might want to fetch resources in."

    Whilst the negatives on using extra dependencies being almost negligible due to the hardware widely used these days to browse the web, cutting down some unused stack is always welcome, that's why I'll be demonstrating how to use Javascript's fetch API in a Vue app.

    On this tutorial we'll be creating a simple app that fetches countries based on the language spoken. We'll be using the free restcountries.eu API which requires no authentication.

    Set up the app's template:

    <div id="app">
        <div class="container">
          <div>
            <label for="languages">Select Language</label>
            <select id="languages" v-model="selectedLanguageCode"  @change="getCountries()">
              <option :value="language.code" v-for="(language, key) of languages" :key="key">[[language.name]]</option>
            </select>
          </div>
              <option :value="language.code" v-for="(language, key) of languages" :key="key">[[language.name]]</option>
            </select>
          </div>
          <div>
            <span v-if="loading">loading</span>
            <div class="countries-list" v-else>
              <div v-for="(country, key) of countries" :key="key" class="country">
                <img class="flag" :src="country.flag">
                <span>[[country.name]]</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    

    Apply some stylesheet:

      .container{
        justify-content: center;
        display: flex;
        flex-flow: column;
      }
      select{
        font-size: larger;
        margin-left: 10px;
      }
      .countries-list{
        display: table
      }
      .countries-list > *{
        display: block;
        margin-bottom: 5px;
        margin-top: 5px;
      }
      .country, .container{
        display: flex;
        align-items: center
      }
      .flag{
        height: 30px;
        width: 40px;
        margin-right: 10px;
      }
    

    Initiate and execute Vue code:

      <script src="https://unpkg.com/[email protected]"></script>
      <script>
        const fetchApiApp = {
          data: () => ({
            languages: [
              {code: 'en', name: 'English'},
              {code: 'fr', name: 'French'},
              {code: 'de', name: 'German'},
              {code: 'pt', name: 'Portugal'},
              {code: 'es', name: 'Spanish'},
              {code: 'sw', name: 'Swahili'}
            ],
            countries: '',
            selectedLanguageCode: '',
            loading: false
          }),
          delimiters: ["[[", "]]"],
          methods: {
            getCountries() {
              this.loading = true;
              fetch(`https://restcountries.eu/rest/v2/lang/${this.selectedLanguageCode}`)
              .then(response => response.json())
              .then(response => {
                this.loading = false;
                this.countries = response
              })
              .catch(err => {
                console.log(err.message || err);
                this.loading = false
              })
            }
          },
        };
    
        Vue.createApp(fetchApiApp).mount('#app')
      </script>
    

    As demonstrated, all that is needed to consume the API is the following:

    fetch(`https://restcountries.eu/rest/v2/lang/${this.selectedLanguageCode}`)
              .then(response => response.json())
              .then(response => {
                this.loading = false;
                this.countries = response
              })
              .catch(err => {
                console.log(err.message || err);
                this.loading = false
              })
    

    A breakdown of the above code: The simplest use of fetch() takes one argument — the path to the resource you want to fetch — and returns a promise containing the response (a Response object). -- developer.mozilla.org.

    In our case calling fetch() and passing in our endpoint as the argument:

    fetch(`https://restcountries.eu/rest/v2/lang/${this.selectedLanguageCode}`)
    

    Since what we get is just an HTTP response, not the actual JSON, we call the json() method .then(response => response.json()) defined on the Body of the Response object to extract the respected json data.

    The Body mixin also has similar methods to extract other types of body content such as arrayBuffer(), blob(), text() and formData()

    Finally, we proceed with the second then() method whose callback now contains our json data and proceed with utilizing it as per our app's needs.

    Below is a pen for the above code.

    See the Pen A pen by James Sinkala (@xinnks) on CodePen.

    Edit Note: Since the Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500 as I was reminded by @patarapolw there's a need to manually implement error handling. In our example before calling the json() method of the Response body, we can check to see if we have a friendly Response status and react accordingly.

    .then(response => {
      if(response.ok){
        return response.json()
      } else {
        throw new Error('Oops!' + (response.status ? ` seen a ${response.status}` : ''))
      }
    })