Takip et

Node.js’te Apollo Server ve Sequelize ile GraphQL Sunucusu Kurulumu

Node.js’te Apollo Server ve Sequelize ile GraphQL Sunucusu Kurulumu Giriş Modern web uygulamaları, verilerle etkileşim kurmak için güçlü ve e

Node.js’te Apollo Server ve Sequelize ile GraphQL Sunucusu Kurulumu

Giriş

Modern web uygulamaları, verilerle etkileşim kurmak için güçlü ve esnek API’lere ihtiyaç duyar. Geleneksel REST API’leri belirli senaryolarda etkili olsa da, veri ihtiyacının karmaşıklaştığı, istemcilerin farklı veri setleri talep ettiği veya aşırı/eksik veri sorunlarının yaşandığı durumlarda GraphQL, daha verimli bir alternatif sunar. Bu makale, Node.js ortamında GraphQL sunucusu kurmak için endüstri standardı Apollo Server’ı ve ilişkisel veritabanı yönetimini kolaylaştıran güçlü ORM (Object-Relational Mapper) Sequelize’ı nasıl entegre edeceğinizi adım adım açıklayacaktır.

GraphQL Nedir?

GraphQL, Facebook tarafından geliştirilen ve 2015 yılında açık kaynak olarak yayınlanan, API’ler için bir sorgu dilidir. Temel amacı, istemcilerin ihtiyaç duydukları veriyi tam olarak talep etmelerini sağlayarak, sunucudan gereksiz veri çekme veya birden fazla istek yapma sorunlarını ortadan kaldırmaktır. GraphQL’in başlıca avantajları şunlardır:

  • Verimlilik: İstemciler tam olarak neye ihtiyaç duyduklarını belirtir, bu da ağ trafiğini azaltır ve performansı artırır.
  • Tek Uç Nokta: Tüm sorgular tek bir HTTP POST uç noktasına yapılır.
  • Güçlü Tip Sistemi: Şema tabanlı yapısı sayesinde API’nin sunduğu tüm veri tipleri ve ilişkileri önceden tanımlanır, bu da geliştirme sürecini kolaylaştırır ve hata olasılığını azaltır.
  • Geliştirici Deneyimi: Şema keşfedilebilirliği ve otomatik tamamlama özellikleri sayesinde geliştiriciler için daha iyi bir deneyim sunar.

Apollo Server Nedir?

Apollo Server, GraphQL API’leri oluşturmak için kullanılan açık kaynaklı, üretim ortamına hazır bir GraphQL sunucusudur. Node.js üzerinde çalışır ve GraphQL şemanızı ve çözümleyicilerinizi (resolvers) kolayca entegre etmenizi sağlar. Express, Koa gibi çeşitli Node.js HTTP framework’leri ile uyumludur ve geliştirme sürecini hızlandıran güçlü araçlar (Apollo Sandbox/GraphQL Playground) sunar.

Sequelize Nedir?

Sequelize, Node.js için Promise tabanlı bir ORM’dir. PostgreSQL, MySQL, MariaDB, SQLite ve MSSQL gibi çeşitli ilişkisel veritabanlarını destekler. Sequelize, JavaScript nesnelerini veritabanı tablolarına eşleyerek SQL sorguları yazma ihtiyacını büyük ölçüde azaltır. Modeller, ilişkiler, migrasyonlar ve seed’ler gibi özellikleriyle veritabanı yönetimini basitleştirir ve daha tutarlı hale getirir.

Bu makalede, bir Node.js projesinde Apollo Server’ı kurarak GraphQL şemamızı tanımlayacak, Sequelize ile veritabanı modellerimizi oluşturacak ve bu iki teknolojiyi birleştirerek güçlü bir GraphQL API’si geliştireceğiz.

Ön Gereksinimler

