Home / Articles / Using Native Fetch With AlpineJs

Using Native Fetch With AlpineJs

February 28, 2021
9 min. read

One of the most common web app patterns involves collecting data from a form and submitting it to a REST API or, the opposite, populating a form from data originating from a REST API. This pattern can easily be achieved in Alpinejs using the native javascript Fetch Api. As a bonus, I describe the fetch async version at the end of the article.

If you are not familiar with the Alpinejs, checkout my introduction article and how can help you introduce simple interactions in your web page very easily.

If you do not like using the native fetch, there is the magic helper $fetch which is using Axios behind the scenes. I prefer to use the native javascript approach to a library that adds another ~15KB payload to my build. Nowadays, with the ability to cancel the fetch, I do not see a reason not to use it. But if you like the Axios library, know that there is an Alpinejs way to it, too.

Backend JSON Server

For testing purposes, we will use a free fake API powered by JSON Server and LowDB. Check these projects out cause they are amazing and may help in your endeavours. In this example, we hit the free fake API at https://jsonplaceholder.typicode.com/users/1, which returns the following data:

 1{
 2  "id": 1,
 3  "name": "Leanne Graham",
 4  "username": "Bret",
 5  "email": "Sincere@april.biz",
 6  "address": {
 7    "street": "Kulas Light",
 8    "suite": "Apt. 556",
 9    "city": "Gwenborough",
10    "zipcode": "92998-3874",
11    "geo": {
12      "lat": "-37.3159",
13      "lng": "81.1496"
14    }
15  },
16  "phone": "1-770-736-8031 x56442",
17  "website": "hildegard.org",
18  "company": {
19    "name": "Romaguera-Crona",
20    "catchPhrase": "Multi-layered client-server neural-net",
21    "bs": "harness real-time e-markets"
22  }
23}

We will only use the name,email, address.city, and address.street attributes from the JSON not to complicate the form structure too much.

Below you may check up the final result. We use the native JavaScript fetch in an Alpinejs component to receive data to populate the form. You may change the form data, and by clicking on the “update info” button, we are making a POST request to an API Endpoint with the updated data. The endpoint does not exist. Therefore, it returns an error, but I want to show how to use the fetch to make a post request.

Form Design using tailwindcss

Initially, let’s build our form using the tailwindcss. Tailwindcss is a utility-first CSS that let you compose your design directly in your markup. It has the same philosophy as the Alpinejs so, they often grouped together.

 1<div class="flex flex-col content-center justify-center h-screen">
 2    <div class="flex flex-col self-center px-8 pt-6 pb-8 my-2 mb-4 bg-white rounded shadow-md max-w-7xl">
 3        <div class="mb-6 md:flex">
 4            <div class="px-3 mb-6 md:w-1/2 md:mb-0">
 5                <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="name">
 6                    Name
 7                </label>
 8                <input
 9                    class="block w-full px-4 py-3 mb-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-red"
10                    required type="text" id="name">
11            </div>
12            <div class="px-3 md:w-1/2">
13                <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="email">
14                    email
15                </label>
16                <input
17                    class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
18                    required type="email" id="email">
19            </div>
20        </div>
21        <div class="mb-6 md:flex">
22            <div class="px-3 mb-6 md:w-1/2 md:mb-0">
23                <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="address">
24                    Address
25                </label>
26                <input
27                    class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
28                    required type="text" id="address">
29            </div>
30            <div class="px-3 md:w-1/2">
31                <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="city">
32                    City
33                </label>
34                <input
35                    class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
36                    required type="text" id="city">
37            </div>
38        </div>
39        <div class="flex justify-end mb-2 md:flex">
40            <button
41                class="inline-block px-6 py-2 text-xs font-medium leading-6 text-center text-white uppercase transition bg-blue-700 rounded shadow ripple hover:shadow-lg hover:bg-blue-800 focus:outline-none">
42                Update Info
43            </button>
44
45        </div>
46    </div>
47</div>

