Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
This Laravel jQuery POS authentication setup tutorial walks you through database migration and secure user login—critical for any protected system.
Now, we’ll make it functional by:
By the end of this part, your POS project will have the core data structure and user login system in place.
We’ll create all the necessary database tables and insert some dummy data to use throughout this tutorial series.
Open the Command Prompt (CMD), navigate to your project root directory, and run the following command:
php artisan make:migration project_tables
Overwrite the generated migration file with this content
<?php
//Laravel jQuery POS authentication setup @ https://laravelcenter.com
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
// Laravel POS With jQuery @ https://laravelcenter.com
// update existing user table
Schema::table('users', function (Blueprint $table) {
$table->string('username')->unique();
$table->enum('role', ['admin', 'cashier', 'superadmin']);
$table->boolean('active');
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->dropColumn('name');
$table->dropColumn('email');
$table->dropColumn('email_verified_at');
$table->softDeletes();
});
// tables
Schema::create('tables', function (Blueprint $table) {
// Set the storage engine to InnoDB
$table->engine = 'InnoDB';
$table->id();
$table->string('name', 50);
$table->tinyInteger('status')->default(2);
$table->string('invoice_no', 20)->nullable();
$table->tinyInteger('discount')->default(0);
$table->decimal('total_discount')->default(0);
$table->decimal('grand_total')->default(0);
$table->decimal('total')->default(0);
$table->decimal('net_amount')->default(0);
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
// product
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name', 100);
$table->foreignId('product_category_id');
$table->decimal('unit_price');
$table->string('image')->nullable();
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
// product category
Schema::create('product_categories', function (Blueprint $table) {
$table->id();
$table->string('name', 50);
$table->integer('ordering')->default(0);
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
// order
Schema::create('orders', function (Blueprint $table) {
// Set the storage engine to InnoDB
$table->engine = 'InnoDB';
$table->id();
$table->foreignId('table_id');
$table->string('invoice_no', 20);
$table->integer('discount')->default(0);
$table->decimal('total_discount')->default(0);
$table->decimal('grand_total')->default(0);
$table->decimal('total')->default(0);
$table->decimal('net_amount')->default(0);
$table->decimal('receive_amount');
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
// order detail
Schema::create('order_details', function (Blueprint $table) {
// Set the storage engine to InnoDB
$table->engine = 'InnoDB';
$table->id();
$table->foreignId('order_id');
$table->foreignId('product_id');
$table->foreignId('product_category_id');
$table->string('description', 100);
$table->integer('qty');
$table->decimal('unit_price');
$table->tinyInteger('discount')->default(0);
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
// order detail temp
Schema::create('order_detail_temps', function (Blueprint $table) {
// Set the storage engine to InnoDB
$table->engine = 'InnoDB';
$table->id();
$table->foreignId('table_id');
$table->foreignId('product_id');
$table->foreignId('product_category_id');
$table->string('description', 100);
$table->integer('qty');
$table->decimal('unit_price');
$table->tinyInteger('discount')->default(0);
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->timestamps();
});
// balance adjustment
Schema::create('balance_adjustments', function (Blueprint $table) {
$table->id();
$table->decimal('amount');
$table->tinyInteger('type_id');
$table->date('adjusted_date');
$table->string('remark');
$table->bigInteger('created_by_id')->default(0);
$table->bigInteger('updated_by_id')->default(0);
$table->bigInteger('deleted_by_id')->default(0);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// Laravel POS With jQuery @ https://laravelcenter.com
Schema::dropIfExists('tables');
Schema::dropIfExists('products');
Schema::dropIfExists('product_categories');
Schema::dropIfExists('orders');
Schema::dropIfExists('order_details');
Schema::dropIfExists('order_detail_temps');
Schema::dropIfExists('balance_adjustments');
}
};
Overwrite the seeder file database/seeders/DatabaseSeeder.php with this content
<?php
// Laravel jQuery POS authentication setup @ https://laravelcenter.com
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Laravel POS With jQuery @ https://laravelcenter.com
// create default users
DB::table('users')->insert([
'username' => 'superadmin',
'password' => bcrypt('123456'),
'role' => 'superadmin',
'active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
DB::table('users')->insert([
'username' => 'admin',
'password' => bcrypt('123456'),
'role' => 'admin',
'active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
DB::table('users')->insert([
'username' => 'cashier',
'password' => bcrypt('123456'),
'role' => 'cashier',
'active' => 1,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
// insert dummy product category
$dummyProducts = [
'Rice' => [
[
'name' => 'Pork Rice',
'unit_price' => 2
],
[
'name' => 'Chicken Rice',
'unit_price' => 2
],
[
'name' => 'Fried Rice',
'unit_price' => 2
]
],
'Noodle' => [
[
'name' => 'Beef Noodle Soup',
'unit_price' => 3
],
[
'name' => 'Seafood Noodle Soup',
'unit_price' => 3
]
],
'Snack' => [
[
'name' => 'Fried Chicken',
'unit_price' => 1
],
[
'name' => 'French Fries',
'unit_price' => 1
],
[
'name' => 'Burger',
'unit_price' => 1
]
],
'Water' => [
[
'name' => 'Eau Kulen',
'unit_price' => 0.25
],
[
'name' => 'Angkor Puro',
'unit_price' => 0.25
],
[
'name' => 'Dasani',
'unit_price' => 0.25
]
],
'Coffee' => [
[
'name' => 'Iced Latte',
'unit_price' => 1.5
],
[
'name' => 'Iced Cappuccino',
'unit_price' => 1.5
],
[
'name' => 'Iced Americano',
'unit_price' => 1.5
]
],
'Beer & Wine' => [
[
'name' => 'ABC',
'unit_price' => 2
],
[
'name' => 'Tiger',
'unit_price' => 2
],
[
'name' => 'Heineken',
'unit_price' => 2
]
],
'Juice' => [
[
'name' => 'Orange Juice',
'unit_price' => 2
],
[
'name' => 'Apple Juice',
'unit_price' => 2
],
[
'name' => 'Mango Juice',
'unit_price' => 2
]
],
];
foreach ($dummyProducts as $key => $value) {
$categoryId = DB::table('product_categories')->insertGetId([
'name' => $key,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
foreach ($value as $product) {
DB::table('products')->insert([
'name' => $product['name'],
'unit_price' => $product['unit_price'],
'product_category_id' => $categoryId,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
// Dummy Table
for ($i = 1; $i <= 10; $i++) {
DB::table('tables')->insert([
'name' => str_pad($i, 3, '0', STR_PAD_LEFT),
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s')
]);
}
}
}
Then run the migration:
php artisan migrate:fresh --seed
Note:
After running this command, Laravel will generate all the required project tables along with sample data for products and their categories. Additionally, three default user accounts will be created for testing:
superadmin
| Password: 123456
admin
| Password: 123456
cashier
| Password: 123456
These users can be used to log in and test different access levels within the system.
To learn more about Laravel’s migration system, you can check out the official Laravel migration documentation.
Replace the contents of app/Http/Controllers/UserController.php with the code below. If the file doesn’t exist yet, please create it.
<?php
// Laravel jQuery POS authentication setup @ https://laravelcenter.com
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
class UserController extends Controller
{
/**
* Handle an authentication attempt.
*/
public function login(Request $request)
{
if ($request->isMethod('GET'))
return view('user.login');
// validation
$rules = [
'username' => 'required',
'password' => 'required'
];
$validator = Validator::make($request->all(), $rules);
if ($validator->fails())
return response()->json([
'success' => false,
'errors' => $validator->errors()
]);
$credentials = [
'username' => $request->username,
'password' => $request->password,
'active' => 1,
];
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
// check page need to redirect base on user menu
return response()->json([
'success' => true,
'redirect_url' => url('/')
]);
}
return response()->json([
'success' => false,
'errors' => [
'username' => ['Incorrect username or password']
]
]);
}
/**
* Log the user out of the application.
*/
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
Replace the contents of resources/views/layout/admin.blade.php with the code below. If the file doesn’t exist yet, please create it.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
{{-- Laravel jQuery POS authentication setup @ https://laravelcenter.com --}}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="icon" type="image/png" href="images/favicon.png">
<title>{{ env('APP_NAME') }}</title>
<!-- Google Fonts -->
<link href="https://fonts.gstatic.com" rel="preconnect">
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i"
rel="stylesheet">
@vite('resources/js/app.js')
</head>
<body style="display: none">
{{-- Header Menu --}}
<header id="header" class="header fixed-top d-flex align-items-center">
<div class="d-flex align-items-center justify-content-between">
<a href="{{ url('/') }}" class="logo d-flex align-items-center">
<img src="images/logo.png" alt="">
</a>
<i class="bi bi-list toggle-sidebar-btn"></i>
</div>
<nav class="header-nav ms-auto">
<ul class="d-flex align-items-center">
<li class="d-none d-md-inline-block form-inline ms-auto nav-item dropdown me-5">
<i class="bi bi-alarm-fill text-secondary pe-2"></i>
<span class="text-secondary">{{ date('d-M-Y H:i:s') }}</span>
</li>
<li class="nav-item dropdown pe-3">
<a class="nav-link nav-profile d-flex align-items-center pe-0" href="#"
data-bs-toggle="dropdown">
<i class="bi bi-person-fill" style="font-size: 35px;"></i>
<span
class="d-none d-md-block dropdown-toggle ps-2">{{ ucwords(request()->user()?->username) }}</span>
</a>
<ul class="dropdown-menu dropdown-menu-end dropdown-menu-arrow profile">
<li>
<button class="dropdown-item d-flex align-items-center"
onclick="ajaxPopup(`{{ url('user/change-password') }}`)">
<i class="bi bi-shield-lock"></i>
<span>Change Password</span>
</button>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<form method="post" action="{{ url('./user/logout') }}">
@csrf
<button type="submit" class="dropdown-item d-flex align-items-center">
<i class="bi bi-box-arrow-right"></i>
<span>Sign Out</span>
</button>
</form>
</li>
</ul>
</li>
</ul>
</nav>
</header>
{{-- Sidebar --}}
<aside id="sidebar" class="sidebar">
<ul class="sidebar-nav" id="sidebar-nav">
<li class="nav-item">
<a href="#dashboard" class="nav-link collapsed">
<i class="bi bi-speedometer2"></i>
<span>Dashboard</span>
</a>
</li>
<li class="nav-heading">Main Data</li>
<li class="nav-item">
<a href="#table" class="nav-link collapsed">
<i class="bi bi-grid-3x3-gap"></i>
<span>Table</span>
</a>
</li>
<li class="nav-item">
<a href="#product" class="nav-link collapsed">
<i class="bi bi-list-ul"></i>
<span>Product</span>
</a>
</li>
<li class="nav-item">
<a href="#product-category" class="nav-link collapsed">
<i class="bi bi-grid"></i>
<span>Product Category</span>
</a>
</li>
<li class="nav-heading">Operation</li>
<li class="nav-item">
<a href="#balance-adjustment" class="nav-link collapsed">
<i class="bi bi-coin"></i>
<span>Balance Adjustment</span>
</a>
</li>
<li class="nav-heading">Report</li>
<li class="nav-item">
<a href="#report/sale-summary" class="nav-link collapsed">
<i class="bi bi-graph-up-arrow"></i>
<span>Sale Summary</span>
</a>
</li>
<li class="nav-item">
<a href="#report/product-summary" class="nav-link collapsed">
<i class="bi bi-clipboard-data"></i>
<span>Product Summary</span>
</a>
</li>
<li class="nav-item">
<a href="#report/sale-history" class="nav-link collapsed">
<i class="bi bi-clock-history"></i>
<span>Sale History</span>
</a>
</li>
<li class="nav-heading">System Setting</li>
<li class="nav-item">
<a href="#user" class="nav-link collapsed">
<i class="bi bi-people"></i>
<span>System User</span>
</a>
</li>
</ul>
</aside>
<main id="main" class="main">
<div id="content">
<div class="pagetitle">
<h1>Dashboard</h1>
</div>
</div>
</main>
</body>
</html>
Replace the contents of resources/views/user/login.blade.php with the code below. If the file doesn’t exist yet, please create it.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
{{-- Laravel jQuery POS authentication setup @ https://laravelcenter.com --}}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="images/favicon.png">
<title>{{ env('APP_NAME') }}</title>
<!-- Google Fonts -->
<link href="https://fonts.gstatic.com" rel="preconnect">
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i"
rel="stylesheet">
@vite(['resources/js/app.js', 'resources/css/app.css', 'resources/css/admin.css'])
</head>
<style>
.loading {
background: lightgoldenrodyellow url('./images/processing.gif') no-repeat center 65%;
height: 80px;
width: 100px;
position: fixed;
border-radius: 4px;
left: 50%;
top: 50%;
margin: -40px 0 0 -50px;
z-index: 2000;
display: none;
}
</style>
<body style="display: none;">
<div class="loading"></div>
<section class="vh-100"
style="background-image: url('images/bg.jpg');background-position: center; background-repeat: no-repeat; background-size: cover;">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col-12 col-md-8 col-lg-6 col-xl-5">
<div class="card shadow-2-strong" style="border-radius: 1rem; background: lightsteelblue;">
<div class="card-body p-5">
<img src="./images/favicon.png"
style="position: absolute; top: 10px; left: 10px; height: 80px;" />
<h1 class="mb-4 text-center fw-bold">Sign In</h1>
<form id="frmLogin" method="post" action="{{ url('./login') }}">
@csrf
<div class="form-outline mb-4">
<input type="text" class="form-control form-control-lg" placeholder="Username"
id="username" name="username" autofocus />
</div>
<div class="form-outline mb-4">
<input type="password" class="form-control form-control-lg" placeholder="Password"
id="password" name="password" />
</div>
<div class="d-grid mb-2">
<button class="btn btn-primary btn-lg" type="submit">
Login
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
<script>
document.addEventListener('DOMContentLoaded', function() {
$("body").show();
$("input[autofocus]").focus();
$(document).on("submit", "form#frmLogin", function(event) {
event.preventDefault();
$(".loading").show();
var form = $(this);
var data = new FormData(form[0]);
var url = form.attr("action");
$.ajaxSetup({
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content"),
},
});
$.ajax({
type: "POST",
url: url,
data: data,
cache: false,
contentType: false,
processData: false,
success: function(data) {
$(".is-invalid").removeClass("is-invalid");
$("span.invalid-feedback").remove();
if (!data.success) {
for (var control in data.errors) {
$("#" + control).addClass("is-invalid");
$(
"<span class='invalid-feedback'>" +
data.errors[control] +
"</span>"
).insertAfter($("#" + control));
}
$("input[autofocus]").focus();
} else {
window.location.href = '/';
}
$(".loading").hide();
},
error: function(xhr, textStatus, errorThrown) {
alert("Error: " + errorThrown);
},
});
return false;
});
});
</script>
</html>
Replace the contents of resources/views/404.blade.php with the code below. If the file doesn’t exist, create it manually. This view will be used for the fallback route when no other routes match.
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
{{-- Laravel jQuery POS authentication setup @ https://laravelcenter.com --}}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="images/favicon.png">
<title>{{ env('APP_NAME') }}</title>
<!-- Google Fonts -->
<link href="https://fonts.gstatic.com" rel="preconnect">
<link
href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Nunito:300,300i,400,400i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i"
rel="stylesheet">
@vite(['resources/js/app.js', 'resources/css/app.css', 'resources/css/admin.css'])
</head>
<body style="display: none;">
<div class="container">
<section class="section error-404 min-vh-100 d-flex flex-column align-items-center justify-content-center">
<h1>404</h1>
<h2>The page you are looking for doesn't exist.</h2>
<a class="btn" href="{{ url('/') }}">Back to home</a>
<img src="{{ url('images/not-found.svg') }}" class="img-fluid" alt="Page Not Found">
</section>
</div>
</body>
Replace the contents of routes/web.php with the code below
<?php
// Laravel jQuery POS authentication setup @ https://laravelcenter.com
use App\Http\Controllers\UserController;
use Illuminate\Support\Facades\Route;
Route::middleware('guest')->match(['get', 'post'], '/login', [UserController::class, 'login'])->name('login');
Route::middleware('auth')->group(function () {
Route::post('/user/logout', [UserController::class, 'logout']);
Route::get('/', function () {
return view('layout.admin');
});
});
Route::fallback(function () {
return view('404');
});
Run the following command to compile your assets:
npm run dev
Use <strong>npm run build</strong>
for production.
Once compiled, visit:
http://laravel-jquery-pos
You should now see your Laravel POS with jQuery project running with the database tables, sample data, and a basic login system ready for upcoming features.
Please use this default account to log in
superadmin
| Password: 123456
admin
| Password: 123456
cashier
| Password: 123456
Well done! 🎉 You’ve now completed the foundational setup of your Laravel POS with jQuery project. In this part, you created essential database tables, added sample data, and implemented a basic authentication system—giving your POS app real structure and secure access control.
In the next tutorial, we’ll move on to building CRUD functionality for managing products, categories, tables, users, and balance adjustment. You’ll learn how to create, edit, and delete records using Laravel’s powerful tools—bringing your POS system to life.
👉 Continue to Part 4: Ajax Menu Navigation and System Data (CRUD)