Bu rehberi takip edebilmek için aşağıdaki ön gereksinimlere sahip olmanız gerekmektedir:

  • Node.js ve npm/yarn: Bilgisayarınızda Node.js ve npm (Node Package Manager) veya yarn kurulu olmalıdır. Node.js’in resmi web sitesinden indirebilirsiniz.
  • Temel JavaScript/TypeScript Bilgisi: ES6+ JavaScript sözdizimine aşina olmanız beklenir.
  • Veritabanı Bilgisi: İlişkisel veritabanları (PostgreSQL, MySQL, SQLite vb.) hakkında temel bilgiye sahip olmak faydalı olacaktır. Bu rehberde SQLite’ı kullanacağız, ancak diğer veritabanlarına geçiş yapmak kolaydır.

Proje Yapılandırması ve Temel Kurulum

İlk adım olarak, yeni bir Node.js projesi oluşturup gerekli bağımlılıkları yükleyeceğiz.

Proje Başlatma

Yeni bir dizin oluşturun ve içine girin:

mkdir graphql-sequelize-app
cd graphql-sequelize-app

Projenizi başlatın:

npm init -y

Bu komut, varsayılan ayarlar ile bir package.json dosyası oluşturacaktır.

Gerekli Paketlerin Yüklenmesi

Şimdi projemiz için gerekli olan temel paketleri yükleyelim:

npm install apollo-server graphql sequelize sqlite3 dotenv nodemon
  • apollo-server: GraphQL sunucusunu kurmak için ana paket.
  • graphql: GraphQL şemaları ve sorguları için temel kütüphane.
  • sequelize: ORM kütüphanesi.
  • sqlite3: SQLite veritabanı sürücüsü. (Eğer PostgreSQL kullanacaksanız pg, MySQL için mysql2 kurmalısınız).
  • dotenv: Ortam değişkenlerini .env dosyasından yüklemek için.
  • nodemon: Geliştirme sırasında dosya değişikliklerini algılayıp sunucuyu otomatik olarak yeniden başlatmak için.

package.json dosyanıza start ve dev script’lerini ekleyelim:

// package.json
{
  "name": "graphql-sequelize-app",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "nodemon src/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "apollo-server": "^3.13.0",
    "dotenv": "^16.4.5",
    "graphql": "^16.8.1",
    "sequelize": "^6.37.3",
    "sqlite3": "^5.1.7"
  },
  "devDependencies": {
    "nodemon": "^3.1.0"
  }
}

Proje Klasör Yapısı

Organize bir proje için aşağıdaki gibi bir klasör yapısı önerilir:

graphql-sequelize-app/
├── src/
│   ├── index.js          // Ana sunucu dosyası
│   ├── graphql/
│   │   ├── schema.js     // GraphQL şema tanımları
│   │   └── resolvers.js  // GraphQL çözümleyicileri
│   └── models/           // Sequelize modelleri
│       ├── index.js      // Sequelize bağlantısı ve model ilişkileri
│       ├── user.js
│       └── post.js
├── config/
│   └── config.json       // Sequelize CLI yapılandırması
├── .env                  // Ortam değişkenleri
├── package.json
└── ...

Bu yapı, GraphQL şemalarını, çözümleyicileri ve Sequelize modellerini ayrı tutarak projenin ölçeklenebilirliğini ve bakımını kolaylaştırır.

Veritabanı Yapılandırması ve Sequelize Entegrasyonu

Şimdi Sequelize’ı projemize entegre edelim ve veritabanı bağlantısını kuralım.

Ortam Değişkenleri (.env)

Veritabanı bağlantı bilgilerini güvenli bir şekilde yönetmek için .env dosyasını kullanacağız. Projenizin kök dizininde bir .env dosyası oluşturun:

# .env
DATABASE_URL=sqlite::memory: # Geliştirme için geçici bellek veritabanı veya

DATABASE_URL=sqlite:./database.sqlite # Kalıcı dosya tabanlı veritabanı

sqlite::memory: geliştirme sırasında hızlı testler için idealdir, ancak sunucu her yeniden başlatıldığında veriler kaybolur. Kalıcı bir veritabanı için sqlite:./database.sqlite kullanabilirsiniz.

Sequelize CLI Kurulumu ve Yapılandırması

Sequelize CLI, model, migrasyon ve seed dosyalarını oluşturmak için kullanışlı bir araçtır. Geliştirme bağımlılığı olarak yükleyin:

npm install --save-dev sequelize-cli

