Learn more
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_base
http://localhost:4000
entity
notes
API endpoints:
REST API
URL
Detail
GET
Request method is GET and this endpoint will get all records with json format
notes/show/id
Request method is GET and this endpoint will get single record for specific ID with json format
POST
notes/store
Request method is POST and this endpoint is used to create a new record.
PUT
notes/update/id
Request method is POST and this endpoint is used to update an existing record for a specific ID.
DELETE
notes/delete/id
Request method is DELETE and this endpoint is used to delete an existing record for a specific ID.
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.
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.
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=
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
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; }
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); } }
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']); });
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/*" ]; }
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
To install the new package, use the following commands.
npm install -g @vue/cli
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.
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.
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>
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;
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>
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/")
npm run serve
And browse this URL: http://localhost:8080/
Enjoy!