Laravel’de OpenAPI ile REST API dokümantasyonu

Eren Hatırnaz
Mobillium
Published in
7 min readMar 31, 2022

OpenAPI/Swagger ve onun Laravel içerisinde kullanımından önce neden REST API’lerde dokümantasyona ihtiyaç duyuyoruz konusuna çok kısa değinmek istiyorum. Öncesinde REST API hakkında bilgi edinmek isterseniz, takım arkadaşım Zehra’nın yazdığı “REST API (RESTful API) nedir?” yazısına mutlaka göz atmanızı tavsiye ederim.

İster dışarıya herkese açık bir şekilde sunduğumuz bir API olsun, isterse de sadece içeride takımlar arasında (Back-End <-> Mobil) kullandığımız bir API olsun, aramızdaki iletişimi net bir şekilde tanımlamamız gerekiyor. Özellikle de alınan ve dönülen verilerin değişken tiplerini bilmek oluşabilecek birçok veri tipi uyumsuzluğunun önüne geçiyor. Bir endpoint’den dönebilecek tüm hataların listelenmesi de bir o kadar önemli.

İşte bu tarz birçok iletişim sorununun önüne geçebilmek için geliştirilmiş bir çözümümüz var: OpenAPI.

OpenAPI Nedir?

OpenAPI Specification ya da eski adıyla Swagger Specification, REST API’lerin dokümante edilmesi için geliştirilmiş bir açıklama formatıdır. “Swagger” ise bu açıklama formatını kullanan açık kaynaklı araçlar barındıran bir settir. Yazının bundan sonraki kısmına OpenAPI ismiyle devam edeceğiz.

OpenAPI Specification, YAML ve JSON olmak üzere 2 farklı formatta yazılabiliyor. Tabii ki formatını öğrendikten sonra elle de yazabilirsiniz fakat genelde bu tarz dosyaların elle yazılması pek tercih ettiğimiz bir yöntem değil. Zira kod içerisinde değişiklik yaptığımızda farklı bir yerde başka bir dokümanı da güncellemek zorunda olmak pek sürdürülebilir bir yöntem olmuyor. Bu nedenle çoğunlukla geliştirme süreçlerinde OpenAPI anotasyonlarını kod içerisinde yorum satırlarında yazabileceğimiz ve sonrasında birkaç komut çalıştırarak bu anotasyonları tek bir YAML/JSON dosyasında toplayan araçlar kullanıyoruz.

Laravel’de OpenAPI kullanımı

Laravel gibi çok geniş bir komünitesi olan bir framework’ün de bu araçlardan mahrum kalması düşünülemez tabii ki. Siz de araştırdığınızda göreceksiniz ki Laravel’e bu araç setini getiren bir çok üçüncü parti kütüphane/araç mevcut, belki de çoktan araştırdınız ve karar vermekte zorlandığınız o yüzden bu yazıyı okuyorsunuz :) O halde okumaya devam edin.

Bizim Mobillium içerisinde Back-End takımı olarak tercih ettiğimiz Laravel kütüphanesinin ismi l5-swagger.

l5-swagger kütüphanesinin diğer alternatiflerine göre avantajı projeye en az eforla eklenebilmesi ve birkaç ayardan sonra hızlıca kullanmaya başlayabilmemiz oldu. Üstelik birden çok OpenAPI dokümantasyonu oluşturmanıza da izin veriyor, örneğin REST API’nizin v1 ve v2 şeklinde iki farklı sürümü için iki farklı OpenAPI dokümanı oluşturabiliyorsunuz.

L5-Swagger kurulumu ve ayarlanması

Kütüphanenin GitHub wiki sayfasındaki tablodan yararlanarak kurulum için gerekli komutları çalışabilirsiniz. Ben bu yazı boyunca örnek projemi Laravel 8.0 ile hazırlayacağım için aşağıdaki komutu çalıştırıyorum:

composer require “darkaonline/l5-swagger”

sonrasında ise gerekli dosyaları projemize import edebilmek için aşağıdaki komutu çalıştırmamız gerekiyor:

php artisan vendor:publish — provider “L5Swagger\L5SwaggerServiceProvider”

