Introduction
Building a RESTful API with Laravel is one of the most common tasks for backend developers today. In this comprehensive tutorial, we'll build a fully functional REST API from scratch — including authentication, resource controllers, validation, and proper JSON responses.
By the end of this guide you will have a production-ready API structure that you can use as a boilerplate for your next project.
Prerequisites
- PHP 8.2+ installed
- Composer installed globally
- MySQL or SQLite database
- Basic knowledge of PHP and OOP
Step 1: Create a New Laravel Project
Open your terminal and run the following command to scaffold a fresh Laravel application:
composer create-project laravel/laravel api-tutorial
cd api-tutorial
php artisan serve
Your app will be available at http://127.0.0.1:8000.
Step 2: Configure the Database
Open the .env file and update your database credentials:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=api_tutorial
DB_USERNAME=root
DB_PASSWORD=
Step 3: Create the Post Model & Migration
php artisan make:model Post -m
Open the generated migration file and define the schema:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->enum('status', ['draft', 'published'])->default('draft');
$table->timestamps();
});
Run the migration:
php artisan migrate
Step 4: Create the API Resource Controller
php artisan make:controller Api/PostController --api --model=Post
This generates a controller with index, store, show, update, and destroy methods.
Step 5: Define API Routes
Open routes/api.php and register the resource routes:
use App\Http\Controllers\Api\PostController;
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
});
Step 6: Implement the Controller
Here is the full implementation of the PostController:
latest()
->paginate(15);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:255',
'body' => 'required|string',
'status' => 'nullable|in:draft,published',
]);
$post = Post::create([
...$data,
'user_id' => auth()->id(),
'slug' => Str::slug($data['title']),
]);
return response()->json($post, 201);
}
public function show(Post $post)
{
return $post;
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$data = $request->validate([
'title' => 'sometimes|string|max:255',
'body' => 'sometimes|string',
'status' => 'sometimes|in:draft,published',
]);
$post->update($data);
return $post;
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return response()->noContent();
}
}
Step 7: Add Sanctum Authentication
Install and configure Sanctum for token-based API auth:
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
Create a login endpoint in routes/api.php:
Route::post('/login', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
if (!Auth::attempt($request->only('email', 'password'))) {
return response()->json(['message' => 'Invalid credentials'], 401);
}
$token = auth()->user()->createToken('api-token')->plainTextToken;
return response()->json(['token' => $token]);
});
Step 8: Test Your API
Use cURL or Postman to test:
# Login
curl -X POST http://127.0.0.1:8000/api/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"password"}'
# Get all posts (replace TOKEN)
curl http://127.0.0.1:8000/api/posts \
-H "Authorization: Bearer TOKEN"
# Create a post
curl -X POST http://127.0.0.1:8000/api/posts \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Hello World","body":"My first post","status":"published"}'
Common API Response Patterns
Always wrap responses in a consistent format. Here's a helper trait:
trait ApiResponse
{
protected function success($data, string $message = 'OK', int $code = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $code);
}
protected function error(string $message, int $code = 400)
{
return response()->json([
'success' => false,
'message' => $message,
], $code);
}
}
Conclusion
You now have a fully functional Laravel REST API with:
- ✅ Sanctum token authentication
- ✅ Resource controller with CRUD operations
- ✅ Request validation
- ✅ Authorization policies
- ✅ Consistent JSON responses
In the next part, we'll add API versioning, rate limiting, and a proper JSON:API-compliant response structure. Stay tuned!
Pro Tip: Always version your APIs from day one. Use
/api/v1/prefixes in your route groups so you can evolve the API without breaking existing clients.