Şimdi Sequelize CLI’ı başlatın:

npx sequelize-cli init

Bu komut, config, models, migrations ve seeders dizinlerini oluşturacaktır. config/config.json dosyasını düzenleyerek veritabanı bağlantı bilgilerimizi ortam değişkenlerinden alacak şekilde ayarlayalım:

// config/config.json
{
  "development": {
    "dialect": "sqlite",
    "storage": "./database.sqlite",
    "logging": false
  },
  "test": {
    "dialect": "sqlite",
    "storage": ":memory:"
  },
  "production": {
    "dialect": "sqlite",
    "storage": "./database.sqlite"
  }
}

Yukarıdaki yapılandırma, sqlite kullanıldığında database.sqlite dosyasını veritabanı olarak kullanır. Eğer PostgreSQL veya MySQL kullanacaksanız, dialect ve bağlantı bilgilerini buna göre güncellemeli ve use_env_variable özelliğini kullanmalısınız. Örneğin:

// config/config.json (PostgreSQL/MySQL için örnek)
{
  "development": {
    "use_env_variable": "DATABASE_URL",
    "dialect": "postgres", // veya "mysql"
    "logging": false
  },
  // ... diğer ortamlar
}

Sequelize Bağlantısı ve Model Tanımlama

src/models/index.js dosyası, tüm Sequelize modellerinizi bir araya getirecek ve veritabanı bağlantısını yönetecek ana dosyadır.

// src/models/index.js
require('dotenv').config();
const { Sequelize, DataTypes } = require('sequelize');
const UserModel = require('./user');
const PostModel = require('./post');

const env = process.env.NODE_ENV || 'development';
const config = require('../../config/config.json')[env];

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(config.database, config.username, config.password, config);
}

const db = {};

db.sequelize = sequelize;
db.Sequelize = Sequelize;

// Modelleri yükle
db.User = UserModel(sequelize, DataTypes);
db.Post = PostModel(sequelize, DataTypes);

// İlişkileri tanımla
Object.keys(db).forEach(modelName => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

module.exports = db;

Model Oluşturma

İki basit model oluşturalım: User (Kullanıcı) ve Post (Gönderi). Bir kullanıcı birden fazla gönderi yazabilir.

User Modeli

src/models/user.js dosyasını oluşturun:

// src/models/user.js
module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define('User', {
    username: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false
    },
    email: {
      type: DataTypes.STRING,
      unique: true,
      allowNull: false,
      validate: {
        isEmail: true
      }
    }
  });

  User.associate = (models) => {
    User.hasMany(models.Post, {
      foreignKey: 'userId',
      as: 'posts'
    });
  };

  return User;
};

Post Modeli

src/models/post.js dosyasını oluşturun:

// src/models/post.js
module.exports = (sequelize, DataTypes) => {
  const Post = sequelize.define('Post', {
    title: {
      type: DataTypes.STRING,
      allowNull: false
    },
    content: {
      type: DataTypes.TEXT,
      allowNull: false
    }
  });

  Post.associate = (models) => {
    Post.belongsTo(models.User, {
      foreignKey: 'userId',
      as: 'author'
    });
  };

  return Post;
};

Veritabanı Migrasyonları

Sequelize CLI ile modellerimiz için migrasyon dosyaları oluşturalım:

npx sequelize-cli model:generate --name User --attributes username:string,email:string
npx sequelize-cli model:generate --name Post --attributes title:string,content:text,userId:integer

Bu komutlar, migrations klasöründe iki yeni dosya oluşturacaktır. userId alanının Post modelinde yabancı anahtar olarak tanımlandığına dikkat edin. Migrasyon dosyalarını inceleyip gerekirse düzenleyebilirsiniz (örneğin unique veya allowNull kısıtlamaları eklemek için).

Şimdi migrasyonları çalıştırarak veritabanı tablolarını oluşturalım:

npx sequelize-cli db:migrate

Bu komut, database.sqlite (veya yapılandırdığınız başka bir veritabanı) içinde Users ve Posts tablolarını oluşturacaktır.

GraphQL Şema Tanımlaması