ve artık projemizde OpenAPI anotasyonları yazabilmemiz için gerekli her şey hazır. O halde ne duruyoruz, hadi başlayalım! (dilerseniz yukarıdaki komut ile birlikte gelen config/l5-swagger.php dosyasına bir göz atabilirsiniz)

PHP dosyalarındaki anotasyonları her değiştirdiğinizde OpenAPI json dosyasının güncellenmesi için çalıştırmanız gereken komut ise şöyle:

php artisan l5-swagger:generate

Bu komut her çalıştığında app/ dizininizin içerisindeki tüm PHP dosyalarındaki OpenAPI anotasyonlarını okur ve bunları storage/api-docs/api-docs.json dosyasına kaydeder. Swagger-UI ise bu json dosyasını okuyarak dokümantasyon sayfasını oluşturur.

Laravel’de OpenAPI Anotasyonları

Her şeyden önce yazmamızın mutlaka zorunlu olduğu OpenAPI için önemli olan bir anotasyonla başlamamız gerekiyor.

Bu anotasyonu olabildiğince merkezi bir dosyanın içerisine yazmamız gerekiyor. Biz genelde app/Http/Controllers dizini altındaki Controller.php dosyasına yazıyoruz. İsimlerinden de kolayca anlayacağınız üzere bu anotasyon ile API’mize bir isim ve versiyon vermiş oluyoruz. @OA\Info içerisinde bunlara ek olarak farklı tanımlamalar da yapabiliyorsunuz fakat şu aşamada title ve version yeterli gözüküyor. İsterseniz endpoint’lerimizi OpenAPI anotasyonları ile dokümante etmeye başlayalım.

Diğer tüm API servislerimize erişmemizin önünü açacak olan login servisi ile başlamak istiyorum.

Dilerseniz şimdi parantez içerisindeki tanımlamaları tek tek açıklayalım:

  • path: Endpoint’imizin URL’i
  • tags: Swagger-UI gibi araçlar üzerinde birbiriyle ilgili endpointleri gruplamak için kullandığımız etiketler. Biz genelde controller ismine göre şöyle bir kural ile etiket veriyoruz: AuthController -> Auth ya da UserAddressController -> User Address gibi.

@OA\RequestBody: İstek içerisinde karşı taraftan beklediğimiz içerik. Laravel içerisinde Request’lerimizi ayrı bir class olarak tanımlayıp, tüm input validation, authorization gibi işlemleri o class içerisinde tanımlayabiliyoruz. Dolayısıyla OpenAPI/Swagger tanımlamalamarımızı da kendi class’ı içerisinde tanımlayıp, o request’in kullanıldığı her yerde tek satırlık bir ifade ile kullanmak isteyebilirsiniz. İşte tam da bu noktada OpenAPI’nin referans özelliği devreye giriyor. Fakat önce gidip LoginRequest class’ımızdaki tanımlamaları inceleyelim:

  • request: Request’in ismi. Referans vermek için kullanıyoruz.
  • required: Request’in kendisinin zorunlu olup olmadığı.
  • @OA\JsonContent: Request içeriği olarak kabul ettiğimiz format. Eğer request’inizin içerisinde image gibi dosya kabul etme durumları varsa bu durumda JsonContent yerine @OA\MediaType kullanmalısınız. Biz örneklerimizde JsonContent ile ilerleyeceğiz.
    - required: Json içeriğindeki zorunlu olan alanlar.
    - @OA\Property: Json içerisinde kabul ettiğimiz input’ların tanımlamaları. Bu tanımlama içerisinde değişken tipi, min ve max uzunluğu, formatı, açıklaması ve örneği gibi birçok tanımlama yapabiliyoruz.

Unutmayın, Request’in kendi required tanımlaması ile JsonContent’in required tanımlaması birbirinden farklı şeyler. Birbiriyle karıştırmamak gerekiyor. LoginRequest’imizi bu şekilde tanımladıktan sonra artık AuthController sınıfımıza geri dönüp referansımızı verip devam edebiliriz.

