NodeJS ile GraphQL-Server Kurulumu

GraphQL, Facebook’un ortaya çıkardığı API lar için bir sorgu dilidir. Aslında REST yaklaşımının geliştirilmiş hali olduğu söylenebilir. Bu yazıda, ne olduğundan daha çok nasıl çalıştığı üzerinde durmak istiyorum. Fakat REST ile kıyaslandığında avantajları adına şunları söyleyebiliriz:

1 - REST sorgularında endpoint lere ihtiyaç vardır. Bu ve endpoint ler uzadıkça okunurluğu azalabilmektedir. Örneğin;

https://domain.com/api/customers/customerId/products/productId/orders/orderId/

GraphQL’de bu tarz queryler e ihtiyaç kalmamaktadır.

2 - REST sorgularında arzu edilen bilgiyle birlikte, gerekli olmayan bir sürü bilgi de ister istemez döndürülmektedir. Bu da serverların iş yükünü artırmaktadır. GraphQL’de lüzumlu olmayan bilgilerin gelmesi engellenebilmektedir.

3 - Nested Query yapmak REST’de birbirini takip eden query ler yapmak demekti. GraphQL’de tek bir query ile nested query yapılabilmektedir. GraphQL bizim için bu query leri parçalara bölüp herbirini Promise olarak elde eder ve elde ettiği sonuçları birleştirip bize döndürür. Bu da bizi zaman kaybı ve kod karmaşasından önemli ölçüde kurtarır.

4 - GraphQL, API larımızı kolayca test etmemizi sağlayan Playground arayüzünü sunmaktadır.

5 - Fonksiyonların döneceği field lar GraphQL’in şema kısmında (TypeDefs) zaten tanımlı olacağı için Backend Developer ın field dönmeyi unutması gibi bir durum söz konusu değildir. TypeDefs da tanımlı bütün field lar için sorgu yapıp yapmamak API kullanıcısına kalmıştır.

Şimdi GraphQL’in yapısına bir göz atalım:

GraphQL temel iki kısımdan oluşur:

i - TypeDefs

ii - reslovers

TypeDefs API ın şemasını tanımladığımız kısımdır. Bu kısımda, kullanılacak fonksiyonların ve değişkenlerin tipleri belirlenir.

resolvers ise fonksiyonların tanımlandığı kısımdır. CRUD fonksiyonları GraphQL’de resolver altında iki kısımda ele alınır; “Read” fonksiyonları “Query” altında diğerleri ise “Mutation altında tanımlanır.

Şimdi basit bir örnek ile bu özellikleri pratikte görelim.

GraphQL bir sorgu dili olduğundan kullanabilmemiz için bir tool’a ihtiyacımız var. NodeJS ile bir server oluşturduğumuz için ben graphql-yoga’yı tercih ediyorum. Fakat her dile uygun birden fazla tool imkanı https://graphql.org/code/ linkinde mevcuttur.

Öncelikle kurulumu yapacağım klasörde package.json dosyası oluşturuyorum:

npm init -y

daha sonra bir JavaScript compiler olan babel i yüklüyorum:

npm i babel-cli babel-preset-es2015

babel, temel olarak syntax kolaylığı sağlar. ES 2015 ve sonrasındaki sürümlerle yazılan kodları browserlarda JavaScript ile uyumlu hale convert eder.

Şimdi graphql-yoga tool’unu yükleyelim.

npm i graphql-yoga -save

Ana dizine server kodlarımızı yazacağımız index.js dosyasını oluşturalım ve içine aşağıdaki kodları yazalım:

import { GraphQLServer } from “graphql-yoga”;const typeDefs = `
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => “Selam.”,
},
};
const server = new GraphQLServer({ typeDefs, resolvers });server.start(() => console.log(“Server is running on localhost:4000”));

index.js dosyasını nodemon ile çalıştırmayı tercih ediyorum. nodemon geliştirme aşamasında projenizi her defasında durdurup tekrar başlatmaktan kurtaran bir nodejs paketidir.

npm i nodemon

package.json dosyasında scripts altına şu kodu ekleyelim.

"start": "nodemon index.js -exec babel-node -presets es2015",

Şu an en basit hali ile server’ımız çalışır durumdadır.

Kısaca bahsetmek gerekirse:

  • ilk satır da ilgili tool/paketi import ettik.
  • TypeDefs ile tipleri belirledik.
  • resolvers’da fonksiyonu tanımladık.
  • Son olarak da yüklediğimiz graphql-yoga paketinden server instance ımızı üretip çalıştırdık.

GraphQL default olarak 4000 portunda çalışır. Farklı portlarda çalıştırmak için şu kodu kullanabilirsiniz.

server.start({ port: 4001 }).then(console.log(“server runs on port localhost:4001”));

Şimdi projemizi çalıştıralım.

npm start

Browser’da yukarıda bahsettiğimiz Playground ekranı gelecektir.

Sol kısımda tanımlı fonksiyonlarımızı çağırıp sağ tarafta sonuçları görebiliriz.

Şimdi biraz daha farklı query ler deneyelim. Bunun için iki Array’den oluşan fake data oluşturalım. Fake dataları TypeDefs in hemen üzerine ekleyebiliriz.

const Users = [
{
id: 1,
username: “john”,
city: “Melbourne”,
},
{
id: 2,
username: “mseven”,
city: “Istanbul”,
},
{
id: 3,
username: “maria”,
city: “Zagreb”,
},
];
const Posts = [
{
id: 1,
title:“Lorem Ipsum is simply dummy text of the printing and typesetting industry.”,
userId: 1,
},
{
id: 2,
title:"Lorem Ipsum je jednostavno probni tekst koji se koristi u tiskarskoj i slovoslagarskoj industriji.”,
userId: 3,
},
{
id: 3,
title:“Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir.”,
userId: 2,
},
{
id: 4,
title:"Lorem Ipsum, dizgi ve baskı endüstrisinde kullanılan mıgır metinlerdir22222.”,
userId: 1,
},
];

Datalar kısaca; Users dizisindeki kullanıcılar ve bu kullanıcıların yaptığı Posts dizisini oluşturan postlarıdır.

Önce TypeDefs altında bu data’ların şemalarını oluşturalım.

const typeDefs = `
type Query {
hello: String
}
type User{
id: ID!
username: String!
city: String
}
type Post{
id: ID!
title: String!
userId: ID!
}`;

Burada GraphQL’e ait iki özellikten bahsetmek istiyorum. “ID” tipi GraphQL’e aittir ve id lere verilen bir type dır. Bu şekilde tanımlanan id parametreleri, fonksiyona String veya Int olarak verilebilir. Diğer bir özellik ise ünlem işaretidir. Ünlem işareti olan değişkenler unnullable (boş bırakılamayacak) değişkenlerdir.

Şimdi de fonksiyon tiplerini belirleyelim:

type Query {
hello: String
users: [User!]!
posts: [Post!]!
}

Bu fonksiyonlar tüm kullanıcıları ve post ları listeleyeceği için type’larını “[ ]” içinde yazdık. Parantezin içindeki ünlem işareti User tipinde dönen sonucun null olamayacağını, parantezin dışındaki ünlem ise dönen sonucun null olamayacağını söyler.

Şimdi şemasını oluşturduğumuz fonksiyonları resolvers altında tanımlayalım. Bu fonksiyonlar birer “Read” fonksiyonu olduğu için Query olarak tanımlayacağız.

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
},
};

parent ve arg a yazının ilerleyen kısımlarında değineceğim.

Projeyi çalıştırdığımızda ve Playground’da sol taraftaki ilgili sorguyu yaptığımızda, sağda gözüken çıktıyı alırız:

Query olarak tanımladığımız fonksiyonları Playground’da yine query ile çağırırız. Ve dönecek field ları da kendimiz belirleyebilriz. Örneğin city field ını çıkardığınızda fonksiyon yine çalışacaktır. Fakat en az bir field olmalı.

Şimdi de id ye göre user çağırmayı deneyelim.

Önce yine şemada fonksiyon tipini ve parametreleri belirliyoruz:

type Query {
hello: String
users: [User!]!
posts: [Post!]!
user(id: ID!): User!
}

Daha sonra resolvers da fonksiyonu tanımlıyoruz:

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
user: (parent, args) => Users.find((user) => String(user.id) === args.id),
},
};

Burda args dan bahsetmek istiyorum. Args aslında tanımlı fonksiyounun parametrelerini tutar. Yukarıdaki user fonksiyonunda görüldüğü gibi girilen id, args.id olarak temsil edilir.

Şimdi projeyi çalıştırdığımızda ve sol tarafta gözüken sorguyu yaptığımızda, sağdaki sonucu alırız:

İd’si 1 olan user getirilmiş oldu. Burada id parametresi ID tipinde olduğu için Int veya String olarak girilebilir.

Nested Query

Şimdi Nested Query örneği yapalım.

Örneğin bir yazarın birden fazla post u olduğu bir senaryomuz olsun. Zaten fake datalarımızda id’si 1 olan yazarın iki post u bulunmakta. Fakat bir post un birden fazla yazarı olamayacağı için her post a bir user karşılık gelecek.

Şimdi bir yazarın post larını ve bir post un yazarını veren sorguları yazalım. Yine işe önce şema ile başlıyoruz. TypeDefs de Post a user field ı ve User a posts filed ı ekliyoruz:

type User{
id: ID!
username: String!
city: String
posts: [Post!]
}
type Post{
id: ID!
title: String!
userId: ID!
user: User!
}

Fakat datalara baktığımızda Users Array’inde posts field’ı olmadığı gibi Posts Array’inde de user filed’ı yok. Bunu resolvers’da user’ın id’si ve post’un userId’ sini eşleştiren fonksiyonları tanımlayarak çözeceğiz. Fakat bu field’lar hazır olarak mevcut olmadığı için bunları Query altında tanımlayamayız.

Post type’ına user eklemek için Post altında ayrı bir resolver tanımlıyoruz. Aynı şekile, User için de bir posts field’ı tanımlayabilmek için User altında ayrı bir resolver tanımlıyoruz:

const resolvers = {
Query: {
hello: () => “Selam.”,
users: (parent, args) => Users,
posts: (parent, args) => Posts,
user: (parent, args) => Users.find((user) => String(user.id) === args.id),
},
Post: {
user: (parent, args) => Users.find((user) => user.id === parent.userId),
},
User: {
posts: (parent, args) => Posts.filter((post) => post.userId === parent.id),
},
};

İşte burada parent parametresi devreye giriyor. Adından da anlaşılacağı üzere Nested Query de içte bulunan query’de kullanmak üzere bir üstteki query’nin parametrelerine ulaşır.

Örneğin ikinci fonksiyonu okuyalım. id’si ile sorgulanan user’ın post’larını bulmak için, Posts Array’i içinde dolan ve userId’si, sorgulanan user’ın id’sine eşit olanları getir.

Şimdi de mutation sorgusu yapalım. Users Array’ine bir User daha ekleyen bir fonksiyon yazalım. TypeDefs e alttaki kodu ekliyorum:

type Mutation{
addUser(id:ID!, username:String!, city:String! ): User
}

Şemasını belirlediğimiz bu fonksiyonun resolver’ını tanımlayalım. resolvers’a aşağıdaki kodu ekleyelim:

Mutation: {
addUser: (_, { id, username, city }) => {
const newUser = {
id: id,
username: username,
city: city,
};
Users.push(newUser);
return newUser;
},
},

Burada fonksiyonun ilk parametresinin alt çizgi olduğunu görüyoruz. parent parametresini burada kullanmadığımız için bu pratik syntax’i tercih edebiliriz. İkinci parametre ise aslında üç parametreden oluşan ve fonksiyona değer olarak girdiğimiz parametrelerdir (süslü parantez içinde).

Fonksiyonu çalıştırıp soldaki sorguyu çalıştırdığımızda şu sonuçları elde ederiz:

Özetleyecek olursak NodeJS de graphql-yoga kütüphanesi ile GraphQL server elde ettik. GraphQL’i teşkil eden iki ana unsur olan TypeDefs ve resolvers kavramlarını açıklayarak örnekler yaptık.

Projenin tamamına şu linkten ulaşabilirsiniz:

https://github.com/azizkale/Web-Server-in-NodeJS-with-GraphQL

Daha fazla bilgi için GraphQL resmi sitesi ziyaret edilebilir:

Web Developer

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store