DEV Community

James Sinkala
James Sinkala

Posted on • Edited on

Consume RESTful APIs with fetch in Vue

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>
Enter fullscreen mode Exit fullscreen mode

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;
  }
Enter fullscreen mode Exit fullscreen mode

Initiate and execute Vue code:

  <script src="https://unpkg.com/vue@next"></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
      }),
      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>
Enter fullscreen mode Exit fullscreen mode

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
          })
Enter fullscreen mode Exit fullscreen mode

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}`)
Enter fullscreen mode Exit fullscreen mode

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.

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}` : ''))
  }
})
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
patarapolw profile image
Pacharapol Withayasakpunt

Just be aware that fetch is not status code aware. Instead, it will throw errors if strings cannot be deserialized as json.

Collapse
 
xinnks profile image
James Sinkala

I'm aware, just as noted on the differences with jQuery.ajax() on the mozzila page - "The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false)", Thanks for the reminder, maybe I should add a bit on that to the post.