TutorialTypeScript

TypeScript Generics for Laravel Developers: A Practical Guide

D

Dinesh Wijethunga

April 1, 2026 9 min readIntermediate 2,885 views
🎓

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.

D

Dinesh Wijethunga

Senior Full Stack Developer · Building SaaS products & teaching Laravel/React · 10+ years experience · Founder of Orion360 · Based in Dubai, UAE.

Reviews & Ratings

Sign in to leave a review.

Comments(5)

Guest comments are held for moderation.