Kritim Yantra
Mar 24, 2025
Laravel remains one of the most popular PHP frameworks for building robust APIs, and with Laravel 12, the process has become even more streamlined. In this guide, we'll walk through the essential steps to build a modern API with Laravel 12, covering everything from setup to deployment.
Before we dive in, let's understand why Laravel is an excellent choice for API development:
First, create a new Laravel project:
composer create-project laravel/laravel laravel-api
cd laravel-api
Install API:
php artisan install:api
Laravel separates web and API routes. All API routes go in routes/api.php
:
use App\Http\Controllers\AuthController;
use App\Http\Controllers\PostController;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::apiResource('posts', PostController::class);
Route::post('/logout', [AuthController::class, 'logout']);
});
Notice how we're using auth:sanctum
middleware to protect our routes.
Let's create a Post
model with migration:
php artisan make:model Post -m
Edit the migration file:
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
Run the migration:
php artisan migrate
Create an AuthController:
php artisan make:controller AuthController
Implement the authentication methods:
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8',
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken,
], 201);
}
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken,
]);
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Logged out']);
}
}
Generate a controller for our posts:
php artisan make:controller PostController --api
Implement the CRUD operations:
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
return Post::where('user_id', auth()->id())->get();
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'content' => 'required|string',
]);
$post = Post::create([
'user_id' => auth()->id(),
'title' => $validated['title'],
'content' => $validated['content'],
]);
return response()->json($post, 201);
}
public function show(Post $post)
{
$this->authorize('view', $post);
return $post;
}
public function update(Request $request, Post $post)
{
$this->authorize('update', $post);
$validated = $request->validate([
'title' => 'sometimes|string|max:255',
'content' => 'sometimes|string',
]);
$post->update($validated);
return $post;
}
public function destroy(Post $post)
{
$this->authorize('delete', $post);
$post->delete();
return response()->noContent();
}
}
Create a policy for the Post model:
php artisan make:policy PostPolicy --model=Post
Define the authorization rules in app/Policies/PostPolicy.php
:
public function view(User $user, Post $post)
{
return $user->id === $post->user_id;
}
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
public function delete(User $user, Post $post)
{
return $user->id === $post->user_id;
}
✅ No need to register the policy manually in AuthServiceProvider.php
!
In Laravel 12, if you follow the convention—placing your policies in the app/Policies
directory and naming them appropriately (for example, PostPolicy
for the Post
model)—Laravel will automatically discover and use them.
If you don’t follow this convention (for example, if you place policies in a different folder or name them differently), you would need to register them manually using the Gate::policy()
method in AppServiceProvider
.
For consistent API responses, create a trait in app/Traits/ApiResponse.php
:
namespace App\Traits;
trait ApiResponse
{
protected function success($data = null, $message = null, $code = 200)
{
return response()->json([
'success' => true,
'message' => $message,
'data' => $data,
], $code);
}
protected function error($message = null, $code = 400, $errors = null)
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors,
], $code);
}
}
Now you can use this trait in your controllers for consistent responses.
Laravel provides built-in rate limiting. Configure it in app/Http/Kernel.php
:
protected $middlewareGroups = [
'api' => [
'throttle:api',
// ...
],
];
Then configure the rates in routes/api.php
:
Route::middleware(['auth:sanctum', 'throttle:60,1'])->group(function () {
// Your protected routes here
});
For API documentation, install L5-Swagger:
composer require darkaonline/l5-swagger
php artisan vendor:publish --provider "L5Swagger\L5SwaggerServiceProvider"
Add annotations to your controllers:
/**
* @OA\Post(
* path="/api/register",
* summary="Register a new user",
* @OA\RequestBody(
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* @OA\Property(
* property="name",
* type="string"
* ),
* @OA\Property(
* property="email",
* type="string"
* ),
* @OA\Property(
* property="password",
* type="string"
* ),
* example={"name": "John Doe", "email": "john@example.com", "password": "secret"}
* )
* )
* ),
* @OA\Response(
* response=201,
* description="User registered successfully"
* ),
* @OA\Response(
* response=422,
* description="Validation error"
* )
* )
*/
Generate the documentation:
php artisan l5-swagger:generate
Laravel provides excellent testing tools. Create a test:
php artisan make:test PostApiTest
Write some tests:
public function test_user_can_create_post()
{
$user = User::factory()->create();
$token = $user->createToken('test-token')->plainTextToken;
$response = $this->withHeaders([
'Authorization' => 'Bearer ' . $token,
])->postJson('/api/posts', [
'title' => 'Test Post',
'content' => 'This is a test post content',
]);
$response->assertStatus(201)
->assertJson([
'title' => 'Test Post',
'content' => 'This is a test post content',
]);
}
Run the tests:
php artisan test
When deploying your Laravel API:
APP_ENV=production
in your .env
filephp artisan key:generate
php artisan optimize
config/cors.php
/api/v1/posts
Building APIs with Laravel 12 is a straightforward process thanks to the framework's built-in features and excellent ecosystem. By following this guide, you've learned how to:
Remember that API development is an iterative process. As your application grows, you'll need to consider additional aspects like:
Laravel provides all the tools you need to build professional-grade APIs that can scale with your application's needs. Happy coding!
Transform from beginner to Laravel expert with our personalized Coaching Class starting June 23, 2025. Limited enrollment ensures focused attention.
1-hour personalized coaching
Build portfolio applications
Industry-standard techniques
Interview prep & job guidance
Complete your application to secure your spot
Thank you for your interest in our Laravel mentorship program. We'll contact you within 24 hours with next steps.
z0e
May 28, 2025 01:32 PM
Kritim Yantra
May 30, 2025 09:17 AM