GraphQL şemanız, istemcilerin sorgulayabileceği veya değiştirebileceği verilerin yapısını tanımlar. src/graphql/schema.js dosyasını oluşturalım:

// src/graphql/schema.js
const { gql } = require('apollo-server');

const typeDefs = gql
  type User {
    id: ID!
    username: String!
    email: String!
    posts: [Post!]!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }

  input CreateUserInput {
    username: String!
    email: String!
  }

  input UpdateUserInput {
    username: String
    email: String
  }

  input CreatePostInput {
    title: String!
    content: String!
    userId: ID!
  }

  input UpdatePostInput {
    title: String
    content: String
  }

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

  type Mutation {
    createUser(input: CreateUserInput!): User!
    updateUser(id: ID!, input: UpdateUserInput!): User!
    deleteUser(id: ID!): Boolean!

    createPost(input: CreatePostInput!): Post!
    updatePost(id: ID!, input: UpdatePostInput!): Post!
    deletePost(id: ID!): Boolean!
  }
;

module.exports = typeDefs;

Şema Açıklaması

  • User ve Post tipleri, veritabanındaki karşılık gelen modellerin alanlarını tanımlar. ID! gibi ünlem işaretleri, alanın zorunlu olduğunu belirtir.
  • posts alanı User tipinde, author alanı Post tipinde, modeller arasındaki ilişkileri yansıtır.
  • input tipleri, Mutation işlemleri için girdi verilerini tanımlar. Bu, daha temiz ve yeniden kullanılabilir bir yapı sağlar.
  • Query tipi, istemcilerin veri okuma (GET) işlemleri için kullanabileceği sorguları tanımlar. Örneğin, tüm kullanıcıları (users) veya belirli bir kullanıcıyı (user(id:)) getirme.
  • Mutation tipi, istemcilerin veri oluşturma, güncelleme ve silme (POST, PUT, DELETE) işlemleri için kullanabileceği mutasyonları tanımlar.

GraphQL Çözümleyicileri (Resolvers) Oluşturma

Çözümleyiciler, GraphQL şemasındaki her alanın nasıl veri sağlayacağını belirleyen fonksiyonlardır. src/graphql/resolvers.js dosyasını oluşturalım:

// src/graphql/resolvers.js
const resolvers = {
  Query: {
    users: async (parent, args, { db }) => {
      return db.User.findAll({ include: [{ model: db.Post, as: 'posts' }] });
    },
    user: async (parent, { id }, { db }) => {
      return db.User.findByPk(id, { include: [{ model: db.Post, as: 'posts' }] });
    },
    posts: async (parent, args, { db }) => {
      return db.Post.findAll({ include: [{ model: db.User, as: 'author' }] });
    },
    post: async (parent, { id }, { db }) => {
      return db.Post.findByPk(id, { include: [{ model: db.User, as: 'author' }] });
    },
  },
  Mutation: {
    createUser: async (parent, { input }, { db }) => {
      return db.User.create(input);
    },
    updateUser: async (parent, { id, input }, { db }) => {
      const user = await db.User.findByPk(id);
      if (!user) throw new Error('User not found');
      await user.update(input);
      return user;
    },
    deleteUser: async (parent, { id }, { db }) => {
      const user = await db.User.findByPk(id);
      if (!user) throw new Error('User not found');
      await user.destroy();
      return true;
    },
    createPost: async (parent, { input }, { db }) => {
      const { userId, ...postData } = input;
      const user = await db.User.findByPk(userId);
      if (!user) throw new Error('User not found');
      return db.Post.create({ ...postData, userId });
    },
    updatePost: async (parent, { id, input }, { db }) => {
      const post = await db.Post.findByPk(id);
      if (!post) throw new Error('Post not found');
      await post.update(input);
      return post;
    },
    deletePost: async (parent, { id }, { db }) => {
      const post = await db.Post.findByPk(id);
      if (!post) throw new Error('Post not found');
      await post.destroy();
      return true;
    },
  },
  User: {
    posts: async (parent, args, { db }) => {
      // Bu, parent.getPosts() ile aynı işi yapar ama daha genel bir yaklaşımdır.
      // N+1 problemini çözmek için DataLoader kullanılması önerilir.
      return db.Post.findAll({ where: { userId: parent.id } });
    }
  },
  Post: {
    author: async (parent, args, { db }) => {
      return db.User.findByPk(parent.userId);
    }
  }
};

