Skip to content

13: Laravel Foundations - The PHP Framework

Laravel is to PHP what Nest.js is to TypeScript—a full-featured framework with elegant syntax, powerful features, and exceptional developer experience. If you’ve worked with Nest.js, Express, or Rails, Laravel will feel familiar yet distinctively refined.

By the end of this chapter, you’ll be able to:

  • ✅ Set up a Laravel project
  • ✅ Understand Laravel’s MVC architecture
  • ✅ Create routes and controllers
  • ✅ Use dependency injection (Laravel’s IoC container)
  • ✅ Work with Eloquent ORM
  • ✅ Handle requests and responses
  • ✅ Use middleware and service providers
  • ✅ Apply Laravel best practices

📁 View Code Examples on GitHub

This chapter includes Laravel foundation examples:

  • 01-setup-guide.md - Complete Laravel setup instructions
  • 02-routes.php - Comprehensive routing patterns
  • 03-controllers.php - Controller examples and comparisons
  • 04-dependency-injection.php - Service container and DI
  • 05-middleware.php - Middleware patterns
  • 06-form-requests.php - Form validation examples

Get started:

Terminal window
cd code/php-typescript-developers/chapter-13
# Review the setup guide for creating a Laravel project
cat 01-setup-guide.md
LaravelNest.jsExpressPurpose
Full-stackComplete framework vs library
ORMEloquentTypeORMDatabase abstraction
DI Container
CLIArtisanNest CLIN/A
Template EngineBladeEJS/PugEJS/Pug
Auth SystemBuilt-inPassportPassport

Laravel ≈ Nest.js (both are opinionated, full-featured frameworks)

Terminal window
npm install -g @nestjs/cli
nest new my-app
cd my-app
npm run start:dev
Terminal window
# Install Laravel via Composer
composer create-project laravel/laravel my-app
cd my-app
# Start development server
php artisan serve
# Visit: http://localhost:8000

Directory Structure:

my-app/
├── app/ # Application code
│ ├── Http/
│ │ └── Controllers/
│ └── Models/
├── routes/ # Route definitions
│ ├── web.php # Web routes
│ └── api.php # API routes
├── resources/ # Views, assets
│ └── views/
├── database/ # Migrations, seeders
├── public/ # Web root
└── artisan # CLI tool
users.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
@Controller('users')
export class UsersController {
@Get()
findAll() {
return ['Alice', 'Bob'];
}
@Get(':id')
findOne(@Param('id') id: string) {
return { id, name: 'Alice' };
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
return { id: 1, ...createUserDto };
}
}
routes/api.php
<?php
use App\Http\Controllers\UserController;
// GET /api/users
Route::get('/users', [UserController::class, 'index']);
// GET /api/users/{id}
Route::get('/users/{id}', [UserController::class, 'show']);
// POST /api/users
Route::post('/users', [UserController::class, 'store']);
// Or use resource routes (RESTful)
Route::apiResource('users', UserController::class);
app/Http/Controllers/UserController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller {
public function index() {
return response()->json(['Alice', 'Bob']);
}
public function show(string $id) {
return response()->json(['id' => $id, 'name' => 'Alice']);
}
public function store(Request $request) {
return response()->json([
'id' => 1,
...$request->all()
], 201);
}
}
users.service.ts
@Injectable()
export class UsersService {
findAll() {
return ['Alice', 'Bob'];
}
}
// users.controller.ts
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}
@Get()
findAll() {
return this.usersService.findAll();
}
}
app/Services/UserService.php
<?php
namespace App\Services;
class UserService {
public function getAll(): array {
return ['Alice', 'Bob'];
}
}
// app/Http/Controllers/UserController.php
class UserController extends Controller {
public function __construct(
private UserService $userService
) {}
public function index() {
return response()->json($this->userService->getAll());
}
}

Laravel automatically resolves dependencies via type hints!

import { IsString, IsEmail, MinLength } from 'class-validator';
export class CreateUserDto {
@IsString()
@MinLength(3)
name: string;
@IsEmail()
email: string;
}
@Post()
create(@Body() createUserDto: CreateUserDto) {
// Validated automatically
return this.usersService.create(createUserDto);
}
app/Http/Requests/StoreUserRequest.php
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreUserRequest extends FormRequest {
public function rules(): array {
return [
'name' => ['required', 'string', 'min:3'],
'email' => ['required', 'email', 'unique:users'],
];
}
}
// Controller
class UserController extends Controller {
public function store(StoreUserRequest $request) {
// $request is automatically validated!
$validated = $request->validated();
// Create user...
}
}
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log(`${req.method} ${req.url}`);
next();
}
}
app/Http/Middleware/LogRequests.php
<?php
namespace App\Http\Middleware;
use Closure;
class LogRequests {
public function handle($request, Closure $next) {
\Log::info($request->method() . ' ' . $request->path());
return $next($request);
}
}
// Apply to route
Route::get('/users', [UserController::class, 'index'])
->middleware(LogRequests::class);
// Or globally in app/Http/Kernel.php