@OA\Response: İsminden de anlaşılacağı üzere karşı taraftan gelen isteğe vereceğimiz cevap ya da cevaplar için kullandığımız bir tanımlama. Bu noktada ise genelde API’lerimizde başarı ve hata durumları için belirlediğimiz çeşitli formatlarımız olur ve bu format üzerinde sadece belirli kısımları değiştirerek ilerleriz. Aynı yapıyı koruyarak yine referans verme yöntemini kullanarak Success ve Error için tanımlamalarımızı genel bir yerde yaptıktan sonra istediğimiz her yerde kullanabiliyoruz. Biz yine Controller.php dosyasında tutmayı tercih ediyoruz.

@OA\Schema kullanımına ileride resource’larımızı tanımlarken daha detaylı gireceğiz. Şimdilik bu tanımlamalarımızı Controller.php dosyamıza ekleyip, AuthController’dan devam edelim.

İlk @OA\Response tanımlamamızda yanlış kullanıcı bilgileri girilmesi durumunda döndüğümüz HTTP 400 kodlu hatamızı tanımladık. “example=” tanımlaması ile döndüğümüz hatanın bir örneğini göstermiş oluyoruz. Swagger-UI bu response tanımlamasını gösterirken buradaki example kısmını kullanarak json örneğini oluşturacak.

İkinci @OA\Response tanımlamamızdan devam edelim. Burada diğerlerinden farklı bir şekilde referans vermişiz. allOf anahtar kelimesi OpenAPI içerisinde tıpkı OOP’de olduğu gibi katılım (extend) için kullanılıyor. Bu örnekte yapmaya çalıştığımız ise, bizim Success schema’mızın formatını extend etmek ve içerisindeki data kısmını özelleştirmek. Diğer kısımlar ise ilk tanımlandığı haliyle kalıyor. Böylece API’mizin her yerinde aynı formatı veri formatını kullanmaya devam edebiliyoruz.

Login endpointimizi tanımladıktan sonra dilerseniz şimdi de örnek olması açısından başka bir Controller dosyamızdaki endpoint’lere anotasyonlarımızı ekleyelim. Elbette register için de bir endpointimizin olması gerekiyor ama yazıyı fazla uzatmak istemiyorum. Bu yazıdan öğrendiklerinizle register endpoint’ini de ödev olarak siz yapabilirsiniz. :)

@OA\Schema ve @OA\Property

Endpoint’lerden önce dilerseniz controller içerisinde her endpoint’in cevabında döneceğimiz Address resource’umuzu tanımlayalım ve buna anotasyonlarımızı ekleyelim. OpenAPI anotasyonları yazarken tıpkı RequestBody’de olduğu gibi bu anotasyonları da resource dosyasında tanımlayıp referans vererek kullanabiliyorsunuz.

Tüm resource’larımızı @OA\Schema anotasyonu ile tanımlayacağız. “schema=” tanımlaması ile uygun bir isim verdikten sonra response olarak döndüğümüz, adresle ile ilgili istediğimiz property’lerı de tek tek tanımlıyoruz. Burada daha önce de söylediğim gibi property’lere ait birçok özelliği OpenAPI anotasyonları ile yazabiliyorsunuz. Hepsinin detayına bu yazıda girmek biraz zor maalesef. Daha detaylı bilgi için buradaki dökümana göz atabilirsiniz.

Authentication

Adres ile ilgili endpointlere geçmeden önce yapmamız gereken son bir adım kaldı, o da OpenAPI/Swagger’a authentication yöntemi olarak ne kullandığımızı söylemek. OpenAPI, api_key ve Oauth2 gibi yaygın kullanılan tüm authentication yöntemlerini destekliyor. Biz basit bir örnek üzerinden ilerlediğimiz için bunları kullanmak yerine, bearer token üzerinden ilerleyip devam edeceğiz. Diğer authentication yöntemleri için aynı dosyadaki tanımlamaları inceleyebilirsiniz.

config/l5-swagger.php dosyamızın içerisine aşağıdaki eklemeyi yapıyoruz:

AddressController

Artık adreslerimizle ilgili endpointlerimize anotasyonlarımızı eklemeye hazırız, haydi öyleyse index methodumuz ile başlayalım:

Burada farklı olarak gördüğünüz kısımlar üzerinden ilerlemek istemiyorum. “security” tanımlaması ile başlayalım. Buradaki “token” ifadesi biraz önce config/l5-swagger.php dosyasına eklediğimiz “token”i işaret ediyor aslında. Dolayısıyla orada hangi isimle tanımlama yaptıysak burada da aynı şekilde kullanıyoruz. Bu sayede endpointimizin authentication gerektiğini OpenAPI’ye de söylemiş olduk.

Hemen altındaki response tanımlamasından devam edecek olursak burada da yine referanslar karşımıza çıkıyor. Bu endpointimize HTTP Header’ı üzerinde Authorization bilgisi gelmezse doğal olarak 401 Unauthorized dönmemiz gerekiyor. Yine referans özelliğimiz yardımımıza yetişiyor, gidip Controller.php dosyamıza @OA\Response ile tanımlamamızı yapıyoruz.

Burada dikkat etmemiz gereken şey, referans olarak kullanacağımız @OA\Response tanımlamamızda “response=” kısmında HTTP kodu değil, bir isim kullanmamız gerekiyor ki diğer yerlerde bu isim ile çağırabilelim. Diğer yandan @OA\Get içerisindeki kullanımda ise HTTP kodunu vermemiz gerekiyor.

Gördüğünüz bir diğer değişik kısım ise JsonContent içerisindeki Property tanımlamasıdır diye düşünüyorum. Burada da yine diğerlerinde yaptığımız gibi Success’den katılım aldıktan sonra data kısmını endpointimize göre değiştiriyoruz. Bu sefer type olarak array verdiğimizi görüyorsunuz. Çünkü index method’u dizi şeklinde kullanıcıya bağlı tüm adresleri dönüyor. type=”array” ise @OA\Items tanımlamamız gerekiyor burada da yine referans vererek biraz önce tanımladığımız Address resource’una ait schema’yı referans gösteriyoruz.

Şimdi de show methodumuza anotasyonlarımızı ekleyelim.

Bu seferki kod bloğumuz biraz daha büyük ama gözünüzü korkutmasın. Birkaç tane farklı kısım var. Onları da hemen açıklayayım. İlk olarak göze batan kısım AddressController sınıfının üzerine eklediğimiz kısımdır diye düşünüyorum, buraya genelde controller içerisinde birden fazla endpoint içerisinde kullandığımız anotasyonları yazıyoruz -ki burdan alıp referans verebilelim. İlki ile başlayalım hemen, kolayca anlayacağınız üzere bu aslında endpoint’in path kısmında aldığımız ID parametresiyle ilgili. Bir diğeri de ilgili Address ID’sinin veritabanında bulunamadığı durumlarda döndüğümüz hata. Burada ek olarak söylemek istediğim 400 HTTP kodlu response tanımlamasının içerisinde gördüğünüz examples kısmı; aynı hata kodu ile birden fazla hata dönüyorsanız hepsini burada @OA\Examples şeklinde devam ederek listeleyebilirsiniz. Son olarak ise 200 HTTP kodlu response tanımlasının içerisindeki JsonContent’i incelemenizi isteyeceğim. Artık burada tek bir adrese ait bilgileri döndüğümüz için bu sefer type kısmımız “object” ve referans olarak da yine aynı referansı kullanabiliyoruz.

AddressController içerisindeki diğer endpointler de bu ikisiyle benzer olacağı için onları yazıya dahil etmedim ama endişelenmeyin bu yazının başından sonunda yazdığımız tüm anotasyonları ve kodları bu GitHub deposunda bulabilirsiniz: https://github.com/erenhatirnaz/laravel-openapi-example

OpenAPI dokümantasyonumuzun SwaggerUI üzerinde görünümü

OpenAPI/Swagger ile ilgili her şeyi bir yazı altında toplamamız imkansız elbette fakat elimden geldiğince gerçek hayat örnekleri üzerinden OpenAPI entegrasyonunu göstermeye çalıştım. Yazıya sığmadığı için gösteremediğim birçok konu var elbette fakat bunları da sizin araştırmacı tarafınıza bırakıyorum. Tabii ki sorularınız olursa yanıtlamaktan mutluluk duyarım orası ayrı :)

Bir sonraki yazımızda görüşmek üzere,

--

--

Eren Hatırnaz
Mobillium

Technical Product Owner @Mobillium (ex Back-End Developer)