module.exports = resolvers;

Çözümleyici Açıklaması

  • Her çözümleyici fonksiyonu (parent, args, context, info) dört argüman alır. Burada parent ve args kullanılıyor. context objesi, veritabanı bağlantısı gibi paylaşılan kaynakları içerir.
  • Query çözümleyicileri, db.User.findAll(), db.User.findByPk() gibi Sequelize metodlarını kullanarak veritabanından veri çeker. include seçeneği, ilişkili verileri (örneğin bir kullanıcıya ait gönderileri) tek bir sorguda getirmeyi sağlar.
  • Mutation çözümleyicileri, db.User.create(), db.User.update(), db.User.destroy() gibi Sequelize metodlarını kullanarak veritabanında değişiklik yapar.
  • User ve Post tipleri altındaki çözümleyiciler, ilişkisel verileri yüklemek için kullanılır. Örneğin, bir User sorgulandığında, posts alanı ayrıca çözümlenir ve ilgili gönderileri getirir. Burada N+1 sorgu problemi oluşabilir; bu, “Gelişmiş Konular” bölümünde ele alınacaktır.

Apollo Server Kurulumu ve Başlatılması

Şimdi tüm parçaları bir araya getirerek Apollo Server’ımızı başlatalım. src/index.js dosyasını oluşturalım:

// src/index.js
require('dotenv').config();
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./graphql/schema');
const resolvers = require('./graphql/resolvers');
const db = require('./models'); // Sequelize bağlantısı

async function startApolloServer() {
  await db.sequelize.sync({ alter: true }); // Veritabanı tablolarını senkronize et (dev ortamı için)

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: () => ({ db }), // Sequelize modellerini çözümleyicilere aktar
    formatError: (error) => {
      // Hata mesajlarını özelleştirme veya loglama
      console.error(error);
      return error;
    },
  });

  const { url } = await server.listen({ port: process.env.PORT || 4000 });
  console.log(  Server ready at ${url});
}

startApolloServer();

Açıklama

  • dotenv.config() ile .env dosyasındaki ortam değişkenleri yüklenir.
  • db.sequelize.sync({ alter: true }): Bu, Sequelize’ın veritabanı tablolarını tanımlanan modellere göre oluşturmasını veya mevcut tabloları güncellemesini sağlar. Geliştirme ortamında kullanışlıdır ancak üretim ortamında migrasyonlar tercih edilir.
  • ApolloServer örneği typeDefs (şema) ve resolvers ile oluşturulur.
  • context fonksiyonu çok önemlidir. Bu fonksiyonun döndürdüğü obje, her bir çözümleyiciye üçüncü argüman olarak iletilir. Burada db objemizi (Sequelize modellerimizi içeren) context‘e ekleyerek çözümleyicilerin veritabanına erişmesini sağlıyoruz.
  • server.listen() sunucuyu belirtilen portta başlatır.

Uygulamanın Test Edilmesi

Her şey hazır! Şimdi sunucuyu çalıştırıp GraphQL API’mizi test edelim.

npm run dev

Tarayıcınızı http://localhost:4000 adresine yönlendirin (eğer port 4000 ise). Apollo Sandbox veya GraphQL Playground arayüzü açılacaktır.

Örnek Sorgular (Queries)

Yeni Kullanıcı Oluşturma

mutation CreateUser {
  createUser(input: { username: "john_doe", email: "john@example.com" }) {
    id
    username
    email
  }
}

Tüm Kullanıcıları Getirme

query GetUsers {
  users {
    id
    username
    email
    posts {
      id
      title
    }
  }
}

Belirli Bir Kullanıcıyı ID ile Getirme

query GetUserById {
  user(id: "1") {
    id
    username
    email
    posts {
      id
      title
      content
    }
  }
}

Yeni Gönderi Oluşturma