Laravel’s Eloquent ORM is more elegant than TypeORM:

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
@OneToMany(() => Post, post => post.user)
posts: Post[];
}
// Usage
const user = await userRepository.findOne({ where: { id: 1 } });
const users = await userRepository.find();
app/Models/User.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model {
protected $fillable = ['name', 'email'];
public function posts() {
return $this->hasMany(Post::class);
}
}
// Usage (so much cleaner!)
$user = User::find(1);
$users = User::all();
$user->posts; // Automatic eager loading

More details in Chapter 14!

Laravel’s Artisan is like Nest CLI:

Terminal window
nest generate controller users
nest generate service users
nest generate module users
Terminal window
php artisan make:controller UserController
php artisan make:model User
php artisan make:migration create_users_table
php artisan make:request StoreUserRequest
php artisan make:middleware LogRequests

List all commands:

Terminal window
php artisan list
Terminal window
DATABASE_URL=postgresql://localhost/mydb
JWT_SECRET=secret
app.module.ts
ConfigModule.forRoot({
envFilePath: '.env',
});
// Usage
constructor(private configService: ConfigService) {}
const dbUrl = this.configService.get('DATABASE_URL');
Terminal window
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=mydb
JWT_SECRET=secret
<?php
// Usage (anywhere)
$dbHost = env('DB_HOST');
$secret = config('app.jwt_secret');
Terminal window
php artisan make:controller Api/UserController --api
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class UserController extends Controller {
// GET /api/users
public function index() {
return User::all();
}
// GET /api/users/{id}
public function show(User $user) {
return $user; // Route model binding!
}
// POST /api/users
public function store(Request $request) {
$validated = $request->validate([
'name' => 'required|string',
'email' => 'required|email|unique:users',
]);
$user = User::create($validated);
return response()->json($user, 201);
}
// PUT /api/users/{id}
public function update(Request $request, User $user) {
$validated = $request->validate([
'name' => 'sometimes|string',
'email' => 'sometimes|email|unique:users,email,' . $user->id,
]);
$user->update($validated);
return $user;
}
// DELETE /api/users/{id}
public function destroy(User $user) {
$user->delete();
return response()->noContent();
}
}
routes/api.php
<?php
Route::apiResource('users', UserController::class);
// Equivalent to:
// GET /api/users -> index()
// POST /api/users -> store()
// GET /api/users/{id} -> show()
// PUT /api/users/{id} -> update()
// DELETE /api/users/{id} -> destroy()

Laravel’s service providers are like Nest.js modules:

app/Providers/AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider {
public function register(): void {
// Bind interfaces to implementations
$this->app->bind(
\App\Contracts\UserRepositoryInterface::class,
\App\Repositories\UserRepository::class
);
}
public function boot(): void {
// Run code on application boot
}
}

Laravel includes PHPUnit with helpful assertions:

tests/Feature/UserApiTest.php
<?php
namespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
class UserApiTest extends TestCase {
public function test_can_list_users(): void {
User::factory()->count(3)->create();
$response = $this->getJson('/api/users');
$response->assertStatus(200)
->assertJsonCount(3);
}
public function test_can_create_user(): void {
$data = [
'name' => 'Alice',
'email' => 'alice@example.com',
];
$response = $this->postJson('/api/users', $data);
$response->assertStatus(201)
->assertJsonPath('name', 'Alice');
$this->assertDatabaseHas('users', $data);
}
}

Run tests:

Terminal window
php artisan test
  1. Laravel ≈ Nest.js - Both are full-featured, opinionated frameworks with rich ecosystems
  2. Routing is clean and expressive with multiple definition styles
  3. Dependency injection works automatically via type hints (no manual binding needed)
  4. Eloquent ORM is incredibly elegant - Active Record pattern beats TypeORM’s Data Mapper
  5. Artisan CLI generates boilerplate (controllers, models, migrations, tests, etc.)
  6. Middleware pipeline like Express/Nest for request/response modification
  7. Testing is built-in and powerful with PHPUnit integration
  8. Service container resolves dependencies automatically - just type-hint in constructors
  9. Form request validation provides clean, reusable validation logic
  10. Route model binding automatically injects models - no manual findOrFail()
  11. API resources provide consistent JSON transformation layer
  12. Laravel ecosystem rivals Node.js: Forge (deployment), Vapor (serverless), Nova (admin panel)
FeatureLaravelNest.js
LanguagePHPTypeScript
PhilosophyConvention over configurationTypeScript + Angular patterns
Learning CurveGentleModerate
ORMEloquent (Active Record)TypeORM (Data Mapper)
TemplatingBladeVarious (EJS, Handlebars)
Real-timeLaravel Echo + PusherSocket.io
Queue SystemBuilt-inBull (separate)
Admin PanelNova (commercial)Various

Now that you know Laravel basics, let’s dive deeper into Eloquent ORM and databases.

Next Chapter: 14: Database & ORMs: TypeORM meets Eloquent


Questions or feedback? Open an issue on GitHub