React, Prisma ve GraphQL ile Bir Yemek Tarifi Uygulaması Nasıl Geliştirilir?
Giriş
Günümüzün hızla değişen dijital dünyasında, kullanıcı deneyimini merkeze alan, performanslı ve ölçeklenebilir uygulamalar geliştirmek büyük önem taşımaktadır. Bu makalede, modern web geliştirmenin en güçlü araçlarından bazılarını bir araya getirerek dinamik bir yemek tarifi uygulaması oluşturma sürecini adım adım inceleyeceğiz: React ile zengin bir kullanıcı arayüzü, GraphQL ile esnek ve verimli bir API katmanı ve Prisma ile güçlü, tip güvenli bir veritabanı etkileşimi sağlayacağız.
Yemek tarifi uygulamaları, genellikle karmaşık veri yapılarına (tarifler, malzemeler, adımlar, kategoriler, kullanıcı yorumları vb.) ve çeşitli kullanıcı etkileşimlerine (tarif arama, filtreleme, ekleme, düzenleme, favorilere ekleme) sahip oldukları için, bu teknolojilerin gücünü sergilemek için mükemmel birer örnek teşkil eder. Bu teknoloji yığını, geliştiricilere hem frontend hem de backend tarafında üst düzey bir verimlilik ve esneklik sunar.
Bu makalenin amacı, başlangıç seviyesinden ileri seviyeye kadar tüm geliştiricilere hitap eden kapsamlı bir rehber sunmaktır. Proje kurulumundan veritabanı modellemeye, GraphQL sunucusu oluşturmaktan React bileşenlerini entegre etmeye kadar her adımı detaylıca ele alacağız. Ayrıca, kimlik doğrulama, hata yönetimi ve dağıtım gibi gelişmiş konulara da değinerek uygulamanızı üretime hazır hale getirme konusunda size yol göstereceğiz.
Teknoloji Seçimleri ve Nedenleri
Bu projede kullandığımız her bir teknoloji, belirli bir problemi çözmek ve geliştirme sürecini optimize etmek için özenle seçilmiştir. İşte bu teknolojilerin tercih edilme nedenleri:
React (Frontend)
React, Facebook tarafından geliştirilen ve kullanıcı arayüzleri oluşturmak için kullanılan popüler bir JavaScript kütüphanesidir. Bileşen tabanlı mimarisi sayesinde, uygulamanın farklı bölümlerini bağımsız, yeniden kullanılabilir parçalara ayırmak mümkündür. Bu, kodun daha düzenli, bakımı daha kolay ve geliştirme sürecinin daha hızlı olmasını sağlar. Sanal DOM (Virtual DOM) kullanımı sayesinde, DOM güncellemeleri optimize edilir ve bu da yüksek performanslı kullanıcı arayüzleri sunar. Geniş bir geliştirici topluluğu ve zengin bir ekosisteme sahip olması, karşılaşılan sorunlara hızlı çözümler bulunmasını ve hazır kütüphanelerden faydalanılmasını kolaylaştırır. Yemek tarifi uygulamamızda, tarif listelerini, detay sayfalarını, formları ve diğer etkileşimli elemanları oluşturmak için React’ın gücünden faydalanacağız.
GraphQL (API Katmanı)
GraphQL, API’ler için bir sorgu dili ve çalışma zamanıdır. RESTful API’lere kıyasla birçok avantaj sunar. En büyük avantajlarından biri, istemcinin tam olarak ihtiyacı olan veriyi talep etmesine olanak tanımasıdır. Bu, “over-fetching” (gereğinden fazla veri çekme) ve “under-fetching” (gereken tüm veriyi tek seferde alamama) sorunlarını ortadan kaldırır. Tek bir endpoint üzerinden tüm veri işlemlerini yönetebilir, böylece API geliştirme ve bakımını basitleştirebilirsiniz. Güçlü tip sistemi sayesinde, API’nizin ne tür veriler döndüreceği ve ne tür argümanlar kabul edeceği açıkça tanımlanır; bu da istemci ve sunucu arasındaki iletişimi daha güvenilir hale getirir. GraphiQL gibi araçlar, API’yi keşfetmeyi ve test etmeyi son derece kolaylaştırır. Yemek tarifi uygulamamızda, tarifleri sorgulamak, yeni tarifler eklemek, mevcut tarifleri güncellemek veya silmek için GraphQL’i kullanacağız.
Prisma (ORM/Veritabanı Katmanı)
Prisma, modern bir veritabanı araç takımıdır ve özellikle TypeScript ile mükemmel bir uyum içindedir. Bir Object-Relational Mapper (ORM) olarak, veritabanı işlemlerini JavaScript/TypeScript kodunuzla yapmanızı sağlar, SQL sorguları yazma ihtiyacını ortadan kaldırır. Prisma’nın en güçlü özelliklerinden biri, schema.prisma dosyası aracılığıyla veritabanı şemanızı kod olarak tanımlayabilmenizdir (Schema-as-Code). Bu, veritabanı şemanızın versiyon kontrolünü yapmanıza ve prisma migrate komutlarıyla kolayca değişiklikleri uygulamanıza olanak tanır. Tip güvenliği, otomatik tamamlama ve derleme zamanı kontrolleri sayesinde geliştirici deneyimini önemli ölçüde artırır ve hataları azaltır. PostgreSQL, MySQL, SQLite, SQL Server ve MongoDB gibi çeşitli veritabanlarını destekler. Yemek tarifi uygulamamızda, tarifler ve malzemeler gibi modelleri tanımlamak, bu modeller üzerinde CRUD (Oluşturma, Okuma, Güncelleme, Silme) işlemleri yapmak ve veritabanı ile etkileşim kurmak için Prisma’yı kullanacağız.
Proje Kurulumu ve Temel Yapılandırma
Uygulamamızı geliştirmeye başlamadan önce, frontend ve backend için ayrı ayrı projeler oluşturup temel yapılandırmaları yapmamız gerekiyor.
Proje Başlatma (Frontend)
React uygulamasını başlatmak için Vite veya Create React App kullanabiliriz. Vite, daha hızlı bir geliştirme deneyimi sunar, bu yüzden onu tercih edeceğiz:
# Frontend projesi oluşturma
npm create vite@latest recipe-app-frontend -- --template react-ts
cd recipe-app-frontend
npm install
npm run dev
Bu komutlar, recipe-app-frontend adında bir dizin oluşturacak, temel React projesini kuracak ve geliştirme sunucusunu başlatacaktır.
Backend Kurulumu (GraphQL Sunucusu ve Prisma)
Backend projemizi ayrı bir dizinde oluşturacağız. Bu dizin, GraphQL sunucumuzu ve Prisma konfigürasyonlarımızı içerecek.
# Backend projesi oluşturma
mkdir recipe-app-backend
cd recipe-app-backend
npm init -y
Gerekli paketleri yükleme
npm install express apollo-server-express graphql prisma @prisma/client typescript ts-node nodemon dotenv
npm install --save-dev @types/express @types/graphql @types/node
TypeScript yapılandırması
npx tsc --init
tsconfig.json dosyasını düzenleyerek outDir ve rootDir gibi ayarları yapabilirsiniz. Örneğin:
// tsconfig.json
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src//*.ts"],
"exclude": ["node_modules"]
}
Şimdi Prisma’yı projemize dahil edelim:
npx prisma init --datasource-provider postgresql
Bu komut, prisma dizini içinde schema.prisma dosyasını ve .env dosyasını oluşturacaktır. .env dosyasına veritabanı bağlantı URL’nizi eklemeniz gerekmektedir. Örneğin:
# .env
DATABASE_URL="postgresql://user:password@localhost:5432/recipe_db?schema=public"
Veritabanı URL’nizi kendi veritabanı bilgilerinize göre değiştirmeyi unutmayın. Yerel bir PostgreSQL veritabanı kullanabilir veya Render, Supabase gibi bulut servislerinden faydalanabilirsiniz.
Veritabanı Şeması Tanımlama (Prisma Schema)
prisma/schema.prisma dosyasını açarak yemek tarifi uygulamamız için temel modelleri tanımlayacağız. Recipe (Tarif) ve Ingredient (Malzeme) modelleri, bu uygulamanın çekirdeğini oluşturacaktır.
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Recipe {
id String @id @default(uuid())
title String @unique
description String?
instructions String
prepTime Int?
cookTime Int?
servings Int?
category String?
imageUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ingredients Ingredient[] // Bir tarifin birden fazla malzemesi olabilir
}
model Ingredient {
id String @id @default(uuid())
name String
quantity String? // Örneğin: "2 su bardağı", "yarım çay kaşığı"
recipeId String
recipe Recipe @relation(fields: [recipeId], references: [id])
}
Burada Recipe modeli, bir yemeğin temel bilgilerini içerirken, Ingredient modeli ise o yemeğin malzemelerini tutar. Recipe ve Ingredient arasında bire çok (one-to-many) bir ilişki tanımladık: bir tarifin birden fazla malzemesi olabilir, ancak bir malzeme yalnızca tek bir tarife aittir. id alanları için uuid() kullanmak, benzersiz ve tahmin edilemez kimlikler sağlar.
Veritabanı Migrasyonları
Şema tanımladıktan sonra, bu şemayı veritabanımıza uygulamamız gerekiyor. Prisma’nın migration aracı bu konuda bize yardımcı olur:
npx prisma migrate dev --name init
Bu komut, Prisma şemasına göre veritabanınızda tabloları oluşturacak ve prisma/migrations dizini altına bir migration dosyası ekleyecektir. İlk migration’ımız genellikle init olarak adlandırılır.
GraphQL Sunucusu Oluşturma
Backend projemizin src dizini altında GraphQL sunucumuzu kuracağız. src/index.ts ana dosyamız olacak.
GraphQL Şeması Tanımlama (Type Definitions)
GraphQL şeması, API’mizin dış dünyaya hangi veri tiplerini ve hangi operasyonları (sorgular ve mutasyonlar) sunduğunu tanımlar. src/schema.ts dosyasında bu tanımları yapacağız:
// src/schema.ts
import { gql } from 'apollo-server-express';
export const typeDefs = gql
type Recipe {
id: ID!
title: String!
description: String
instructions: String!
prepTime: Int
cookTime: Int
servings: Int
category: String
imageUrl: String
createdAt: String!
updatedAt: String!
ingredients: [Ingredient!]!
}
type Ingredient {
id: ID!
name: String!
quantity: String
}
input IngredientInput {
name: String!
quantity: String
}
input CreateRecipeInput {
title: String!
description: String
instructions: String!
prepTime: Int
cookTime: Int
servings: Int
category: String
imageUrl: String
ingredients: [IngredientInput!]!
}
input UpdateRecipeInput {
id: ID!
title: String
description: String
instructions: String
prepTime: Int
cookTime: Int
servings: Int
category: String
imageUrl: String
ingredients: [IngredientInput!]
}
type Query {
recipes: [Recipe!]!
recipe(id: ID!): Recipe
}
type Mutation {
createRecipe(input: CreateRecipeInput!): Recipe!
updateRecipe(input: UpdateRecipeInput!): Recipe!
deleteRecipe(id: ID!): Recipe!
}
;
Burada Recipe ve Ingredient tiplerini, Prisma modellerimizle uyumlu olacak şekilde tanımladık. Query tipi, veri okuma operasyonlarını (tüm tarifleri listeleme, tek bir tarifi getirme) içerirken, Mutation tipi veri yazma operasyonlarını (yeni tarif oluşturma, güncelleme, silme) tanımlar. Input tipleri ise mutasyonlara gönderilecek verilerin yapısını belirler.
Çözümleyiciler (Resolvers)
Çözümleyiciler (Resolvers), GraphQL şemasındaki her bir alan için gerçek veriyi döndürmekten sorumlu fonksiyonlardır. Bu fonksiyonlar, veritabanı ile etkileşim kurmak için Prisma istemcisini kullanır. src/resolvers.ts dosyasında çözümleyicilerimizi tanımlayalım:
// src/resolvers.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export const resolvers = {
Query: {
recipes: async () => {
return prisma.recipe.findMany({
include: {
ingredients: true, // Tariflerle birlikte malzemeleri de getir
},
});
},
recipe: async (_parent: any, { id }: { id: string }) => {
return prisma.recipe.findUnique({
where: { id },
include: {
ingredients: true,
},
});
},
},
Mutation: {
createRecipe: async (_parent: any, { input }: { input: any }) => {
const { ingredients, ...recipeData } = input;
return prisma.recipe.create({
data: {
...recipeData,
ingredients: {
create: ingredients, // İlişkili malzemeleri de oluştur
},
},
include: {
ingredients: true,
},
});
},
updateRecipe: async (_parent: any, { input }: { input: any }) => {
const { id, ingredients, ...recipeData } = input;
// Mevcut malzemeleri silip yenilerini oluşturmak veya güncellemek daha karmaşık olabilir.
// Basitlik adına, burada sadece tarifin kendisini güncelleyelim.
// Malzemelerin güncellenmesi için ayrı bir mutasyon veya daha detaylı bir mantık gerekebilir.
return prisma.recipe.update({
where: { id },
data: recipeData, // Sadece recipeData'yı güncelliyoruz
include: {
ingredients: true,
},
});
},
deleteRecipe: async (_parent: any, { id }: { id: string }) => {
// İlişkili malzemeleri de silmek için CASCADE delete ayarı yapılabilir
// veya önce malzemeleri manuel olarak silmek gerekebilir.
await prisma.ingredient.deleteMany({
where: { recipeId: id },
});
return prisma.recipe.delete({
where: { id },
});
},
},
};
Çözümleyicilerimiz, prisma istemcisini kullanarak veritabanı sorguları yapar. findMany, findUnique, create, update ve delete gibi Prisma yöntemleri, veritabanı işlemlerini kolayca gerçekleştirmemizi sağlar. Özellikle createRecipe mutasyonunda, ingredients alanı için create anahtar kelimesini kullanarak, tek bir işlemde hem tarifi hem de ilişkili malzemeleri oluşturabildiğimize dikkat edin.
Apollo Sunucusunu Başlatma
Son olarak, GraphQL şemamızı ve çözümleyicilerimizi bir araya getirerek Apollo Server’ı başlatacağız. src/index.ts dosyamız şöyle görünecek:
// src/index.ts
import 'dotenv/config'; // .env dosyasını yüklemek için
import express from 'express';
import { ApolloServer } from 'apollo-server-express';
import { typeDefs } from './schema';
import { resolvers } from './resolvers';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function startApolloServer() {
const app = express();
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req, res }) => ({
prisma, // Prisma istemcisini tüm çözümleyicilere erişilebilir hale getir
// req, res // İhtiyaç duyulursa HTTP isteği ve yanıt nesnelerine erişim
}),
});
await server.start();
server.applyMiddleware({ app, path: '/graphql' });
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(Server ready at http://localhost:${PORT}/graphql);
});
}
startApolloServer();
Bu kod parçası, express ile bir HTTP sunucusu oluşturur, ApolloServer‘ı başlatır ve GraphQL endpoint’ini /graphql yolu üzerinde yapılandırır. context özelliği sayesinde prisma istemcisini tüm çözümleyicilere kolayca iletebiliriz. dotenv/config ile .env dosyasındaki ortam değişkenlerinin yüklenmesini sağlıyoruz.
Backend projesini çalıştırmak için package.json dosyanıza aşağıdaki script’leri ekleyebilirsiniz:
// package.json
{
"name": "recipe-app-backend",
"version": "1.0.0",
"main": "dist/index.js",
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts"
},
// ... diğer bağımlılıklar
}
Şimdi npm run dev komutunu çalıştırarak GraphQL sunucunuzu başlatabilirsiniz. http://localhost:4000/graphql adresine giderek GraphiQL arayüzünü görebilir ve sorgularınızı test edebilirsiniz.
React Frontend Entegrasyonu
Frontend uygulamamızda GraphQL API’mizle etkileşim kurmak için Apollo Client’ı kullanacağız.
Apollo Client Kurulumu
React projemizin kök dizininde (recipe-app-frontend) Apollo Client’ı kuralım:
cd recipe-app-frontend
npm install @apollo/client graphql
src/main.tsx veya src/index.tsx dosyasını güncelleyerek ApolloProvider ile uygulamamızı sarmalayacağız:
// src/main.tsx (Vite için)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.tsx';
import './index.css';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000/graphql', // GraphQL sunucumuzun adresi
cache: new InMemoryCache(),
});
ReactDOM.createRoot(document.getElementById('root')!).render(
,
);
Bu yapılandırma, App bileşenimiz ve alt bileşenlerinin tümünün Apollo Client’a erişmesini sağlar.
Veri Çekme (Queries)
GraphQL sorgularını React bileşenlerimizde kullanmak için @apollo/client kütüphanesinin useQuery hook’unu kullanacağız. Tüm tarifleri listeleyen bir RecipeList bileşeni oluşturalım:
// src/components/RecipeList.tsx
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_RECIPES = gql
query GetRecipes {
recipes {
id
title
description
imageUrl
prepTime
cookTime
servings
}
}
;
interface Recipe {
id: string;
title: string;
description?: string;
imageUrl?: string;
prepTime?: number;
cookTime?: number;
servings?: number;
}
function RecipeList() {
const { loading, error, data } = useQuery(GET_RECIPES);
if (loading) return Yükleniyor...
;
if (error) return Hata: {error.message}
;
return (
Tüm Tarifler
{data?.recipes.map((recipe) => (
{recipe.imageUrl && }
{recipe.title}
{recipe.description}
{/ Diğer detaylar eklenebilir /}
))}
);
}
export default RecipeList;
useQuery hook’u loading, error ve data nesnelerini döndürür. Bu sayede uygulamanızın yüklenme, hata ve veri durumlarını kolayca yönetebilirsiniz.
Tek bir tarifi görüntülemek için RecipeDetail bileşenini ve useQuery hook’unu kullanabiliriz.
// src/components/RecipeDetail.tsx
import React from 'react';
import { useQuery, gql } from '@apollo/client';
import { useParams } from 'react-router-dom'; // React Router kullanıyorsanız
const GET_RECIPE_BY_ID = gql
query GetRecipeById($id: ID!) {
recipe(id: $id) {
id
title
description
instructions
prepTime
cookTime
servings
category
imageUrl
ingredients {
id
name
quantity
}
}
}
;
interface Ingredient {
id: string;
name: string;
quantity?: string;
}
interface RecipeDetailType {
id: string;
title: string;
description?: string;
instructions: string;
prepTime?: number;
cookTime?: number;
servings?: number;
category?: string;
imageUrl?: string;
ingredients: Ingredient[];
}
function RecipeDetail() {
const { id } = useParams(); // URL'den id'yi al
const { loading, error, data } = useQuery(GET_RECIPE_BY_ID, {
variables: { id },
});
if (loading) return Tarif yükleniyor...
;
if (error) return Hata: {error.message}
;
if (!data?.recipe) return Tarif bulunamadı.
;
const { recipe } = data;
return (
{recipe.title}
{recipe.imageUrl && }
Açıklama: {recipe.description}
Hazırlık Süresi: {recipe.prepTime ? ${recipe.prepTime} dk : 'Belirtilmemiş'}
Pişirme Süresi: {recipe.cookTime ? ${recipe.cookTime} dk : 'Belirtilmemiş'}
Porsiyon: {recipe.servings || 'Belirtilmemiş'}
Kategori: {recipe.category || 'Belirtilmemiş'}
Malzemeler:
{recipe.ingredients.map((ingredient) => (
-
{ingredient.quantity} {ingredient.name}
))}
Hazırlanışı:
{recipe.instructions}
);
}
export default RecipeDetail;
Bu bileşen, URL’den tarifin ID’sini alır ve bu ID ile GraphQL API’mizden ilgili tarifi çeker.
Veri Gönderme (Mutations)
Yeni tarif ekleme veya mevcut tarifleri güncelleme gibi işlemler için useMutation hook’unu kullanacağız. Bir RecipeForm bileşeni oluşturalım:
// src/components/RecipeForm.tsx
import React, { useState } from 'react';
import { useMutation, gql } from '@apollo/client';
import { useNavigate } from 'react-router-dom';
const CREATE_RECIPE = gql
mutation CreateRecipe($input: CreateRecipeInput!) {
createRecipe(input: $input) {
id
title
description
imageUrl
}
}
;
const GET_RECIPES = gql
query GetRecipes {
recipes {
id
title
description
imageUrl
}
}
;
function RecipeForm() {
const navigate = useNavigate();
const [formData, setFormData] = useState({
title: '',
description: '',
instructions: '',
prepTime: '',
cookTime: '',
servings: '',
category: '',
imageUrl: '',
ingredients: [{ name: '', quantity: '' }],
});
const [createRecipe, { loading, error }] = useMutation(CREATE_RECIPE, {
refetchQueries: [{ query: GET_RECIPES }], // Yeni tarif eklendiğinde liste güncellensin
onCompleted: () => {
navigate('/'); // Başarılı olursa ana sayfaya yönlendir
},
});
const handleChange = (e: React.ChangeEvent) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
const handleIngredientChange = (index: number, e: React.ChangeEvent) => {
const { name, value } = e.target;
const newIngredients = [...formData.ingredients];
newIngredients[index] = { ...newIngredients[index], [name]: value };
setFormData((prev) => ({ ...prev, ingredients: newIngredients }));
};
const addIngredient = () => {
setFormData((prev) => ({
...prev,
ingredients: [...prev.ingredients, { name: '', quantity: '' }],
}));
};
const removeIngredient = (index: number) => {
const newIngredients = formData.ingredients.filter((_, i) => i !== index);
setFormData((prev) => ({ ...prev, ingredients: newIngredients }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await createRecipe({
variables: {
input: {
...formData,
prepTime: formData.prepTime ? parseInt(formData.prepTime) : null,
cookTime: formData.cookTime ? parseInt(formData.cookTime) : null,
servings: formData.servings ? parseInt(formData.servings) : null,
ingredients: formData.ingredients.filter(ing => ing.name.trim() !== ''),
},
},
});
} catch (err) {
console.error("Tarif oluşturulurken hata oluştu:", err);
}
};
return (
);
}
export default RecipeForm;
useMutation hook’u, bir mutasyon fonksiyonu (createRecipe) ve mutasyonun durumu (loading, error) gibi bilgileri döndürür. refetchQueries seçeneği, mutasyon tamamlandığında belirli sorguların yeniden çalıştırılmasını sağlayarak önbelleği günceller. onCompleted geri çağırma fonksiyonu ile mutasyon başarıyla tamamlandığında yönlendirme gibi işlemler yapabiliriz.
Arayüz Bileşenleri
Yukarıdaki RecipeList, RecipeDetail ve RecipeForm bileşenlerini src/App.tsx içinde veya React Router kullanarak farklı rotalara bağlayarak tam bir uygulama oluşturabilirsiniz.
// src/App.tsx
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import RecipeList from './components/RecipeList';
import RecipeDetail from './components/RecipeDetail';
import RecipeForm from './components/RecipeForm';
import './App.css'; // Basit stil dosyası
function App() {
return (
} />
} />
} />
{/ Düzenleme rotası da eklenebilir: } /> /}
);
}
export default App;
Bu, uygulamanın temel navigasyonunu ve farklı sayfalar arasındaki geçişleri sağlar.
Gelişmiş Konular ve İyileştirmeler
Uygulamamızı daha sağlam, güvenli ve kullanıcı dostu hale getirmek için bazı gelişmiş konulara değinmekte fayda var.
Kimlik Doğrulama ve Yetkilendirme
Bir yemek tarifi uygulamasında, kullanıcıların kendi tariflerini eklemesi, düzenlemesi veya silmesi için kimlik doğrulama ve yetkilendirme mekanizmaları gereklidir. JWT (JSON Web Tokens) genellikle bu amaçla kullanılır.
* Backend: Kullanıcı kaydı ve girişi için mutasyonlar oluşturulur. Giriş yapıldığında, sunucu bir JWT token’ı döndürür. Bu token, sonraki isteklerde Authorization başlığında gönderilir. GraphQL context içinde, gelen isteğin Authorization başlığından token’ı ayrıştırıp kullanıcı bilgilerini çözümleyicilere iletebilirsiniz. Çözümleyicilerde, kullanıcının belirli bir işlemi yapmaya yetkili olup olmadığını kontrol edebilirsiniz.
* Frontend: Kullanıcı giriş yaptıktan sonra alınan JWT token’ı localStorage veya sessionStorage gibi yerel depolarda saklanır. Apollo Client, her GraphQL isteğine bu token’ı eklemek için bir ApolloLink kullanabilir.
Hata Yönetimi
API’ler ve frontend uygulamaları arasındaki iletişimde hatalar kaçınılmazdır. GraphQL, standart bir hata formatı sunar.
* Backend: Prisma’dan gelen veritabanı hataları veya iş mantığı hataları, GraphQL hata formatına uygun olarak istemciye iletilmelidir. apollo-server varsayılan olarak bu hataları işler, ancak özel hata mesajları veya kodları tanımlayarak daha anlamlı geri bildirimler sağlayabilirsiniz.
* Frontend: useQuery ve useMutation hook’larının error nesnesi sayesinde, hataları kullanıcı arayüzünde gösterebilirsiniz. Kullanıcıya net ve anlaşılır hata mesajları sunmak, kullanıcı deneyimi açısından kritik öneme sahiptir.
Sayfalama ve Filtreleme
Çok sayıda tarif olduğunda, tümünü tek seferde yüklemek performans sorunlarına yol açabilir.
* Sayfalama: offset-based (skip/take) veya cursor-based (after/before) sayfalama teknikleri kullanılabilir. GraphQL sorgularınıza limit, offset veya after gibi argümanlar ekleyerek sunucunun yalnızca belirli bir veri dilimini döndürmesini sağlayabilirsiniz.
* Filtreleme ve Sıralama: GraphQL sorgularına filter veya orderBy gibi argümanlar ekleyerek kullanıcıların tarifleri kategoriye, pişirme süresine veya başlığa göre filtrelemesine ve sıralamasına olanak tanıyabilirsiniz. Prisma, findMany metodunda bu tür filtreleme ve sıralama seçeneklerini kolayca sunar.
Gerçek Zamanlı Güncellemeler (Subscriptions)
GraphQL Subscriptions, WebSocket’ler aracılığıyla gerçek zamanlı veri güncellemeleri sağlamak için kullanılabilir. Örneğin, yeni bir tarif eklendiğinde veya mevcut bir tarif güncellendiğinde, tüm bağlı istemcilere anında bildirim gönderebilirsiniz. Bu, sohbet uygulamaları veya canlı bildirimler gibi senaryolarda çok kullanışlıdır. Apollo Server, Subscriptions için kolay entegrasyon sunar.
Test Etme
Uygulamanın güvenilirliğini sağlamak için testler yazmak önemlidir.
* Frontend: React Testing Library ve Jest kullanarak bileşenlerinizi ve kullanıcı etkileşimlerini test edebilirsiniz. Apollo Client’ın MockedProvider‘ı, GraphQL sorgularını taklit ederek bileşen testlerini kolaylaştırır.
* Backend: Jest veya Mocha gibi test çatısı ile GraphQL çözümleyicilerinizi ve Prisma ile veritabanı etkileşimlerinizi test edebilirsiniz. Entegrasyon testleri, API’nizin uçtan uca doğru çalıştığını doğrular.
Dağıtım (Deployment)
Uygulamanızı canlıya almak için frontend ve backend’i ayrı ayrı dağıtmanız gerekir.
* Frontend (React): Vercel, Netlify veya AWS Amplify gibi servisler, statik React uygulamalarını dağıtmak için mükemmel seçeneklerdir. npm run build komutuyla oluşturulan statik dosyaları bu servislere yüklemeniz yeterlidir.
* Backend (Node.js/GraphQL): Render, Heroku, AWS EC2, DigitalOcean Droplets veya Google Cloud Run gibi platformlar, Node.js sunucularını barındırmak için kullanılabilir. Veritabanı olarak da bulut tabanlı bir PostgreSQL (örneğin Render PostgreSQL, Supabase, Railway) kullanmak, yönetim yükünü azaltır.
Sonuç
Bu makalede, React, Prisma ve GraphQL gibi modern ve güçlü teknolojileri bir araya getirerek dinamik bir yemek tarifi uygulaması oluşturma sürecini detaylı bir şekilde inceledik. Proje kurulumundan veritabanı şeması tanımlamaya, GraphQL sunucusu ve çözümleyicileri oluşturmaktan React frontend’ini entegre etmeye kadar her adımı adım adım ele aldık. Ayrıca, kimlik doğrulama, hata yönetimi, sayfalama ve dağıtım gibi gelişmiş konulara da değinerek uygulamanızın daha sağlam ve üretime hazır hale gelmesi için gerekli bilgileri sunduk.
Bu teknoloji yığını, geliştiricilere hem frontend hem de backend tarafında eşsiz bir verimlilik, esneklik ve tip güvenliği sunar. GraphQL’in esnek veri sorgulama yeteneği, Prisma’nın modern ORM özellikleri ve React’ın bileşen tabanlı mimarisi, karmaşık uygulamaların bile hızlı ve hatasız bir şekilde geliştirilmesini mümkün kılar. Bu yaklaşım, sadece yemek tarifi uygulamaları için değil, veri odaklı birçok farklı web uygulaması için de uygulanabilir ve geleceğe yönelik, ölçeklenebilir çözümler üretmenize olanak tanır. Bu rehberin, kendi projelerinizi geliştirirken size ilham vermesini ve yol göstermesini umuyoruz.
