GraphQL Temel Kavramlar

Abdulsamet İLERİ
5 min readSep 12, 2020

Örnekler ile GraphQL temel kavramları görelim ve uygulamaya çalışalım…

Bir GraphQL query’si ile başlayalım. Mevcut kullanıcının ismini getiriyor. Burada “me” ve “name” fields olarak isimlendiriliyor.

query {  me {    name   }}

Client bu sorguyu GraphQL servere gönderdikten sonra şu tarz bir sonuç geliyor.

{  "data": {    "me": {      "name": "Abdulsamet"    }  }}

Dikkat edersek request ile response formları birbirine çok benziyor.

NOT: Başarılı bir sonuç dönen GraphQL sorgusu içinde, daima data keyini bulundurur.

Tabiki, birden fazla alan da isteyebiliriz…

query { 
me {
name
friends(first: 2) {
name
age
}
}
}

Burada mevcut kullancının ismini getirmekle beraber, arkadaş olduğu iki ayrı kullanıcının isim ve yaş bilgisini getir sorgusunu yazmış olduk.

Burda dikkatimizi çeken complex sorgu yazabilmekle beraber, field’ların parametre de alabileceği. O zaman field’ları bir nevi fonksiyon olarak düşünmekte fayda var. Dönen sonucumuz şu şekilde..

{
"data":{
"me":{
"name":"Abdulsamet",
"friends":[
{
"name":"Onur",
"age":24
},
{
"name":"Ahmet",
"age":24
}
]
}
}
}

Peki client hangi dataları isteyebileceğini, server hangi alanları işleyebileceğini nasıl biliyor?

Burada GraphQL’in Type System’i devreye giriyor.

Type System

Örnek üzerinden neye benzediğini görelim..

type Shop {
name: String!
location: Location
products: [Product!]!
}
type Location {
address: String
}
type Product {
name: String!
price: Price!
}

alanAdı: alanTipi şeklinde bir yazım oldugunu hemen fark edebiliyoruz.

  1. Shop: Name, String’miş. Bildiğimiz primitive veya başka bir deyişle scalar type. Sonuna ! alması biraz garip gelse de, alanın gerekli bir alan oldugunu belirtiyor. İlerideki makalemde biraz daha detaylı anlatmaya çalışacağım.
  2. Location: Location görünce, Location’ın hemen user-defined tip oldugunu anlıyoruz. Hemen sıcağı sıcağına location için nasıl query yazabiliriz bir bakalım.
query {
shop(id: 1) {
location {
address
}
}
}

address alması çok da şaşırtıcı değil. Objeler içinde tanımlanan propertyler ile sorgular yazabilmek mümkün. Tıpkı address bilgisini location objesinden istediğimiz gibi.

Bir shop objesinin location’u oldugunu ve location objesininde address adında string bir alan aldığını yukarıda görmüştük.

Peki shop nereden geliyor?

Schema roots’dan…

Schema Roots

Bir query yazabilmek için bir giriş noktası vermek gerekiyor. Yani bir root (query root) tanımlanmalı.. Aşağıdaki şekilde olduğu gibi çalıştırabileceğimiz queryleri type Query içerisinde teker teker tanımlıyoruz.

type Query {
shop(id: ID!): Shop!
}

Mesela yukarıda, adı shop ve parametre olarak id alan sonuç olarak Shop objesi dönen bir query tanımlaması yapmış olduk.

O zaman bana id’si 1 olan shop objesinin ismini ver sorgusunu yazalım…

query {
shop(id: 1) {
name
}
}

Gayet basit. Burada aynı zamanda sorgudaki değişkene statik bir değer verip (id: 1) çalıştırabileceğimizi görmüş olduk.

Arguments

Argümanlara yakından bakalım.
argümanAdı: argümanTipi şeklinde yazıldığını görüyoruz.

type Query {
shop(id: ID!): Shop!
}

İstersek birden fazla argümanı şu şekilde verebiliriz.

type Query {
shop(owner: String!, name: String!, location: Location): Shop!
}

Böyle açıkça yazabildiğimiz gibi daha toplu, okunabilir bir yazım sağlayan input types şeklinde de yazabiliriz. Şöyle ki;

type Product {
price(format: PriceFormat): Int!
}
input PriceFormat {
displayCents: Boolean!
currency: String!
}

Variables

Değişkenler, dinamik query yazmak için olmazsa olmaz malum. Değişkenleri de şu şekilde tanımlıyoruz.

query FetchProduct($id: ID!, $format: PriceFormat!) {
product(id: $id) {
price(format: $format) {
name
}
}
}

Başında $ koyarak, bunun bir değişken olduğunu ve client’in bunu dinamik olarak vericeğini söylemiş olduk.

NOT: Query’ye FetchProduct adını verdiğimize dikkat edelim. Buna operation name deniliyor.

NOT: Yukarıda input olarak tanımladığımız PriceFormat Tipinin okunabilirliği nasıl arttırdığına dikkat edelim.

Peki client bu argümanları nasıl vericek?

{
"id": "abc",
"format": {
"displayCents": true,
"currency": "USD"
}
}

Aliases

Örnek ile ne olduğunu görelim.

query {
product(id: "abc") {
name
price
}
}

