# Laravel ile JSON API uçları yazmak

> Laravel'de arayüzden bağımsız JSON API uç noktaları oluşturmanın pratik yollarını anlattım.

- Yayın: 2015-05-03
- Kategori: Web Geliştirme
- Etiketler: Laravel, API
- Okuma süresi: 3 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/laravel-ile-json-api-uclari-yazmak/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Laravel ile [Blade şablonları](/blog/blade-sablon-motoru-tekrar-eden-htmli-bitirmek) üretmek yerine JSON döndürmeye geçtiğimde ilk zorluk "bunu nasıl yapılandıracağım?" oldu. Formların action'larına POST atılan, redirect ile yönlendirilen klasik web uygulamasından farklı bir düşünce biçimi gerektiriyor. İstemci artık bir tarayıcı değil; belki mobil uygulama, belki başka bir servis, belki de AJAX isteği atan bir JavaScript kodu.

Bu yazıda [Laravel 5](/blog/laravel-5e-gecis-yeni-dizin-yapisi-ve-env) ile JSON API uç noktalarını (endpoint) nasıl yazdığımı, yanıt yapısını nasıl düzenlediğimi ve sık karşılaştığım durumları nasıl ele aldığımı anlatacağım.

## Route tanımlamak

Laravel 5'te API rotaları (route) için `app/Http/routes.php` içinde ayrı bir gruba almak iyi bir alışkanlık. Hem URL öneki (`/api/v1/...`) eklemeyi kolaylaştırıyor hem de middleware uygulamayı düzenliyor.

```php
Route::group(['prefix' => 'api/v1'], function () {
    Route::get('orders',         'Api\OrderController@index');
    Route::post('orders',        'Api\OrderController@store');
    Route::get('orders/{id}',    'Api\OrderController@show');
    Route::put('orders/{id}',    'Api\OrderController@update');
    Route::delete('orders/{id}', 'Api\OrderController@destroy');
});
```

`Api\` namespace'i, API controller'larını normal web controller'larından ayırmak için kullanıyorum. `app/Http/Controllers/Api/` altındaki dosyalar bu prefix'i alıyor.

## JSON yanıt döndürmek

Laravel'de JSON yanıtı döndürmek için `response()->json()` kullanıyorsunuz. İkinci parametre durum kodunu belirliyor.

```php
class OrderController extends Controller
{
    public function index()
    {
        $orders = Order::all();

        return response()->json([
            'data' => $orders,
        ], 200);
    }

    public function store(Request $request)
    {
        $order = Order::create($request->only('product_id', 'quantity'));

        return response()->json([
            'data' => $order,
        ], 201);
    }

    public function show($id)
    {
        $order = Order::find($id);

        if (!$order) {
            return response()->json([
                'message' => 'Sipariş bulunamadı.',
            ], 404);
        }

        return response()->json([
            'data' => $order,
        ], 200);
    }
}
```

Bir konuya dikkat ettim: her yanıtta `data` anahtarını kullanmak. Başta "neden bu katmanı ekleyeyim?" diye düşündüm ama istemci tarafında yanıt yapısı tutarlı olunca "buradaki `data` benim verimsem hata da ayrı bir anahtarda" şeklinde net bir sözleşme oluşuyor.

## Hata yanıtlarını standartlaştırmak

Uygulama genelinde hata yanıtlarını tek bir biçimde döndürmek, istemciyi yazan kişinin işini kolaylaştırıyor. Tek bir yardımcı metod yeterli:

```php
protected function hataDon($mesaj, $kod = 400): \Illuminate\Http\JsonResponse
{
    return response()->json([
        'error'   => true,
        'message' => $mesaj,
    ], $kod);
}
```

Sonra controller içinde `return $this->hataDon('Geçersiz veri.', 422);` şeklinde kullanıyorsunuz. Her controller'da ayrı format üretmiyorsunuz.

Bunu bir temel controller'a koymak yerine ayrı bir trait'e taşımak daha temiz olabiliyor. Böylece bu davranışa ihtiyaç duyan her controller'a eklenebiliyor, zorunlu bir kalıtım hiyerarşisi oluşturulmuyor.

## Validation ve API

[Laravel'in validation sistemi](/blog/laravel-ile-form-dogrulama-validation-pratigi) normalde hata durumunda redirect yapıyor. API'de redirect değil; JSON hata yanıtı istiyoruz.

`FormRequest` sınıfı burada işe yarıyor. Bir `StoreOrderRequest` oluşturup `wantsJson()` kontrolünü kaldırırsanız Laravel 5, JSON isteği için otomatik olarak 422 durum koduyla JSON hata yanıtı dönüyor.

```php
class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'product_id' => 'required|integer|exists:products,id',
            'quantity'   => 'required|integer|min:1',
        ];
    }
}
```

Controller'da tip belirtmek yeterli:

```php
public function store(StoreOrderRequest $request)
{
    // Bu noktaya gelindiyse doğrulama geçti demektir
    $order = Order::create($request->only('product_id', 'quantity'));

    return response()->json(['data' => $order], 201);
}
```

Doğrulama başarısız olduğunda Laravel otomatik olarak şöyle bir yanıt üretiyor:

```json
{
  "product_id": ["Ürün alanı zorunludur."],
  "quantity": ["Miktar alanı zorunludur."]
}
```

422 durum koduyla.

## Content-Type başlığını doğru ayarlamak

İstemci tarafında JSON gönderirken `Content-Type: application/json` başlığını eklemeyi unutmamak gerekiyor. Laravel bu başlığa bakarak isteği JSON olarak ayrıştırıyor. `$request->input()` ile değerlere ulaşabilmek için bu önemli.

Bu başlık eksik gönderildiğinde Laravel isteği form verisi (form-data) olarak işliyor. JSON gövdesi gönderdiniz ama `$request->input('product_id')` null dönüyor — ve nedenini anlamak biraz zaman alıyor. Bunu Postman ile test ederken mutlaka `Content-Type: application/json` başlığını ve `Accept: application/json` başlığını eklemek gerekiyor. İkincisi Laravel'in doğrulama hatasını JSON olarak döndürmesini sağlıyor; eksik bırakırsanız HTML hata sayfası alabilirsiniz.

## İlk gerçek API deneyimi

Bu yöntemi kullanarak küçük bir sipariş API'si yazdığımda ve bunu basit bir iOS uygulamasıyla bağladığımda çok temiz hissettirdi. Sunucu tarafı sadece veri döndürüyor; nasıl gösterileceğini bilmek zorunda değil. İstemci tarafı sadece veriyi alıp gösteriyor; nasıl üretildiğini bilmek zorunda değil.

Bu ayrışma, uygulamayı büyütürken çok işe yarıyor. Web arayüzü, mobil uygulama ve başka bir servis aynı API'yi kullanıyor. Mantığı bir kez yazıyorsunuz.
