Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Ajax with Axios

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Our app is really starting to come together. I think it's time to make our data dynamic: the categories on the sidebar are hardcoded and the products aren't even loading yet. Let's make some Ajax calls!

Installing Axios

To make those, we're going to use a library called Axios. To install it, open a new terminal tab and run:

yarn add axios --dev

The --dev part isn't very important.

The other popular option for Ajax calls is to use fetch() instead of Axios. fetch() is actually a built-in JavaScript function, which means you don't need any outside library. However, if you need to support IE 11, then you will need a polyfill to use it. Both Axios and fetch are great options.

Investigating our API

For our first trick, let's load products onto the page. Our app already has an API - powered by API Platform - and you can see its docs by going to /api. Scroll down to the section about products and expand the endpoint for GET /api/products.

The best way to see how this works is... to try it! Let's see... hit Execute and... here's the response body. We already have a set of products in the database.

If you haven't used API Platform before - it's no problem. But, the structure with @id, @context, hydra:member and other keys might look odd. This is JSON, but it's using a format called JSON-LD Hydra, which is basically JSON with extra metadata: each response will have the same structure with extra fields to give you more info. It's super handy.

Now, notice that the URL to the endpoint is /api/products. But if we put /api/products in our browser... we don't see JSON! It's the same documentation page! That's because API Platform realizes - by reading the Accept header - that we're requesting this from a browser and so it returns HTML. When we request this from Axios with no Accept header, we'll get back JSON.

But if you ever want to see the JSON in a browser to see how it looks, there's a hack: add .jsonld to the end of the URL. This is our endpoint.

Let's go all the way back to our homepage and... I'll re-open the browser dev tools.

Making the Ajax Call from mounted()

Ok, when we load products, which component is going to need that data? The top-level products.vue component renders the sidebar and catalog. We could load the products here... but we won't actually need them. Hold Command or Ctrl and click <catalog to jump to that component.

Ah, this is the component that needs the products data.

Here's the goal: as soon as Vue loads this component, we'll start the Ajax call so that we can load the products as quickly as possible. Fortunately, Vue allows us to run code during its startup process, and there are two main "hook" points: mounted and created. We'll talk more about these later but Vue considers your component mounted when it's actually added to the page - like, in products.js when we call .$mount().

To run code right after our component is mounted, all we need to do is create a function called mounted(). Inside, we'll make the Ajax call.

How? First, at the top of the script section import axios from 'axios'.

... lines 1 - 22
<script>
import axios from 'axios';
... lines 25 - 42
</script>

Then, using Axios is beautifully simple: axios.get('/api/products'). And like every Ajax library, this will return a Promise, which you can learn all about in a JavaScript Tutorial here on SymfonyCasts.

To use the Promise, add .then(), and pass an arrow function with response as the argument. Let's console.log(response) to see what it looks like.

... lines 1 - 36
mounted() {
axios.get('/api/products').then((response) => {
console.log(response);
});
},
... lines 42 - 44

Testing time! Back over on the browser, click to view the console. Thanks to hot module replacement... that already ran! But to make the flow more realistic, let's refresh the page.

Now... boom! The log shows up almost instantly. The response is an object with headers, status and other things. What we want is data. One of the nice features of Axios is that it decodes the JSON automatically.

When you're working with JSON-LD Hydra like this, the collection of items is stored on a hydra:member property. Yep, it's an array with 12 products. We have product data!

Next, this is working great, but I'm going to choose a slightly different syntax for handling Promises: async and await. Then, we'll use our brand new data to render those products onto the page.

Leave a comment!

9
Login or Register to join the conversation
davidmintz Avatar
davidmintz Avatar davidmintz | posted 1 year ago | edited

just wondering why we yarn add axios --dev. I thought the --dev meant you only want it in the dev environment. I see that yarn help add says it "save[s] package to your devDependencies" but I'm not really sure what that means in practical terms.

Reply

Hey David,

Actually, all our JS dependencies are dev dependencies if you take a look at package.json file :) That's because technically there are dev deps, nothing is revealed to the user directly, everything is passed though Webpack Encore, i.e. precompiled by Webpack Encore, and technically Webpack Encore decides what to reveal to the end user. And so, it's not that much important where to put that JS dependency - into "dependencies" or in "devDependencies" section. You can do whatever you like, but you need to understand this concept with Webpack Encore. And it's kind of a good practice to put everything in devDependencies when you're handling your assets via Webpack Encore.

