TypeScript Generics for Laravel Developers: A Practical Guide
Dinesh Wijethunga
What We're Building
In this tutorial, we'll build TypeScript Generics step by step. By the end you'll have a working implementation you can use in your own projects, plus a clear understanding of why each piece is structured the way it is.
Prerequisites
- PHP 8.4 installed (via Laravel Herd or your preferred method)
- Composer >= 2.x
- Basic familiarity with Laravel and object-oriented PHP
- A MySQL or PostgreSQL database
Step 1: Project Setup
Let's start with a fresh Laravel 13 project:
composer create-project laravel/laravel:^13.0 my-project
cd my-project
php artisan key:generate
Configure your .env file with your database credentials, then run the initial migrations:
php artisan migrate
Step 2: Create the Model & Migration
We'll use PHP 8.4's new attribute syntax to keep our model clean and self-documenting:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Model;
#[Fillable(['name', 'slug', 'content', 'is_published'])]
class Post extends Model
{
public bool $isPublished {
get => $this->status === 'published';
}
protected function casts(): array
{
return ['published_at' => 'datetime'];
}
}
Step 3: Build the Controller
With Laravel 13's new attribute-based middleware and authorization, our controller stays lean:
#[Middleware('auth:sanctum')]
class PostController extends Controller
{
public function index(Request $request): AnonymousResourceCollection
{
$posts = Post::query()
->published()
->with(['author', 'category'])
->withCount('comments')
->paginate(12);
return PostResource::collection($posts);
}
}
Step 4: Wire Up the Routes
Route::prefix('v1')->group(function () {
Route::get('posts', [PostController::class, 'index']);
Route::get('posts/{slug}', [PostController::class, 'show'])
->name('posts.show');
});
Testing Our Endpoint
Let's write a quick Pest test to make sure everything works:
it('returns paginated published posts', function () {
Post::factory(15)->published()->create();
Post::factory(5)->draft()->create();
getJson('/api/v1/posts')
->assertOk()
->assertJsonCount(12, 'data')
->assertJsonStructure(['data', 'meta', 'links']);
});
Next Steps
You now have a solid foundation. In the next part of this series, we'll add authentication, image uploads, and a caching layer to make this production-ready.
Reviews & Ratings
Sign in to leave a review.
