Handle HTTP requests in a Laravel Vue.js app with Axios

Introduction

Introduction

In every application, HTTP requests play an essential role as they allow us to communicate with the server, tackling some API endpoints and much more. Have you ever wondered how you can handle HTTP requests in your app and this seamlessly? Well Axios is your friend. What is Axios and why you should pay it special attention ?

Axios is a JavaScript library designed to handle HTTP requests in the browser and Node.js ecosystem. It means that Axios will help you make HTTP calls to your backend code and save you headaches. Is that not nice 😊?!

In this tutorial, we’ll build a working app with Laravel and Vue.js and see how we can handle our HTTP requests with the Axios library. We’ll also manage the state with Vuex library.

Here is a preview of what we’ll get at the end :

axios-laravel-demo

Prerequisites

Before you jump in this tutorial, make sure you have npm or Yarn installed on your machine as we’ll be using them throughout this course to install dependencies. To follow along you need the follow requirements:

Installing frontend dependencies

Open your package.json file at the root of your folder and paste the following code, then run npm install or yarn add to install the packages needed for the app.

1{
2        "private": true,
3        "scripts": {
4            "dev": "npm run development",
5            "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6            "watch": "npm run development -- --watch",
7            "watch-poll": "npm run watch -- --watch-poll",
8            "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9            "prod": "npm run production",
10            "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11        },
12        "devDependencies": {
13            "axios": "^0.18",
14            "bootstrap": "^4.0.0",
15            "popper.js": "^1.12",
16            "cross-env": "^5.1",
17            "jquery": "^3.2",
18            "laravel-mix": "^2.0",
19            "lodash": "^4.17.5",
20            "vue": "^2.5.7",
21            "vuex": "^3.0.1"
22        }
23    }

Getting started with Laravel

Now, this is the moment you’ve been longing for, the coding part. Let’s roll up our sleeves and dive into the code.

Open up your terminal, and run the following command to create a new Laravel project as well as required dependencies on your machine:

    laravel new laravel_vue_axios

Note : This assumes you have already installed Laravel and Composer on your local machine.

Once the installation is finished run the following command to move to your app directory:

    cd laravel_vue_axios

Now, from your project directory, run this command in order to see our brand new project rendered in the browser:

    php artisan serve 

You should see this image in your browser otherwise get back to previous steps, and make sure you follow them carefully.

axios-laravel-homepage

Setting up the database

In this tutorial we’ll use a MySQL database, however you can use any database you feel comfortable with. Refer to this section on Laravel website for more relevant information.

Building models and seeding our database

If you already worked with Laravel you know that it has a great command-line tool to generate models and so on. First run this command:

    php artisan make:model Post -mc

This command tells Laravel to generate a Post model for us, the -mc flags indicates that it should also generate migration as well as a controller named PostController. We’ll take a look at these files further in the tutorial.

Next, copy and paste this piece of code into your post migration file

1//laravel_vue_axios/database/migrations/create_posts_table.php
2    
3    <?php
4    use Illuminate\Support\Facades\Schema;
5    use Illuminate\Database\Schema\Blueprint;
6    use Illuminate\Database\Migrations\Migration;
7    
8    class CreatePostsTable extends Migration
9    {
10    
11        /**
12         * Run the migrations.
13         *
14         * @return void
15         
16         */
17         
18        public function up()
19        {
20            Schema::create('posts', function (Blueprint $table) {
21                $table->increments('id');
22                $table->string('title');
23                $table->text('content');
24                $table->timestamps();
25            });
26        }
27        
28        /**
29         * Reverse the migrations.
30         *
31         * @return void
32         */
33        
34        public function down()
35        {
36            Schema::dropIfExists('posts');
37        }
38    }

Now, run php artisan migrate to create the posts table in your database with the corresponding fields.

Having our database functional we can begin adding some data but it can be tiresome. So let’s seed our database with Laravel database seeding functionnality.

Execute this command php artisan make:factory PostFactory to generate a factory for our Post model. Next copy and paste the following code inside our PostFactory.php file

1//laravel_vue_axios/database/factories/PostFactory.php
2    <?php
3    
4    use Faker\Generator as Faker;
5    
6    
7    $factory->define(App\Post::class, function (Faker $faker) {
8        return [
9            'title' => $faker->sentence(3, true),
10            'content' => $faker->realText($faker->numberBetween(10, 100))
11        ];
12        
13    });

The above code defines a set of attributes for our model with fake data as you can notice, and the code is self-explanatory. Then paste this code inside your DatabaseSeeder.php file:

1//laravel_vue_axios/database/seeds/DatabaseSeeder.php
2    <?php
3    
4    use Illuminate\Database\Seeder;
5    class DatabaseSeeder extends Seeder
6    {
7    
8        /**
9         * Seed the application's database.
10         *
11         * @return void
12         */
13         
14        public function run()
15        {
16            factory(App\Post::class, 15)->create();
17        }
18    }

So what it means ? You may have guessed it, it tells Laravel to generate 15 instances of our Post model.

And finally run the following command: php artisan db:seed to make Laravel seed the database with the factory we define. If you check up your database you should see 15 fresh rows in your posts table. Great isn’t it 😊 !?

Defining routes and controller functions

In this part we’ll define the routes that our app should call to access our data , as well as the proper controller responsible to handle the logic for us.

First paste this code Route::get('/','PostController@index'); in your routes/web.php file. It means that index function should be called whenever a get request is made to / routes. And then open your routes/api.php file and paste this :

1Route::post('posts', 'PostController@store');
2    
3    Route::get('posts', 'PostController@get');
4    
5    Route::delete('posts/{id}', 'PostController@delete');

The above piece of code defines our routes and which function should handle them. Basically the first line is saying that for the routes \posts with a post request, the store function of our PostController should handle the logic and so on. Now let’s create the corresponding functions in our controller. Paste the following code in your PostController class body.

1//laravel_vue_axios/app/http/controllers/PostController.php
2    
3    public function index()
4    {
5        return view('posts');
6    }
7    
8    public function get(Request $request)
9    {
10        $posts = Post::orderBy('created_at', 'desc')->get();
11        return response()->json($posts);
12    }
13    
14    public function store(Request $request)
15    {
16        $post = Post::create($request->all());
17    
18        return response()->json($post);
19    }
20    
21    public function delete($id)
22    {
23        Post::destroy($id);
24    
25        return response()->json("ok");
26    }

Well, let’s take a minute to explain this code bock.

  • index returns a view where should be listed all our posts
  • get returns all posts existing in our database
  • store creates a Post instance an returned it as a JSON response
  • delete destroys as you can guess a post provided its ID is given

Now let’s focus on the frontend part of our fullstack app. We’ll build here our Vue.js components, manage state with Vuex and handle our requests with the Axios library

Create and manage our state

If you have ever worked with Vuex you should know that it helps manage state of an app in a centralized way. According to the official definition, Vuex is:

a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.

It means that you have sole source of data (centralized way) in your data that you can share between all your components, and every change made to your data is well supervised and reflected through every single component of your app.

As said above you should have some notions of Vuex in order to follow this tutorial in due form. So let’s jump into the code.

Create our state

Vuex state is a single object that contains all our application data. So let’s create ../resources/js/store/state.js and paste this code inside:

1let state = {
2        posts: []
3    }
4    
5    export default state

The code above is straightforward and therefore goes without explanation. The posts key is an array responsible to store our database posts info.

Define getters

Getters are like computed property for data store. With help of getters we can compute derived based on our data store state. Create ../resources/js/store/getters.js and paste this code inside

1let getters = {
2         posts: state => {
3             return state.posts
4         }
5    }
6    
7    export default  getters

Define mutations

The only way to actually change state in a Vuex store is by committing a mutation. Vuex mutations are very similar to events: each mutation has a string type and a handler. The handler function is where we perform actual state modifications, and it will receive the state as the first argument.

According to the official definition provided mutations allow us to perform some changes on our data. Create ../resources/js/store/mutations.js and paste this piece of code inside and we’ll look it up to understand.

1let mutations = {
2        CREATE_POST(state, post) {
3            state.posts.unshift(post)
4        },
5        FETCH_POSTS(state, posts) {
6            return state.posts = posts
7        },
8        DELETE_POST(state, post) {
9            let index = state.posts.findIndex(item => item.id === post.id)
10            state.posts.splice(index, 1)
11        }
12        
13    }
14    export default mutations

The code above has a mutations object with three functions each of them having our state object as argument:

  • CREATE_POST takes as arguments our state and the post we intend to add to our posts. The unshift function add the new post to the begining of our posts array.

  • FETCH_POSTS returns our posts state data simply. Very simple right?!

  • DELETE_POST takes two arguments, our state data and the post we intent to remove from our posts. let index = state.posts.findIndex(item => item.id === post.id) find the index of the post to delete by looping through the posts array and returning the first item that matches the given condition. Then it removes the post.

Define actions

Actions are similar to mutations, the differences being that:

  • Instead of mutating the state, actions commit mutations.
  • Actions can contain arbitrary asynchronous operations.

This is the most important part of our tutorial because it explains how requests are performed by the Axios library. So you should pay more attention to it. Vuex actions allow us to perform asynchronous operations over our data and to do so we need Axios . Create the following file and paste this code inside ../resources/js/store/actions.js file that you have to create

1let actions = {
2        createPost({commit}, post) {
3            axios.post('/api/posts', post)
4                .then(res => {
5                    commit('CREATE_POST', res.data)
6                }).catch(err => {
7                console.log(err)
8            })
9          
10        },
11        fetchPosts({commit}) {
12            axios.get('/api/posts')
13                .then(res => {
14                    commit('FETCH_POSTS', res.data)
15                }).catch(err => {
16                console.log(err)
17            })
18        },
19        deletePost({commit}, post) {
20            axios.delete(`/api/posts/${post.id}`)
21                .then(res => {
22                    if (res.data === 'ok')
23                        commit('DELETE_POST', post)
24                }).catch(err => {
25                console.log(err)
26            })
27        }
28    }
29    
30    export default actions

At a first glance it can look barbarian and obscure 😕🤔 but after explanations everything will seem clearer to you. We have defined three actions and each of them responsible of a single operation, either post creation, posts fetch or post deletion. They all perform an asynchronous call to our API routes.

Let’s analyze how this is done:

  • createPost We intend to perform a post request with axios.post('/api/posts', post).Axios has a dedicated function for that, the post function which takes the route and the data as parameters. We make use of the axios instance to perform a post request to our database. We tackle our API by calling the /api/posts route. The next part defines what should be done if the response is wether successful or unsuccessful. We commit the CREATE_POST mutation if response is successful and log the error if we encounter an error .

  • fetchPosts This one may look clearer to you now. We perform a get request in this action axios.get('/api/posts'). Axios provides a get function for this purpose. It takes one parameter which obviously is the endpoint we intend to tackle. The second part of the request does almost the same thing as the previous one expect here we commit the FETCH_POSTS mutation.

  • deletePost This part shows how we can perform a delete request with Axios. axios.delete(/api/posts/${post.id}) sends a delete request to our database by providing the API route with the ID of the post to delete. The next part of the request commits the DELETE_POST mutation if our response is successful and logs the error if something got wrong.

Set up our store with Vue

Now, we can import our getters, mutations, actions, state in the ../resources/js/store/index.js file that you should create. Paste this code to achieve that.

1import Vue from 'vue'
2    import Vuex from 'vuex'
3    import actions from './actions'
4    import mutations from './mutations'
5    import getters from './getters'
6    import state from "./state";
7    
8    Vue.use(Vuex);
9    
10    export default new Vuex.Store({
11        state,
12        mutations,
13        getters,
14        actions
15    })

Then, we export our store and add it to the vue instance. Add this code to your ../resouces/js/app.js file.

1require('./bootstrap');
2    window.Vue = require('vue');
3    
4    import store from './store/index'
5    
6    Vue.component('posts', require('./components/Posts.vue'))
7    Vue.component('createPost', require('./components/CreatePost.vue'))
8    
9    const app = new Vue({
10        el: '#app',
11        store
12    });

The previous code also globally registers two Vue components, Posts.vue and CreatePost.vue that we’ll build in the next part of this tutorial.

Building our components

We’ll create two components for our app, one for listing and deleting our posts and the second one for post creation purpose.

Create your Posts.vue component

Create your Posts.vue file and paste inside this code. We define this component for rendering our posts items in a table.

1//../resources/js/components/Posts.vue
2    
3    <template>
4        <div>
5            <h4 class="text-center font-weight-bold">Posts</h4>
6            <table class="table table-striped">
7                <thead>
8                <tr>
9                    <th scope="col">Title</th>
10                    <th scope="col">Content</th>
11                    <th scope="col">Actions</th>
12                </tr>
13                </thead>
14                <tbody>
15                <tr v-for="post in posts">
16                    <td>{{post.title}}</td>
17                    <td>{{post.content}}</td>
18                    <td>
19                        <button class="btn btn-danger" @click="deletePost(post)"><i style="color:white" class="fa fa-trash"></i></button>
20                    </td>
21                </tr>
22                </tbody>
23            </table>
24        </div>
25    
26    </template>
27    
28    <script>
29        import {mapGetters} from 'vuex'
30    
31        export default {
32            name: "Posts",
33            mounted() {
34                this.$store.dispatch('fetchPosts')
35            },
36            methods: {
37                deletePost(post) {
38                    this.$store.dispatch('deletePost',post)
39                }
40            },
41            computed: {
42                ...mapGetters([
43                    'posts'
44                ])
45            }
46        }
47    </script>
48    
49    <style scoped>
50    
51    </style>

In the mounted hook function we dispatch the fetchPosts action defined above in this tutorial responsible for fetching posts from database: this.$store.dispatch('deletePost', post) We also dispatch the deletePost action whenever we click the delete button rendered on each row. Inside our computed properties we import our posts getter in a style way using Vue.js mapGetters helper.

Create your Create.vue component

Now, create your CreatePost.vue file and paste inside this code.

1//../resources/js/components/CreatePost.vue
2    
3    <template>
4        <form action="" @submit="createPost(post)">
5            <h4 class="text-center font-weight-bold">Post creation form</h4>
6            <div class="form-group">
7                <input type="text" placeholder="Post title" v-model="post.title" class="form-control">
8    
9            </div>
10            <div class="form-group">
11                <textarea v-model="post.content" placeholder="Post content" class="form-control">
12    
13                </textarea>
14            </div>
15            <div class="form-group">
16                <button :disabled="!isValid" class="btn btn-block btn-primary" @click.prevent="createPost(post)">Submit
17                </button>
18            </div>
19        </form>
20    </template>
21    
22    <script>
23        export default {
24            name: "CreatePost",
25            data() {
26                return {
27                    post: {
28                        title: '',
29                        content: ''
30                    }
31                }
32            },
33            methods: {
34                createPost(post) {
35                    this.$store.dispatch('createPost', post)
36                }
37            },
38            computed: {
39                isValid() {
40                    return this.post.title !== '' && this.post.content !== ''
41                }
42            }
43        }
44    </script>
45    
46    <style scoped>
47    
48    </style>

It contains a form with a createPost action dispatched whenever the form is submitted by the user. We also defined isValid computed property responsible to disable the submit button if one of the fields is empty.

Finalize the app

Let’s create posts.blade.php file which contains our two vue components. Paste this code inside.

1//../resources/views/posts.blade.php
2    <!doctype html>
3    <html lang="{{ app()->getLocale() }}">
4    <head>
5        <meta charset="utf-8">
6        <meta http-equiv="X-UA-Compatible" content="IE=edge">
7        <meta name="viewport" content="width=device-width, initial-scale=1">
8        <meta name="csrf-token" content="{{ csrf_token() }}">
9    
10        <title>Laravel Vue.js app</title>
11    
12        <!-- Fonts -->
13        <link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
14        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
15              integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
16    
17        <link rel="stylesheet" href="{{mix('css/app.css')}}">
18    
19        <!-- Styles -->
20        <style>
21            html, body {
22                padding: 45px;
23                background-color: #fff;
24                color: #636b6f;
25                font-family: 'Nunito', sans-serif;
26                font-weight: 200;
27                height: 100vh;
28                margin: 0;
29            }
30    
31            .full-height {
32                height: 100vh;
33            }
34    
35            .flex-center {
36                align-items: center;
37                display: flex;
38                justify-content: center;
39            }
40    
41            .position-ref {
42                position: relative;
43            }
44    
45            .top-right {
46                position: absolute;
47                right: 10px;
48                top: 18px;
49            }
50    
51            .content {
52                text-align: center;
53            }
54    
55            .title {
56                font-size: 84px;
57            }
58    
59            .links > a {
60                color: #636b6f;
61                padding: 0 25px;
62                font-size: 12px;
63                font-weight: 600;
64                letter-spacing: .1rem;
65                text-decoration: none;
66                text-transform: uppercase;
67            }
68    
69            .m-b-md {
70                margin-bottom: 30px;
71            }
72        </style>
73    </head>
74    <body>
75    {{--<div class="flex-center position-ref full-height">--}}
76    
77    <div id="app">
78    
79        <div class="container">
80            <div class="row">
81                <div class="col-md-5">
82                    <create-post></create-post>
83    
84                </div>
85                <div class="col-md-7">
86                    <posts></posts>
87    
88                </div>
89            </div>
90        </div>
91    
92    
93    </div>
94    
95    {{--</div>--}}
96    
97    <script async src="{{mix('js/app.js')}}"></script>
98    </body>
99    </html>

We are almost done. Now open your terminal and run npm run dev to build your app in a proper way. This can take a few seconds. After this step if you open your browser at localhost:8000 or run php artisan serve if the server was not running you should see something like this: Isn’t nice ?

axios-laravel-demo

Conclusion

I hope this tutorial was helpful enough to increase your enthusiasm about exploring using Axios to handle your HTTP requests.

You can visit the documentation to learn more about it. You can do more

The source code for the app can be found here on GitHub if you are interested. Feel free to read it .