I hope this helps!

Cheers!

Reply
davidmintz Avatar
davidmintz Avatar davidmintz | Victor | posted 1 year ago

Yeah thank you. That's interesting indeed. Now I'm slightly less ignorant than I was a couple minutes ago!

Reply

Hey David,

Haha, great! :) But in short, that doesn't matter much and works both ways, depends on your preferences ;)

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | posted 2 years ago | edited

Hi guys.
I got an error at the last step:
`xhr.js:184 GET http://127.0.0.1:8000/api/products 500 (Internal Server Error)
createError.js:16 Uncaught (in promise) Error: Request failed with status code 500

at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:69)`

Probably because of database connection:
hydra:description: "An exception occurred in driver: SQLSTATE[HY000] [2002] Connection refused"

But locally via browser it works! http://127.0.0.1:8000/api/products
Any ideas?

Reply

Hey triemli!

Hmm, that's super weird. So.... if you browser to http://127.0.0.1:8000/api/products it works... but if you send an AJAX request to POST http://127.0.0.1:8000/api/products it fails with "Connection refused?". There should really be no difference between the two from an infrastructure standpoint: in both cases, your browser is making a web request to the same backend. Do you get this error consistently?

Cheers!

Reply
triemli Avatar
triemli Avatar triemli | weaverryan | posted 2 years ago | edited

Actually mu fault, I didn't see the mmigrations there.

I created docker-compose yaml with:


version: '3'
services:
    mysql:
        image: mysql:5.7
        restart: always
        volumes:
            - ./data:/var/lib/mysql
        environment:
            MYSQL_ROOT_PASSWORD: "root"
            MYSQL_USER: 'root'
            MYSQL_PASSWORD: 'root'
        networks:
            - backend
        ports:
            - 3306:3306

networks:
    backend:
        driver: bridge

Up it, made migrations and success xD

1 Reply
triemli Avatar
triemli Avatar triemli | weaverryan | posted 2 years ago | edited

Ah sorry, misinformed a bit:
/api/products just page works. On json also connection refused:

` "@context": "/api/contexts/Error",
"@type": "hydra:Error",
"hydra:title": "An error occurred",
"hydra:description": "An exception occurred in driver: SQLSTATE[HY000] [2002] Connection refused",
"trace": [

{
  "namespace": "",
  "short_class": "",
  "class": "",
  "type": "",
  "function": "",
  "file": "/mnt/d/domains/vue/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/AbstractMySQLDriver.php",
  "line": 93,
  "args": []
},`

But where it goes to connect?
Actually we don't up docker or any database services.

Reply
Sjoerd Avatar
Sjoerd Avatar Sjoerd | triemli | posted 2 years ago | edited

Ensure the database is there.
Update the .env with valid database connection information.
Afterwards load the fixtures.

php bin/console doctrine:fixtures:load

Now you can proceed (or at least I could)

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is also built to work with Vue 3!

What JavaScript libraries does this tutorial use?

// package.json
{
    "devDependencies": {
        "@symfony/webpack-encore": "^0.30.0", // 0.30.2
        "axios": "^0.19.2", // 0.19.2
        "bootstrap": "^4.4.1", // 4.5.0
        "core-js": "^3.0.0", // 3.6.5
        "eslint": "^6.7.2", // 6.8.0
        "eslint-config-airbnb-base": "^14.0.0", // 14.1.0
        "eslint-plugin-import": "^2.19.1", // 2.20.2
        "eslint-plugin-vue": "^6.0.1", // 6.2.2
        "regenerator-runtime": "^0.13.2", // 0.13.5
        "sass": "^1.29.0", // 1.29.0
        "sass-loader": "^8.0.0", // 8.0.2
        "vue": "^2.6.11", // 2.6.11
        "vue-loader": "^15.9.1", // 15.9.2
        "vue-template-compiler": "^2.6.11", // 2.6.11
        "webpack-notifier": "^1.6.0" // 1.8.0
    }
}
userVoice