(Önce yukarıdaki CreateUser ile bir kullanıcı oluşturduğunuzdan emin olun ve userId‘ı güncelleyin.)

mutation CreatePost {
  createPost(input: { title: "My First Post", content: "Hello GraphQL with Sequelize!", userId: "1" }) {
    id
    title
    content
    author {
      username
    }
  }
}

Tüm Gönderileri Getirme

query GetPosts {
  posts {
    id
    title
    content
    author {
      id
      username
      email
    }
  }
}

Belirli Bir Gönderiyi ID ile Getirme

query GetPostById {
  post(id: "1") {
    id
    title
    content
    author {
      username
      email
    }
  }
}

Örnek Mutasyonlar (Mutations)

Kullanıcı Güncelleme

mutation UpdateUser {
  updateUser(id: "1", input: { username: "john_doe_updated" }) {
    id
    username
    email
  }
}

Gönderi Silme

mutation DeletePost {
  deletePost(id: "1")
}

Bu sorgu ve mutasyonları deneyerek API’nizin beklediğiniz gibi çalıştığını doğrulayabilirsiniz.

Gelişmiş Konular ve En İyi Uygulamalar

Kimlik Doğrulama ve Yetkilendirme (Authentication & Authorization)

Gerçek dünya uygulamalarında, kullanıcıların API’ye erişimini ve belirli işlemleri yapma yetkilerini kontrol etmeniz gerekir. Apollo Server, context objesini kullanarak kimlik doğrulama ve yetkilendirme bilgilerini çözümleyicilere iletmek için mükemmel bir yol sunar.
context fonksiyonunda bir kullanıcının kimlik bilgilerini (örneğin JWT token’dan) ayrıştırabilir ve db objesiyle birlikte çözümleyicilere iletebilirsiniz:

// src/index.js (context örneği)
const server = new ApolloServer({
  typeDefs,
  resolvers,
  context: ({ req }) => {
    // HTTP başlıklarından token'ı al
    const token = req.headers.authorization || '';
    // Token'ı doğrula ve kullanıcı bilgilerini çıkar
    // Örneğin, JWT.verify(token) ile
    const user = { id: 1, username: 'mockUser' }; // Gerçek bir kullanıcı objesiyle değiştirin
    return { db, user }; // Kullanıcı objesini context'e ekle
  },
});

Çözümleyicilerinizde bu user objesini kullanarak yetkilendirme kontrolleri yapabilirsiniz:

// src/graphql/resolvers.js (yetkilendirme örneği)
Mutation: {
  createPost: async (parent, { input }, { db, user }) => {
    if (!user) throw new Error('Authentication required');
    // ... post oluşturma mantığı
  }
}

Hata Yönetimi (Error Handling)

GraphQL, hata yönetimi için standart bir yapıya sahiptir. Apollo Server, hataları otomatik olarak işler. Ancak, hataları daha okunabilir hale getirmek veya hassas bilgileri gizlemek için formatError seçeneğini kullanabilirsiniz:

// src/index.js (formatError örneği)
const server = new ApolloServer({
  // ...
  formatError: (error) => {
    if (error.extensions.code === 'UNAUTHENTICATED') {
      return new Error('Authentication error: Could not authenticate with given token.');
    }
    // Diğer hata türleri için özel işlem yapabilirsiniz
    return error;
  },
});

Veri Yükleyici (DataLoader) Kullanımı

Yukarıdaki User ve Post çözümleyicilerindeki ilişkisel veri yükleme yönteminde (örneğin, bir kullanıcı için tüm gönderileri getirme), her kullanıcı için ayrı bir veritabanı sorgusu yapıldığını fark etmiş olabilirsiniz. Bu, N+1 sorgu problemine yol açar ve performansı olumsuz etkileyebilir. DataLoader kütüphanesi bu sorunu çözmek için tasarlanmıştır. DataLoader, aynı anda birden fazla isteği toplar (batching) ve önbelleğe alır (caching), böylece veritabanına yapılan sorgu sayısını önemli ölçüde azaltır.

src/index.js dosyasında context‘e DataLoader’ı ekleyebiliriz:

// src/index.js (DataLoader entegrasyonu)
const DataLoader = require('dataloader');