Bu query’i çalıştırırsak bize aşağıdaki şekilde sonuç döner.

{
"data": {
"product": {
"name": "T-Shirt",
"price": 10,
}
}
}

Burada product tagını beğenmeyip başka bir isim vermek istersek queryi şu şekilde yazmalıyız.

query {
abcProduct: product(id: "abc") {
name
price
}
}
{
"data": {
"abcProduct": {
"name": "T-Shirt",
"price": 10,
}
}
}

daha önce döndüğü “product” tagını “abcProduct” olarak döndürdü. Standartlar üzerinde bu şekilde oynama yapabiliyoruz.

Nerede mi kullanırım?

Aynı alanı farklı parametrelerle çağırmak istediğinde her çağrıma alias ile farklı isimler verebilirsin mesela.

Mutation

Şu ana kadar Query ile tek yaptığımız şey, var olan bilgiyi getirmek oldu.. Yani bir nevi GET isteği. Peki ya veri yazmak ya da güncellemek nasıl oluyor onu görelim. GraphQL’de bu amaçla geliştirilen konsepti var. Hemen bir örnek görelim.

Nasıl tanımlarız?

type Mutation {
addProduct(name: String!, price: Price!): AddProductPayload
}
type AddProductPayload {
product: Product!
}

Nasıl kullanırız?

mutation {
addProduct(name: String!, price: Price!) {
product {
id
}
}
}

Tabi bu sorguyu çalıştırmak için name ve price’i belirtmek lazım. Sorgu sonucu olarak create edilen objenin id değeri dönecek.

NOT: Yazımı query ile benzemesine dikkat edelim..

Enums

type Shop {
type: ShopType!
}
enum ShopType {
APPAREL
FOOD
ELECTRONICS
}

Abstract Types

Abstract type çoğu dilde mevcut. GraphQL’de de bunu farklı yapan bir şey yok. Örnek ile görelim

Interface Kullanımı

interface Discountable {
priceWithDiscounts: Price!
priceWithoutDiscounts: Price!
}

Bunu implement ediyoruz.

type Product implements Discountable {
name: String!
priceWithDiscounts: Price!
priceWithoutDiscounts: Price!
}

Discountable interface’sini implement etmek demek içindeki propertyleri tanımlamak demek.

type GiftCard implements Discountable {
code: String!
priceWithDiscounts: Price!
priceWithoutDiscounts: Price!
}

Yine başka bir type’da implement ettik. Şimdi kullanımı görelim.

type Cart {
discountedItems: [Discountable!]!
}

interface sayesinde discountItems alanı somut bir tip olmaktan çıktı. Ya ‘Product’ ya da ‘GiftCard’ tipinde olabilir.

Product da olsa GiftCard da olsa interface yüzünden bu iki tipi genel olarak şu şekilde sorgulanabilir.

query { 
cart {
discountedItems {
priceWithDiscounts
priceWithoutDiscounts
}
}
}

Tipe özel ekstra bir şey istiyorsak, onu şu şekilde söylemeliyiz…

query { 
cart {
discountedItems {
priceWithDiscounts
priceWithoutDiscounts
... on Product {
name
}
... on GiftCard {
code
}

}
}
}

NOT: “… on” yapısına => Fragment Spreads veya Typed Fragments deniliyor..

Union Ile

union CartItem = Product | GiftCardtype Cart {
items: [CartItem]
}

Union type biraz daha farklı. Herhangi bir contract belirtilmediğinden aşağıdaki gibi net olarak söylemek gerekiyor.

query { 
cart {
discountedItems {
... on Product {
name
}
... on GiftCard {
code
}

}
}
}

Fragments

Fragmentler sayesinde aynı parçalara sahip queryleri defalarca yazmıyoruz. Bir kere yazıp istenilen yerde kullanabiliyoruz. Şöyle ki;

query {
products(first: 100) {
...ProductFragment
}
}
fragment ProductFragment on Product {
name
price
variants
}

ProductFragment => Fragment’in adı oluyor.
Product => Product’a kullanılabileceğini söylüyor.

Directives

2 Tane built-in directivemiz var. @skip and @include.. Tabi istersek kendimize özel directive de yazabiliriz.

query MyQuery($shouldInclude: Boolean) {
myField @include(if: $shouldInclude)
}

Yukarıdaki örnekte, shouldInclude değişkeni true olursa, myField ile sorgulama yapılabilir diyebiliyoruz.

NOT: Directives’lerde tıpkı fieldslar gibi parametre kabul edebiliyor

Introspection

Bu yapı sayesinde hangi querylerimizi hangi formatlarda yazabileceğimizi görmüş oluyoruz.

Çalıştırdığımız query’imiz:

query { 
__schema {
types {
name
}
}
}

Dönen sonuç:

{
"data":{
"__schema":{
"types":[
{
"name":"Query"
},
{
"name":"Product"
}
]
}
}
}

Bu yapı sayesinde UI toolları vs. yazılabilmiş. GraphQL playground de bunlardan biri.

Mesela Github API’yi deneyebilirsiniz:

Bununla da kalınmıyor Client’ta code generation’a da yarıyor. Böylelikle sorgular tek tek yazmakla vs. uğraşılmamış oluyor.

Bir sonraki GraphQL yazımda görüşmek üzere…

--

--