Before We Start — Understanding Laravel
Overview
A crisp primer before you dive into commands and code. What Laravel is, where it shines, what to expect in production, and which topics matter most.
What is Laravel?
Laravel is a modern PHP web framework focused on developer happiness and rapid delivery. It ships with elegant routing, Eloquent ORM, robust authentication & authorization, queues, events, notifications, mail, storage, and first-class testing support.
Dependencies (typical stack)
- PHP 8.2+ with common extensions (OpenSSL, PDO, Mbstring, Tokenizer, XML, Ctype, JSON, BCMath, Fileinfo).
- Composer for dependency management.
- Database (MySQL/MariaDB, PostgreSQL, SQLite, SQL Server).
- Cache: Redis or Memcached (speed, queues, sessions).
- Queue workers: Redis + Supervisor (Linux) or Horizon.
- Mail: SMTP / SES / Mailgun / Postmark.
- Storage: Local, S3-compatible, or other drivers.
Market value & production time
- High demand for CRUD-heavy business apps (admin panels, ERPs, POS, e-commerce, SaaS).
- Rapid MVPs: days to weeks with batteries included.
- Production hardening: add caching, queues, logging, metrics, CI/CD.
- Team velocity: consistent conventions → fewer surprises.
Strengths
- Clean syntax, expressive Eloquent ORM.
- Auth, policies, gates, notifications out of the box.
- Queues, jobs, events, broadcasting, task scheduler.
- Great DX: Artisan, Tinker, testing (Pest/PHPUnit), Horizon, Telescope.
- Huge ecosystem (Livewire, Inertia, Cashier, Scout, Socialite).
Weaknesses
- Not ideal for CPU-bound tasks (use queues/workers or external services).
- Careless Eloquent usage can cause N+1 queries (profile & eager load).
- Full-stack monolith may feel heavy for tiny static projects.
- Scaling requires basic Linux/infra familiarity (Supervisor, Redis, Opcache).
Why Laravel?
- Fast to ship, consistent patterns, massive community.
- Battle-tested primitives for auth, queues, emails, files, APIs.
- Easy path from MVP → production (caching, Horizon, Octane if needed).
Why not Laravel?
- Ultra-low latency, specialized realtime systems may prefer Go/Rust/Node ws stacks.
- Pure static front-end sites (no server logic) are overkill on a PHP framework.
- Heavy data science/ML workloads belong outside the PHP runtime.
Extent of use & scope
- CRUD apps, dashboards, ERPs, POS, accounting, HR, e-commerce.
- Public APIs with Sanctum/Passport, API Resources, rate-limiters.
- Event-driven modules (events, listeners, jobs) and scheduled tasks.
- Hybrid stacks with Livewire/Inertia, or headless APIs for SPA/mobile.
Important topics to master
- Routing & Controllers
- Eloquent (Scopes, Relations, Mutators, Casts)
- Migrations & Seeders
- Validation & Form Requests
- Auth (guards), Policies & Gates
- Queues, Jobs, Events, Listeners
- Notifications, Mailables
- Caching (Redis), Rate Limiting
- File Storage (local/S3)
- API Resources, Middleware
- Testing (Pest/PHPUnit), Factories
- Horizon, Telescope, Octane (when needed)
Tip: profile database queries early (Laravel Debugbar locally, Telescope) and add ->with() to eager load relations.
Install Laravel (easy steps)
Installation
Do these in order. Keep it simple.
1) Check your PHP
You need PHP 8.2 or higher. Common PHP extensions too.
# check version
php -v
2) Get PHP + Composer + Laravel CLI
Run the command for your system.
Windows (PowerShell as Admin)
iwr php.new/install/win/8.4 -UseB | iex
macOS (Terminal)
/bin/bash -c "$(curl -fsSL https://php.new/install/mac/8.4)"
Linux (Terminal)
/bin/bash -c "$(curl -fsSL https://php.new/install/linux/8.4)"
After it finishes, close and reopen your terminal.
3) If you already have PHP + Composer
Install the Laravel CLI tool:
composer global require laravel/installer
4) Make a new app
laravel new my-app
cd my-app
5) Run the app
php artisan serve
6) One-time setup
cp .env.example .env
php artisan key:generate
php artisan storage:link
7) Speed up (optional)
php artisan config:cache
php artisan route:cache
php artisan view:cache
Notes:
- PHP 8.2+ required.
- Put Composer’s global bin in PATH if CLI not found.
Esewa And Khalti Integration
Payments
Quick intro
This section reproduces only existing project code for eSewa and Khalti. Copy-ready snippets. No additions.
.env environment
.env
Values present in .env. Use {{ "{{ PLACEHOLDER " }}}} if missing.
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:9udJHIhqoyemNXWuHuZbm7KTZL2ntEEROAwJr1GnPcc=
APP_DEBUG=true
APP_URL=http://127.0.0.1:8000
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
# APP_MAINTENANCE_STORE=database
PHP_CLI_SERVER_WORKERS=4
BCRYPT_ROUNDS=12
...
APP_URL=http://127.0.0.1:8000
# eSewa — Sandbox (UAT)
ESEWA_MERCHANT=EPAYTEST
ESEWA_SECRET=8gBm/:&EnhH.1/q
# v2 form submit (sandbox)
ESEWA_FORM_BASE=https://rc-epay.esewa.com.np
ESEWA_FORM_PATH=/api/epay/main/v2/form
# Status Check (sandbox)
ESEWA_STATUS_BASE=https://rc.esewa.com.np
ESEWA_STATUS_PATH=/api/epay/transaction/status
# Khalti
KHALTI_BASE={{ KHALTI_BASE }}
KHALTI_SECRET_KEY={{ KHALTI_SECRET_KEY }}
Set production keys on deploy. Keep secrets out of git.
config/services.php
config/services.php
Add this block inside return [ ... ]; (if not present):
'khalti' => [
'base' => env('KHALTI_BASE', 'https://a.khalti.com'),
'secret_key' => env('KHALTI_SECRET_KEY'),
],
Routes
routes/web.php
<?php
// routes/web.php
use App\Http\Controllers\EsewaController;
use App\Http\Controllers\KhaltiController;
use App\Http\Controllers\ShopController;
Route::get('/', fn() => redirect()->route('items.index'));
Route::get('/items', [ShopController::class, 'index'])->name('items.index');
Route::get('/checkout/{item}', [ShopController::class, 'checkout'])->name('checkout.show');
// eSewa
Route::post('/pay/{item}', [EsewaController::class, 'initiate'])->name('esewa.pay');
Route::match(['GET','POST'], '/esewa/success', [EsewaController::class, 'success'])->name('esewa.success');
Route::match(['GET','POST'], '/esewa/failure', [EsewaController::class, 'failure'])->name('esewa.failure');
// Khalti
Route::post('/khalti/initiate/{item}', [KhaltiController::class, 'initiate'])->name('khalti.initiate');
Route::get('/khalti/verify', [KhaltiController::class, 'verify'])->name('khalti.verify');
// List payments
Route::get('/payments', [ShopController::class, 'payments'])->name('payments.index');
Controllers
app/Http/Controllers/EsewaController.php
<?php
// app/Http/Controllers/EsewaController.php
namespace App\Http\Controllers;
use App\Models\Item;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class EsewaController extends Controller
{
/** Build the v2 form payload with signature and auto-post to eSewa */
public function initiate(Request $request, Item $item)
{
// ... your existing initiate logic here (project's real code)
}
public function success(Request $request)
{
// ... your existing success callback verification logic
}
public function failure(Request $request)
{
$uuid = $request->input('transaction_uuid')
?? $request->query('transaction_uuid')
?? $request->query('oid')
?? $request->query('pid');
if ($uuid) Payment::where('pid', $uuid)->update(['status'=>'failed']);
return redirect()->route('payments.index')->with('fail','Payment cancelled or failed.');
}
}
app/Http/Controllers/KhaltiController.php
<?php
namespace App\Http\Controllers;
use App\Models\Item;
use App\Models\Payment;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
class KhaltiController extends Controller
{
/** Start a Khalti payment (Initiate) and redirect user to hosted page */
public function initiate(Request $request, Item $item)
{
// ... your existing initiate logic here (project's real code)
}
/** Verify/lookup result by pidx and update local payment status */
public function verify(Request $request)
{
// ... your existing verify/lookup logic here
}
/** Normalize Khalti statuses to our table’s: success | pending | failed */
private function mapKhaltiStatusToLocal(?string $khaltiStatus): string
{
$k = strtolower($khaltiStatus ?? '');
return match ($k) {
'completed' => 'success',
'initiated', 'pending' => 'pending',
'expired', 'user canceled', 'refunded', 'partially_refunded', 'failed' => 'failed',
default => 'pending',
};
}
}
Models
app/Models/Item.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
protected $fillable = ['name','price'];
}
app/Models/Payment.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Payment extends Model
{
protected $fillable = [
'item_id','payer_name','payer_email','amount','pid','ref_id','status','meta'
];
protected $casts = ['meta' => 'array'];
public function item() { return $this->belongsTo(Item::class); }
}
Migrations
database/migrations/2025_10_09_061747_create_items_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('items', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->unsignedInteger('price'); // in NPR
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('items');
}
};
database/migrations/2025_10_09_061748_create_payments_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('payments', function (Blueprint $table) {
$table->id();
$table->foreignId('item_id')->constrained()->cascadeOnDelete();
$table->string('payer_name')->nullable();
$table->string('payer_email')->nullable();
$table->unsignedInteger('amount');
$table->string('pid')->unique();
$table->string('ref_id')->nullable();
$table->enum('status',['pending','success','failed'])->default('pending');
$table->json('meta')->nullable();
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('payments');
}
};
Blade templates
resources/views/layouts/app.blade.php
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>{{ config('app.name','Laravel') }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary mb-3">
<div class="container">
<a class="navbar-brand" href="{{ route('items.index') }}">Shop</a>
<div class="ms-auto">
<a class="btn btn-outline-secondary btn-sm" href="{{ route('payments.index') }}">Payments</a>
</div>
</div>
</nav>
<main>
@yield('content')
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
resources/views/items/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container py-4">
<h1 class="mb-3">Items</h1>
<div class="row g-3">
@foreach($items as $item)
<div class="col-md-4">
<div class="card h-100">
<div class="card-body">
<h5 class="card-title mb-1">{{ $item->name }}</h5>
<p class="text-secondary mb-3">NPR {{ number_format($item->price) }}</p>
<a href="{{ route('checkout.show',$item) }}" class="btn btn-primary btn-sm">Buy</a>
</div>
</div>
</div>
@endforeach
</div>
</div>
@endsection
resources/views/items/checkout.blade.php
@extends('layouts.app')
@section('content')
<div class="container py-4">
<h1 class="mb-3">Checkout</h1>
<div class="card">
<div class="card-body">
<h5 class="mb-1">{{ $item->name }}</h5>
<p class="mb-3">Amount: NPR {{ number_format($item->price) }}</p>
<form method="POST" action="{{ route('esewa.pay', $item) }}">
@csrf
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Your Name (optional)</label>
<input type="text" name="payer_name" class="form-control" value="{{ old('payer_name') }}">
</div>
<div class="col-md-6">
<label class="form-label">Email (optional)</label>
<input type="email" name="email" class="form-control" value="{{ old('email') }}">
</div>
</div>
<div class="mt-3 d-flex gap-2">
<button type="submit" class="btn btn-success">Pay via eSewa</button>
<a href="{{ route('items.index') }}" class="btn btn-link">Back</a>
</div>
</form>
</div>
</div>
</div>
@endsection
resources/views/esewa/redirect.blade.php
<!doctype html>
<html lang="en">
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Redirecting…</title></head>
<body onload="document.forms[0].submit();" style="font-family:system-ui;padding:2rem;">
<p>Redirecting to eSewa…</p>
<form method="POST" action="{{ $endpoint }}">
@csrf
@foreach($payload as $k => $v)
<input type="hidden" name="{{ $k }}" value="{{ $v }}">
@endforeach
<noscript><button type="submit">Continue to eSewa</button></noscript>
</form>
</body>
</html>
resources/views/payments/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container py-4">
<h1 class="mb-3">Payments</h1>
<div class="table-responsive">
<table class="table table-striped align-middle">
<thead>
<tr>
<th>#</th>
<th>Item</th>
<th>PID</th>
<th>Ref ID</th>
<th>Amount</th>
<th>Status</th>
<th>Created</th>
</tr>
</thead>
<tbody>
@foreach($payments as $p)
<tr>
<td>{{ $p->id }}</td>
<td>{{ $p->item->name ?? '-' }}</td>
<td>{{ $p->pid }}</td>
<td>{{ $p->ref_id ?? '-' }}</td>
<td>NPR {{ number_format($p->amount) }}</td>
<td><span class="badge bg-{{ $p->status === 'success' ? 'success' : ($p->status === 'failed' ? 'danger' : 'warning text-dark') }}">{{ $p->status }}</span></td>
<td>{{ $p->created_at->format('Y-m-d H:i') }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endsection
How to test (sandbox)
- Migrate & seed an item: price in NPR.
- Open
/items → pick an item → checkout.
- Use sandbox keys; complete payment on eSewa/Khalti page.
- Return to the app; open
/payments to see status.
- For eSewa: verify signature or call Status Check API.
Summary & tips
- Never trust front-end success only; always verify on server.
- Store raw responses under
meta for audit.
- Rotate secrets regularly; keep them in
.env.
User Authentication (Registration, Login & Forgot Password)
Auth
Clean, default-friendly auth using Laravel’s starter kit. Works great for dashboards, shops, and admin areas.
Install a starter (Breeze)
composer require laravel/breeze --dev
php artisan breeze:install
# Choose: Blade (or Inertia/React/Vue)
npm install
npm run build
php artisan migrate
This scaffolds registration, login, logout, email verification, password reset, and basic views.
What you get
- Routes for register, login, logout, password reset.
- Controllers, views, and notifications for reset links.
- Middleware for authenticated pages.
Production notes
- Configure mail (SMTP/SES/etc.) for reset emails.
- Force HTTPS in production; set
SESSION_SECURE_COOKIE=true.
- Rate limit login attempts; enable recaptcha if public app.
Mail setup for password resets
# .env (example)
MAIL_MAILER=smtp
MAIL_HOST={{ MAIL_HOST }}
MAIL_PORT={{ MAIL_PORT }}
MAIL_USERNAME={{ MAIL_USERNAME }}
MAIL_PASSWORD={{ MAIL_PASSWORD }}
MAIL_ENCRYPTION={{ MAIL_ENCRYPTION }}
MAIL_FROM_ADDRESS={{ MAIL_FROM_ADDRESS }}
MAIL_FROM_NAME="{{ MAIL_FROM_NAME }}"
Protecting routes
// routes/web.php
Route::middleware(['auth'])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
Common commands (auth)
| Command | Usage |
php artisan make:model User -m | Create User model + migration (already exists in most apps). |
php artisan make:controller Auth/LoginController | Custom auth endpoints if not using Breeze/Fortify. |
php artisan make:notification ResetPasswordNotification | Customize reset email content. |
php artisan migrate | Run all auth tables (users, password_resets, sessions if DB driver). |
Tip
If you already have a custom layout, move Breeze’s generated Blade into your structure and keep routes/controllers as is. Test register → email reset → login → logout flow end-to-end.
No tutorials match your search in this category.