// ...

async function startApolloServer() {
  await db.sequelize.sync({ alter: true });

  const server = new ApolloServer({
    typeDefs,
    resolvers,
    context: () => ({
      db,
      // DataLoader örnekleri
      userLoader: new DataLoader(async (ids) => {
        const users = await db.User.findAll({ where: { id: ids } });
        return ids.map(id => users.find(user => user.id === id));
      }),
      postLoader: new DataLoader(async (ids) => {
        const posts = await db.Post.findAll({ where: { id: ids } });
        return ids.map(id => posts.find(post => post.id === id));
      })
      // Diğer DataLoader'lar...
    }),
    formatError: (error) => {
      console.error(error);
      return error;
    },
  });

  const { url } = await server.listen({ port: process.env.PORT || 4000 });
  console.log(  Server ready at ${url});
}

Ve src/graphql/resolvers.js dosyasında DataLoader’ı kullanabiliriz:

// src/graphql/resolvers.js (DataLoader kullanımı)
const resolvers = {
  // ...
  User: {
    posts: async (parent, args, { db, postLoader }) => {
      // Bir kullanıcının tüm gönderilerini getirmek için DataLoader kullanmak daha karmaşıktır
      // Çünkü DataLoader genellikle ID'ye göre tekil öğeleri yükler.
      // İlişkisel veriler için ayrı bir DataLoader yazmak gerekebilir
      // veya Sequelize'ın eager loading özelliğini Query seviyesinde kullanmak daha mantıklı olabilir.
      // Ancak, eğer bir Post'un yazarını yüklerken N+1 problemi yaşanıyorsa:
      // return postLoader.loadMany(parent.postIds); // Eğer parent'ta post ID'leri varsa
      return db.Post.findAll({ where: { userId: parent.id } }); // Şimdilik bu şekilde kalabilir
    }
  },
  Post: {
    author: async (parent, args, { userLoader }) => {
      return userLoader.load(parent.userId); // N+1 problemini çözer
    }
  }
};

Post tipindeki author çözümleyicisinde userLoader.load(parent.userId) kullanımı, birden fazla gönderinin aynı yazara sahip olması durumunda yalnızca tek bir veritabanı sorgusu yapılmasını sağlayacaktır.

TypeScript Kullanımı

Büyük ölçekli projelerde TypeScript kullanmak, kod kalitesini artırır ve hataları derleme zamanında yakalamanızı sağlar. Apollo Server ve Sequelize, TypeScript ile mükemmel uyumludur. Bu makale JavaScript tabanlı olsa da, projeyi TypeScript’e taşımak için @types/apollo-server, @types/graphql, @types/sequelize gibi tip tanımlamalarını kurmanız ve kodunuzu .ts uzantılı dosyalar halinde yazmanız yeterlidir.

Sonuç

Bu makalede, Node.js ortamında GraphQL sunucusu kurmak için Apollo Server’ı ve ilişkisel veritabanı yönetimini kolaylaştıran Sequelize’ı nasıl entegre edeceğinizi adım adım öğrendiniz. Proje yapılandırmasından başlayarak, Sequelize modellerini tanımladık, GraphQL şemamızı ve çözümleyicilerimizi oluşturduk ve son olarak Apollo Server’ı başlatarak API’mizi test ettik. Ayrıca, kimlik doğrulama, hata yönetimi ve N+1 sorgu problemini çözmek için DataLoader gibi gelişmiş konulara da değindik.

GraphQL’in esnekliği, Apollo Server’ın geliştirici dostu yapısı ve Sequelize’ın güçlü ORM yetenekleri birleştiğinde, modern ve ölçeklenebilir web uygulamaları için son derece etkili bir API katmanı oluşturabilirsiniz. Bu kurulum, projenizin temelini oluşturacak ve gelecekteki geliştirmeleriniz için sağlam bir başlangıç noktası sağlayacaktır. Veritabanı etkileşimlerinizi GraphQL ile nasıl yöneteceğinizi anlayarak, istemci tarafı uygulamalarınız için daha verimli ve optimize edilmiş veri erişimi sağlayabilirsiniz.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.