Build a REST API with Laravel 13 and Sanctum in 30 Minutes
Dinesh Wijethunga
What We're Building
In this tutorial, we'll build Laravel REST API 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.