The above markup produces the following responsive form design:

The form design using tailwindcss

Form Initialization

Next, we create an alpinejs component by defining an object person in the x-data attribute, which it stores all the information needed for the form:

1x-data="{ person : {
2          name: null,
3          email: null,
4          address: null,
5          city: null
6        }}" 

The best time to get our values from the REST API and display them in the form is during the alpinejs component’s initialization using the x-init attribute:

 1 x-init="fetch('https://jsonplaceholder.typicode.com/users/1')
 2          .then(response=> {
 3              if (!response.ok) alert(`Something went wrong: ${response.status} - ${response.statusText}`)
 4              return response.json()
 5          })
 6          .then(data => person = {
 7              name: data.name,
 8              email: data.email,
 9              address: data.address.street,
10              city: data.address.city
11          })"

We are using the fetch to hit our REST endpoint, which returns a promise. At first, we resolve the response from the endpoint and check if it was a success. If not, we alert the user about the problem. To extract the JSON data from the response object, we use the json() command, which also returns a promise that it resolves at last to a javascript object containing the data returned from the endpoint. Finally, we are populated our person object with these data.

Two-way Form binding

To display the data in the form, we are binding the person object attributes to the corresponding input text boxes in our form using the x-model attribute. The x-mode attributes create a two-way binding between the input elements’ value and the value of corresponding person attributes. For example, for the name input box, we are two-way binding to the person.name:

1<input 
2        x-model="person.name"
3        class="block w-full px-4 py-3 mb-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-red"
4        required type="text" id="name">

Form Submission

Finally, by clicking the “UPDATE INFO” button, we are sending the form data to a REST endpoint by a POST request through the fetch command. To do that, we attach a listener to the click event using the Alpinejs event syntax @:event:

 1<button @click="fetch('/update', {
 2                    method: 'POST',
 3                    body: JSON.stringify(person)
 4                })
 5                .then(response => {
 6                    if (response.ok) alert('Updated Successfully!')
 7                    else alert(`Something went wrong: ${response.status} - ${response.statusText}`)
 8                })" 
 9        class="inline-block px-6 py-2 text-xs font-medium leading-6 text-center text-white uppercase transition bg-blue-700 rounded shadow ripple hover:shadow-lg hover:bg-blue-800 focus:outline-none">
10    Update Info
11</button>

The fetch command will send a POST response to a non-existent endpoint, so the output will be an error alert. If you want to pass a header to the request, you may add it in the options object. For learning about available full options, you may check out the mozilla MDN web docs

 1<button @click="fetch('/update', {
 2                    method: 'POST',
 3                    body: JSON.stringify(person),
 4                    headers: {
 5                        'Content-Type': 'application/json'
 6                        }
 7                })
 8                .then(response => {
 9                    if (response.ok) alert('Updated Successfully!')
10                    else alert(`Something went wrong: ${response.status} - ${response.statusText}`)
11                })" 
12        class="inline-block px-6 py-2 text-xs font-medium leading-6 text-center text-white uppercase transition bg-blue-700 rounded shadow ripple hover:shadow-lg hover:bg-blue-800 focus:outline-none">
13    Update Info
14</button>

The final code is:

 1    <div class="flex flex-col content-center justify-center h-screen">
 2        <div class="flex flex-col self-center px-8 pt-6 pb-8 my-2 mb-4 bg-white rounded shadow-md max-w-7xl" 
 3            x-data="{ person : {
 4                        name: null,
 5                        email: null,
 6                        address: null,
 7                        city: null
 8                        }}" 
 9            x-init="fetch('https://jsonplaceholder.typicode.com/users/1')
