Yeni bir Laravel API projesine her başladığımda, aynı setup için yarım gün harcayarak zaman kaybettiğim günler geride kaldı:
- Sanctum kimlik doğrulaması
- Spatie rolleri ve izinleri
- Tutarlı JSON hata yanıtları
- API versiyonlama
- Rate limiting
Projelerimde bunu tekrar tekrar yaparak, her seferinde işe yarayan bir yapı buldum. İşte tam olarak nasıl yaptığım.
1. Tutarlı JSON Yanıt Formatı
İlk olarak, her endpoint’in aynı JSON yapısını döndürmesini zorunlu kılan bir trait kuruyorum. Başarı ve hata durumları için farklı formatlar döndüren bir API’den daha sinir bozucu bir şey yoktur.
json([
'success' => true,
'message' => $message,
'data' => $data,
'meta' => [
'version' => 'v1',
'timestamp' => now()->toISOString(),
],
], $statusCode);
}
public function errorResponse($message = 'Error', $statusCode = 400, $errors = [])
{
return response()->json([
'success' => false,
'message' => $message,
'errors' => $errors,
'meta' => [
'version' => 'v1',
'timestamp' => now()->toISOString(),
],
], $statusCode);
}
public function notFoundResponse($message = 'Resource not found')
{
return $this->errorResponse($message, 404);
}
public function unauthorizedResponse($message = 'Unauthorized')
{
return $this->errorResponse($message, 401);
}
public function validationErrorResponse($errors, $message = 'Validation failed')
{
return $this->errorResponse($message, 422, $errors);
}
}
Her controller bu trait’i kullanır. Artık tutarsız yanıt yok.
2. Küresel İstisna Yöneticisi
İstisnaları her bir controller’ın içinde ele almak yerine, hepsini tek bir yerde yakalıyorum.
expectsJson()) {
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'success' => false,
'message' => 'Resource not found',
], 404);
}
if ($exception instanceof AuthenticationException) {
return response()->json([
'success' => false,
'message' => 'Unauthenticated',
], 401);
}
if ($exception instanceof ValidationException) {
return response()->json([
'success' => false,
'message' => 'Validation failed',
'errors' => $exception->errors(),
], 422);
}
if ($exception instanceof NotFoundHttpException) {
return response()->json([
'success' => false,
'message' => 'Route not found',
], 404);
}
}
return parent::render($request, $exception);
}
}
Artık her istisna, temiz bir JSON yanıtı döndürüyor – API’de çirkin HTML hata sayfaları yok.
3. API Versiyonlama Yapısı
Her zaman yolları ve controller’ları versiyona göre ayırırım. Bu sayede gelecekteki değişiklikler mevcut istemcileri bozmaz.
app/Http/Controllers/Api/
├── V1/
│ ├── AuthController.php
│ └── UserController.php
└── V2/ ← geleceğe hazır
routes/api.php dosyasında:
group(function () {
// Public auth routes
Route::prefix('auth')->middleware('throttle:auth')->group(function () {
Route::post('register', [AuthController::class, 'register']);
Route::post('login', [AuthController::class, 'login']);
});
// Protected routes
Route::middleware('auth:sanctum')->group(function () {
Route::post('auth/logout', [AuthController::class, 'logout']);
Route::get('auth/me', [AuthController::class, 'me']);
Route::get('users', [UserController::class, 'index']);
});
});
Temiz, versiyonlu, genişletilmesi kolay.
4. Sanctum Kimlik Doğrulaması
Tüm API kimlik doğrulamaları için Laravel Sanctum kullanıyorum. İşte temiz bir AuthController:
$request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
]);
$user->assignRole('user');
$token = $user->createToken('api-token')->plainTextToken;
return $this->successResponse([
'user' => $user,
'token' => $token,
], 'Registration successful', 201);
}
public function login(LoginRequest $request)
{
if (!Auth::attempt($request->only('email', 'password'))) {
return $this->unauthorizedResponse('Invalid credentials');
}
$user = Auth::user();
$token = $user->createToken('api-token')->plainTextToken;
return $this->successResponse([
'user' => $user,
'token' => $token,
], 'Login successful');
}
public function logout(Request $request)
{
$request->user()->currentAccessToken()->delete();
return $this->successResponse(null, 'Logged out successfully');
}
public function me(Request $request)
{
return $this->successResponse(
$request->user()->load('roles', 'permissions')
);
}
}
5. Spatie Rolleri ve İzinleri
Rol sistemi için her zaman spatie/laravel-permission kullanıyorum. İşte benim seeder’ım:
$permission]);
}
// Rolleri oluştur ve izinleri ata
$admin = Role::firstOrCreate(['name' => 'admin']);
$admin->syncPermissions($permissions);
$editor = Role::firstOrCreate(['name' => 'editor']);
$editor->syncPermissions(['view users', 'edit users']);
Role::firstOrCreate(['name' => 'user']);
}
}
php artisan db:seed --class=RolePermissionSeeder komutunu çalıştırın ve tam bir rol sistemi hazır olacak.
6. Rate Limiting
app/Providers/RouteServiceProvider.php dosyasında:
by($request->user()->id ?: $request->ip());
});
RateLimiter::for('auth', function (Request $request) {
return Limit::perMinute(10)->by($request->ip());
});
Kaba kuvvet saldırılarını önlemek için kimlik doğrulama yollarında daha sıkı limitler. Genel API yolları için dakikada 60 istek.
Sonuç
API’mden dönen her yanıt bu şekilde görünüyor:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe",
"email": "[email protected]",
"roles": ["admin"],
"permissions": ["view users", "edit users"]
},
"meta": {
"version": "v1",
"timestamp": "2026-03-05T10:00:00Z"
}
}Temiz, tutarlı, öngörülebilir. Her endpoint’te. Her seferinde.
Kapatırken
Bu yapı benim için birçok projede şekillendi. Ana prensipler şunlardır:
- Tek yanıt formatı — bir trait kullan, her yerde zorla
- İstisnalar için tek yer — küresel yönetici, her controller için değil
- Günden bir versiyonlama — gelecekte kendinize teşekkür edeceksiniz
- Günden bir roller — sonradan eklemek zor
Bunları manuel olarak ayarlamak istemiyorsanız, her şey dahil bir başlangıç kiti hazırladım – migrations, seeders, controller’lar, Postman koleksiyonu ve README belgeleri hepsi dahil.
Kaynak: Orijinal Makale


