Handle HTTP requests in a Laravel Vue.js app with Axios
You will need a good knowledge of Laravel, including how to set up a database to use with it. You should have the Laravel CLI installed. Some familiarity with Vue.js will be helpful.
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 :
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:
- Basic or good knowledge of the Laravel framework
- Good knowledge of the Vue.js framework
- Basic knowledge of Vuex library
- Laravel CLI installed on your machine
- Good understanding of Javascript language (ES6)
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.
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"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",
"prod": "npm run production",
"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"
},
"devDependencies": {
"axios": "^0.18",
"bootstrap": "^4.0.0",
"popper.js": "^1.12",
"cross-env": "^5.1",
"jquery": "^3.2",
"laravel-mix": "^2.0",
"lodash": "^4.17.5",
"vue": "^2.5.7",
"vuex": "^3.0.1"
}
}
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.
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
//laravel_vue_axios/database/migrations/create_posts_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
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
//laravel_vue_axios/database/factories/PostFactory.php
<?php
use Faker\Generator as Faker;
$factory->define(App\Post::class, function (Faker $faker) {
return [
'title' => $faker->sentence(3, true),
'content' => $faker->realText($faker->numberBetween(10, 100))
];
});
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:
//laravel_vue_axios/database/seeds/DatabaseSeeder.php
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
factory(App\Post::class, 15)->create();
}
}
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 :
Route::post('posts', 'PostController@store');
Route::get('posts', 'PostController@get');
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.
//laravel_vue_axios/app/http/controllers/PostController.php
public function index()
{
return view('posts');
}
public function get(Request $request)
{
$posts = Post::orderBy('created_at', 'desc')->get();
return response()->json($posts);
}
public function store(Request $request)
{
$post = Post::create($request->all());
return response()->json($post);
}
public function delete($id)
{
Post::destroy($id);
return response()->json("ok");
}
Well, let’s take a minute to explain this code bock.
index
returns a view where should be listed all our postsget
returns all posts existing in our databasestore
creates a Post instance an returned it as a JSON responsedelete
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:
let state = {
posts: []
}
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
let getters = {
posts: state => {
return state.posts
}
}
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.
let mutations = {
CREATE_POST(state, post) {
state.posts.unshift(post)
},
FETCH_POSTS(state, posts) {
return state.posts = posts
},
DELETE_POST(state, post) {
let index = state.posts.findIndex(item => item.id === post.id)
state.posts.splice(index, 1)
}
}
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. Theunshift
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
let actions = {
createPost({commit}, post) {
axios.post('/api/posts', post)
.then(res => {
commit('CREATE_POST', res.data)
}).catch(err => {
console.log(err)
})
},
fetchPosts({commit}) {
axios.get('/api/posts')
.then(res => {
commit('FETCH_POSTS', res.data)
}).catch(err => {
console.log(err)
})
},
deletePost({commit}, post) {
axios.delete(`/api/posts/${post.id}`)
.then(res => {
if (res.data === 'ok')
commit('DELETE_POST', post)
}).catch(err => {
console.log(err)
})
}
}
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 withaxios.post('/api/posts', post)
.Axios has a dedicated function for that, thepost
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 theCREATE_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 aget
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 theFETCH_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 theDELETE_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.
import Vue from 'vue'
import Vuex from 'vuex'
import actions from './actions'
import mutations from './mutations'
import getters from './getters'
import state from "./state";
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
getters,
actions
})
Then, we export our store and add it to the vue instance. Add this code to your ../resouces/js/app.js
file.
require('./bootstrap');
window.Vue = require('vue');
import store from './store/index'
Vue.component('posts', require('./components/Posts.vue'))
Vue.component('createPost', require('./components/CreatePost.vue'))
const app = new Vue({
el: '#app',
store
});
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.
//../resources/js/components/Posts.vue
<template>
<div>
<h4 class="text-center font-weight-bold">Posts</h4>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Content</th>
<th scope="col">Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="post in posts">
<td>{{post.title}}</td>
<td>{{post.content}}</td>
<td>
<button class="btn btn-danger" @click="deletePost(post)"><i style="color:white" class="fa fa-trash"></i></button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import {mapGetters} from 'vuex'
export default {
name: "Posts",
mounted() {
this.$store.dispatch('fetchPosts')
},
methods: {
deletePost(post) {
this.$store.dispatch('deletePost',post)
}
},
computed: {
...mapGetters([
'posts'
])
}
}
</script>
<style scoped>
</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.
//../resources/js/components/CreatePost.vue
<template>
<form action="" @submit="createPost(post)">
<h4 class="text-center font-weight-bold">Post creation form</h4>
<div class="form-group">
<input type="text" placeholder="Post title" v-model="post.title" class="form-control">
</div>
<div class="form-group">
<textarea v-model="post.content" placeholder="Post content" class="form-control">
</textarea>
</div>
<div class="form-group">
<button :disabled="!isValid" class="btn btn-block btn-primary" @click.prevent="createPost(post)">Submit
</button>
</div>
</form>
</template>
<script>
export default {
name: "CreatePost",
data() {
return {
post: {
title: '',
content: ''
}
}
},
methods: {
createPost(post) {
this.$store.dispatch('createPost', post)
}
},
computed: {
isValid() {
return this.post.title !== '' && this.post.content !== ''
}
}
}
</script>
<style scoped>
</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.
//../resources/views/posts.blade.php
<!doctype html>
<html lang="{{ app()->getLocale() }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Laravel Vue.js app</title>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css?family=Nunito:200,600" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
<link rel="stylesheet" href="{{mix('css/app.css')}}">
<!-- Styles -->
<style>
html, body {
padding: 45px;
background-color: #fff;
color: #636b6f;
font-family: 'Nunito', sans-serif;
font-weight: 200;
height: 100vh;
margin: 0;
}
.full-height {
height: 100vh;
}
.flex-center {
align-items: center;
display: flex;
justify-content: center;
}
.position-ref {
position: relative;
}
.top-right {
position: absolute;
right: 10px;
top: 18px;
}
.content {
text-align: center;
}
.title {
font-size: 84px;
}
.links > a {
color: #636b6f;
padding: 0 25px;
font-size: 12px;
font-weight: 600;
letter-spacing: .1rem;
text-decoration: none;
text-transform: uppercase;
}
.m-b-md {
margin-bottom: 30px;
}
</style>
</head>
<body>
{{--<div class="flex-center position-ref full-height">--}}
<div id="app">
<div class="container">
<div class="row">
<div class="col-md-5">
<create-post></create-post>
</div>
<div class="col-md-7">
<posts></posts>
</div>
</div>
</div>
</div>
{{--</div>--}}
<script async src="{{mix('js/app.js')}}"></script>
</body>
</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 ?
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 .
17 September 2018
by Ethiel Adiassa