10                    .then(response=> {
11                        if (!response.ok) alert(`Something went wrong: ${response.status} - ${response.statusText}`)
12                        return response.json()
13                    })
14                    .then(data => person = {
15                        name: data.name,
16                        email: data.email,
17                        address: data.address.street,
18                        city: data.address.city
19                    })">
20            <div class="mb-6 md:flex">
21                <div class="px-3 mb-6 md:w-1/2 md:mb-0">
22                    <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="name">
23                        Name
24                    </label>
25                    <input 
26                        x-model="person.name"
27                        class="block w-full px-4 py-3 mb-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-red"
28                        required type="text" id="name">
29                </div>
30                <div class="px-3 md:w-1/2">
31                    <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="email">
32                        email
33                    </label>
34                    <input x-model="person.email"
35                        class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
36                        required type="email" id="email">
37                </div>
38            </div>
39            <div class="mb-6 md:flex">
40                <div class="px-3 mb-6 md:w-1/2 md:mb-0">
41                    <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="address">
42                        Address
43                    </label>
44                    <input x-model="person.address"
45                        class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
46                        required type="text" id="address">
47                </div>
48                <div class="px-3 md:w-1/2">
49                    <label class="block mb-2 text-xs font-bold tracking-wide uppercase text-grey-darker" for="city">
50                        City
51                    </label>
52                    <input x-model="person.city"
53                        class="block w-full px-4 py-3 border rounded appearance-none bg-grey-lighter text-grey-darker border-grey-lighter"
54                        required type="text" id="city">
55                </div>
56            </div>
57            <div class="flex justify-end mb-2 md:flex">
58                <button @click="fetch('/update', {
59                                    method: 'POST',
60                                    body: JSON.stringify(person),
61                                    headers: {
62                                        'Content-Type': 'application/json'
63                                        }
64                                })
65                                .then(response => {
66                                    if (response.ok) alert('Updated Successfully!')
67                                    else alert(`Something went wrong: ${response.status} - ${response.statusText}`)
68                                })"  
69                        class="inline-block px-6 py-2 text-xs font-medium leading-6 text-center text-white uppercase transition bg-blue-700 rounded shadow ripple hover:shadow-lg hover:bg-blue-800 focus:outline-none">
70                    Update Info
71                </button>
72
73            </div>
74        </div>
75    </div>
76</body>

Using async

Although, in the code above we are resolving the fetch promise through its classic approach, the async/await have achieve a very wide support on the modern browsers. So there nothing holding back to not use it (except for supporting legacy browsers). The only consideration is that alpinejs expects a function expression in its attributes instead of a function declaration. That means that we need to surround the arrow function with a self-execute function. Therefore, our x-init code is transformed:

 1x-init="(async () => {
 2                const response = await fetch('https://jsonplaceholder.typicode.com/users/1')
 3                if (! response.ok) alert(`Something went wrong: ${response.status} - ${response.statusText}`)
 4                data = await response.json()
 5                person = {
 6                            name: data.name,
 7                            email: data.email,
 8                            address: data.address.street,
 9                            city: data.address.city
10                        }
11            })()"

And the async/await version for the POST request code is:

1click="(async () => {
2                const response = await fetch('/api/update', {
3                    method: 'POST',
4                    body: JSON.stringify(person)
5                })
6                if (response.ok) alert('Updated Successfully!')
7                else alert(`Something went wrong: ${response.status} - ${response.statusText}`)       
8            })()"

The final example using the async/await version is:

As you see, using the fetch command is very straightforward, and I think it is the preferred way to deal with REST APIs in our Alpinejs components.

Share:

comments powered by Disqus

Also Read:

One of the most frequent requirements when writing AlpineJs components is the communication between them. There are various strategies for how to tackle this problem. This article describes the four most common patterns that help pass information between different Alpinejs components.
Vue’s primary motivation behind the introduction of Composition API was a cost-free mechanism for reusing logic between multiple components or apps. Is there a way to use that approach for AlpineJs without sacrificing its simplicity?
Most uncomplicated today web sites, like this blog, for example, or a landing page, do not need extensive javascript frameworks with complicated build steps. The Alpine.js framework is a drop-in library that replaces the jQuery query-based (imperative) approach with the tailwindcss-inspired declarative approach on the DOM using a familiar Vue-like syntax.