Step by step CRUD application using Laravel, Vue 3 and MySQL database with RESTful style

This tutorial will show you step by step on how to create basic data manipulation operations CRUD(Create, Read, Update and Delete) using Laravel as backend programming, MySQL as Database, Vue 3 as frontend. Software architectural style is REST(Representational State Transfer) APIs. Before going to the main tutorial steps, we need to know the following basic terms to better understand.
In the end of CRUD application, we will get a daily note management system by which one can manage daily note or dairy. So our target entity name is notes and basic fields for that entity are note number, title, datetime, note detail, involved person, location etc.
RESTful API:
A REST API (also known as RESTful API) is an application programming interface (API or web API) that conforms to the constraints of REST architectural style and allows for interaction with RESTful web services. REST stands for representational state transfer and was created by computer scientist Roy Fielding.
If entity name is notes, then RESTful API basic four methods and all url structures for CRUD operation are as like:
API endpoints:
Vue Js 3:
Vue.js is an open-source model–view–viewmodel front end JavaScript framework for building user interfaces and single-page applications. Latest stable version of Vue JS is 3.
Vue CLI is a full system for rapid Vue.js development which aims to be the standard tooling baseline for the Vue ecosystem.
Laravel:
Laravel is a free, open-source PHP web framework, created by Taylor Otwell and intended for the development of web applications following the model–view–controller architectural pattern and based on Symfony.
MySQL:
MySQL is an open-source relational database management system. Its name is a combination of "My", the name of co-founder Michael Widenius's daughter, and "SQL", the abbreviation for Structured Query Language.
CRUD Operation:
CRUD stands for Create, Read, Update and Delete which are basic operations of persistent storage. CRUD operations are basic data manipulation for databases which is very important in database based applications. To understand this CRUD clearly is a very basic task for any application developer.
Prerequisites
Before getting started with this tutorial, you must be aware of the fundamentals of
-
HTML and CSS
-
Mordan JavaScript, or ES6.
-
Vue JS
-
PHP
-
MySQL and
-
API request
Software Installation
-
Node.js: https://nodejs.org
-
Apache Friends - XAMPP: https://www.apachefriends.org/index.html
-
Postman: a tool for API testing https://www.postman.com/downloads/
This tutorial is mainly divided into two parts:
Part 1: Build backend or API using Laravel and MySQL Database
Part 2: Build frontend or UI using Node, Vue JS, Bootstrap
Before starting steps, Go to PHP server root directory www or htdocs and create a folder named crud_app and open terminal
Let's start now.
Part 1: Build backend or API using Laravel and MySQL Database
Step 1: Install Laravel App
Open terminal further run below command to manifest a new laravel project.
composer create-project laravel/laravel backend --prefer-dist
If you want to know, how create first project in laravel, please visit https://laravel.com/docs/4.2/quick
cd backend
php artisan serve
Once you have started the Artisan development server, you may access your application at http://localhost:8000.
Step 2: Database Connection
This step explains how to make database connection by adding database name, username and password in .env config file of your project:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud_db
DB_USERNAME=root
DB_PASSWORD=
Step 3: Run migration (Optional):
Add following code in database/migrations/create_notes_table.php:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateNotesTable extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up() {
Schema::create('notes', function (Blueprint $table) {
$table->id();
$table->string('note_number');
$table->string('title');
$table->dateTime('date_time');
$table->text('description');
$table->string('involved_person');
$table->string('location');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down() {
Schema::dropIfExists('notes');
}
}
Run following command to run migrate:
php artisan make:migration create_notes_table
Step 4: Create Model
Run the below process to respectively create the Model (database table) and migration file:
php artisan make:model note -m
Update model with below code:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Note extends Model
{
const UID = "123213";
//'note_number', 'title', 'date_time', 'description', 'involved_person', 'location'
use HasFactory;
protected $fillable = [
'uid',
'note_number',
'title',
'date_time',
'description',
'involved_person',
'location'
];
public $timestamps = false;
}
Step 5: Create Notes Controller
You need to create the notes controller and define the CRUD operations methods:
php artisan make:controller NotesController
Open and update the below code in app\Http\Controllers\NotesController.php:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Note;
class NotesController extends Controller {
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index() {
$notes = Note::all()->toArray();
return array_reverse($notes);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request) {
$note = new Note([
'note_number' => $request->input('note_number'),
'uid' => uniqid(),
'title' => $request->input('title'),
'date_time' => $request->input('date_time'),
'description' => $request->input('description'),
'involved_person' => $request->input('involved_person'),
'location' => $request->input('location')
]);
$note->save();
$response = [];
$response["code"] = 200;
$response["message"] = 'A Daily Note has been created!';
return response()->json($response);
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id) {
$note = Note::where('id', $id)->first();
return $note;
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update($id, Request $request) {
$note = Note::find($id);
$note->update($request->all());
$response["code"] = 200;
$response["message"] = 'A Daily Note has been updated!';
return response()->json($response);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id) {
$note = Note::find($id);
$note->delete();
$response["code"] = 200;
$response["message"] = 'A Daily Note has been deleted!';
return response()->json($response);
}
}
Step 6 Create CRUD Routes
Open routes/web.php file, in here; you have to define the following route:
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
use App\Http\Controllers\NotesController;
Route::middleware(['cors'])->group(function () {
Route::get('/notes', [NotesController::class, 'index']);
Route::get('/notes/show/{id}', [NotesController::class, 'show']);
Route::post('/notes/store', [NotesController::class, 'store']);
Route::post('/notes/update/{id}', [NotesController::class, 'update']);
Route::post('/notes/delete/{id}', [NotesController::class, 'destroy']);
});
Step 7: Enable CORS on Laravel and Exclude Route From Verify CSRF Token
Enable CORS:
You can use a middleware that adds Access-Control-Allow-Origin to an http response header.
First, create an middleware
php artisan make:middleware Cors
Secondly, append 'cors' => \App\Http\Middleware\Cors::class in the middleware section to Kernel.php files
protected $routeMiddleware = [
…..
'cors' => \App\Http\Middleware\Cors::class,
];
Exclude Route From Verify CSRF Token
Navigate to the app/Http/Middleware folder in your Laravel application. You will find VerifyCsrfToken.php and update $except variable for notes all API endpoints.
<?php
namespace App\Http\Middleware;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware;
class VerifyCsrfToken extends Middleware
{
/**
* The URIs that should be excluded from CSRF verification.
*
* @var array<int, string>
*/
protected $except = [
"notes",
"notes/*"
];
}
Step 8: Check API using postman
Open postman and create the following collection request which will help to check if the API request is working or not.
Endpoint 1 - to get all notes: http://127.0.0.1:8000/notes
Endpoint 2- to get single note: http://127.0.0.1:8000/notes/show/2
Endpoint 3- to add new note: http://127.0.0.1:8000/notes/store
Endpoint 4 - to update record: http://127.0.0.1:8000/notes/update/1
Endpoint 5 - to delete record: http://127.0.0.1:8000/notes/delete/1
Part 2 Build frontend or UI using Node, ReactJS, Bootstrap
Step 1: VueCli Installation
To install the new package, use the following commands.
npm install -g @vue/cli
Step 2: Create Vue Project
To create a new project, run:
vue create frontend
This will give us a screen where we’re going to choose to use “Vue 3 Preview”:
This will create a base Vue 3 application, and we can ensure it works by going into the directory and running the server:
cd frontend
npm run serve
Output:
DONE Compiled successfully in 2963ms 10:30:22 PM
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.0.108:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
Step 3 Install Vue Router, Axios, and vue-axios.
Okay, now go to the terminal and type the following command to install the vue-router, axios, and vue-axios dependencies.
npm install vue-router axios vue-axios --save
Now that we have that, we can have a look at setting up Bootstrap for our project.
We first install the dependencies:
npm install bootstrap bootstrap-vue
After those have been installed, we need to open up our project in our IDE.
Step 4: Create required files
Create views folder under src and create following files and update respective content.
NoteList.vue:
Create views/notes/NoteList.vue and update following code
<template>
<router-link class="btn btn-primary my-3" to="/notes/add">Add Note</router-link>
<div class="card">
<div class="card-header">
Notes
</div>
<div class="card-body">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Note Number</th>
<th scope="col">Title</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
Item Loop...
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
data() {
return{
items: []
}
},
inject: ['apiBasePath'],
created: function ()
{
this.fetchItems();
},
methods: {
fetchItems()
{
this.axios.get(this.apiBasePath + 'notes', {
}).then(res => {
console.log(res);
this.items = res.data;
}).catch(err => {
console.log(err.response);
});
},
deleteNote(id) {
if (confirm("Do you confirm to delete this item")) {
this.axios.post(this.apiBasePath + 'notes/delete/' + id)
.then(res => {
if (res.status === 200 && res.data.code === 200) {
let i = this.items.map(data => data.id).indexOf(id);
this.items.splice(i, 1)
}
});
}
}
}
}
</script>
NoteForm.vue:
Create views/notes/NoteForm.vue and update following code
<template>
<div>
<div class="mb-3">
<label for="note_number" class="form-label">Note number</label>
<input v-model="formItem.note_number" type="text" class="form-control" id="note_number" placeholder="">
</div>
<div class="mb-3">
<label for="title" class="form-label">Note Title</label>
<input v-model="formItem.title" type="text" class="form-control" id="title" placeholder="">
</div>
<div class="mb-3">
<label for="date_time" class="form-label">Date time</label>
<input v-model="formItem.date_time" type="datetime-local" class="form-control" id="date_time" placeholder="">
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea v-model="formItem.description" class="form-control" id="description" placeholder=""></textarea>
</div>
<div class="mb-3">
<label for="involved_person" class="form-label">Involved person</label>
<input v-model="formItem.involved_person" type="text" class="form-control" id="involved_person" placeholder="">
</div>
<div class="mb-3">
<label for="location" class="form-label">Location</label>
<input v-model="formItem.location" type="text" class="form-control" id="location" placeholder="">
</div>
</div>
</template>
<script>
export default {
name: "NoteForm",
props: ["item"], // declare the props
computed: {
formItem() {
return this.item;
}
}
}
</script>
CreateNote.vue:
Create views/notes/CreateNote.vue and update following code
<template>
<router-link class="btn btn-primary my-3" to="/notes">Back To All Notes</router-link>
<div class="card">
<form @submit.prevent="addNote">
<div class="card-header">
Add Note
</div>
<div class="card-body">
<NoteForm :item="item_data" />
</div>
<div class="card-footer">
<button type="submit" class="btn btn-info mx-1 text-white" >Save</button> or
<router-link class="btn btn-danger text-white" to="/notes">Cancel</router-link>
</div>
</form>
</div>
</template>
<script>
import NoteForm from '@/views/notes/NoteForm.vue';
export default {
name: 'App',
data() {
return{
item_data: {},
successMessage: "",
errorMessage: ""
};
},
inject: ['apiBasePath'],
methods: {
addNote() {
this.axios.post(this.apiBasePath + 'notes/store/', this.item_data)
.then((res) => {
if (res.status === 200 && res.data.code === 200) {
this.successMessage = res.data.message;
this.$router.push("/notes");
} else {
this.errorMessage = res.data.message;
}
});
},
},
components: {
NoteForm
}
}
</script>
EditNote.vue:
Create views/notes/EditNote.vue and update following code
<template>
<router-link class="btn btn-primary my-3" to="/notes">Back To All Notes</router-link>
<div class="card">
<form @submit.prevent="updateNote">
<div class="card-header">
Edit Note
</div>
<div class="card-body">
<NoteForm :item="item_data" />
</div>
<div class="card-footer">
<button type="submit" class="btn btn-info mx-1 text-white">Update</button> or
<router-link class="btn btn-danger text-white" to="/notes">Cancel</router-link>
</div>
</form>
</div>
</template>
<script>
import NoteForm from '@/views/notes/NoteForm.vue';
export default {
name: 'App',
data() {
return{
item_data: {},
successMessage: "",
errorMessage: ""
}
},
inject: ['apiBasePath'],
created: function ()
{
this.fetchItem();
},
methods: {
fetchItem()
{
this.axios.get(this.apiBasePath + 'notes/show/' + this.$route.params.id, {
}).then(res => {
// console.log(res);
this.item_data = res.data;
}).catch(err => {
console.log(err.response);
});
},
updateNote() {
this.axios.post(this.apiBasePath + 'notes/update/' + this.$route.params.id, this.item_data)
.then((res) => {
if (res.status === 200 && res.data.code === 200) {
console.log(res);
this.successMessage = res.data.message;
this.$router.push('/notes');
} else {
this.errorMessage = res.data.message;
}
});
}
},
components: {
NoteForm
}
}
</script>
PageHome.vue:
Create views/pages/PageHome.vue and update following code
<template>
<h1 class="mt-5"> CRUD application Home </h1>
<div class="card">
<div class="card-body p-3">
<p> Step by step CRUD application using Laravel, Vue 3(CLI) and MySQL database with RESTful style.</p>
<p>
<router-link class="nav-link" to="/notes">Go To CRUD Application</router-link>
</p>
</div>
</div>
</template>
PageAbout.vue:
Create views/pages/PageAbout.vue and update following code
<template>
<h1 class="mt-5"> About Me</h1>
<div class="card">
<div class="card-body p-3">
<p> I am Md Saidul Haque. For details, visit <a href="https://saidulhaque.com/" target="_blank">https://saidulhaque.com/</a></p>
</div>
</div>
</template>
Step 5: Create router files
Create src/router/index.js and update following code
import { createWebHistory, createRouter } from "vue-router";
import Home from "@/views/pages/PageHome.vue";
import About from "@/views/pages/PageAbout.vue";
import NoteList from "@/views/notes/NoteList.vue";
import CreateNote from '@/views/notes/CreateNote.vue';
import EditNote from '@/views/notes/EditNote.vue';
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/about",
name: "About",
component: About,
},
{
path: "/notes",
name: "Notes",
component: NoteList,
},
{
path: "/notes/add",
name: "CreateNote",
component: CreateNote,
},
{
path: "/notes/edit/:id",
name: "EditNote",
component: EditNote,
},
];
const router = createRouter({
history: createWebHistory(),
routes,
});
export default router;
Step 6: Update app.vue files
Update app.vue file with following code
<template>
<div class="">
<header>
<div class="d-flex flex-column flex-md-row align-items-center py-2 px-5 border-bottom">
<a href="/" class="d-flex align-items-center text-dark text-decoration-none">
<span class="fs-4">CRUD Application (Laravel + Vue 3(CLI) + MySQL)</span>
</a>
<nav class="d-inline-flex mt-2 mt-md-0 ms-md-auto">
<router-link class="nav-link active" to="/">Home</router-link>
<router-link class="nav-link" to="/about">About</router-link>
<router-link class="nav-link" to="/notes">Notes</router-link>
</nav>
</div>
</header>
<main>
<div class="container">
<router-view />
</div>
</main>
<footer class="footer">
<div class="container">
<span class="text-muted">CRUD Application is basic and initial program for a developer who want to work with database.</span>
</div>
</footer>
</div>
</template>
<style>
.top-bar{
position: absolute;
}
.border-top {
border-top: 1px solid #e5e5e5;
}
.border-bottom {
border-bottom: 1px solid #e5e5e5;
}
.box-shadow {
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
}
.footer {
position: absolute;
bottom: 0;
width: 100%;
height: 60px;
line-height: 60px;
background-color: #f5f5f5;
}
</style>
Step 7: Update main.js files
Update main.js file with following code
import { createApp } from 'vue'
import axios from 'axios'
import VueAxios from 'vue-axios'
import App from './App.vue'
import router from './router'
import 'bootstrap/dist/css/bootstrap.min.css'
import 'jquery/src/jquery.js'
import 'bootstrap/dist/js/bootstrap.min.js'
const app = createApp(App)
app.use(VueAxios, axios)
app.use(router).mount('#app')
//set base path as global variable
app.provide('apiBasePath', "http://127.0.0.1:8000/")
Step 8: Run and Test Application
npm run serve
And browse this URL: http://localhost:8080/
Enjoy!