Hasura Nedir?
Bu yazının demo projesi GitHub’da bulunmaktadır — Demo projesinin bulunduğu GitHub Reposu → Repo
Yeni bir projeye başlayacaksınız, tüm teknoloji seçimlerinizi yaptınız ve API tarafı için GraphQL’i seçtiniz. Ancak, GraphQL API’sini oluşturmak, uygulamanızın kapsamına göre zaman ve efor gerektirebilir. CRUD işlemleri için API tarafını yazdınız ve bitirdiniz diyelim. Uygulamanızın filtreleme veya sayfalama gibi özelliklere de ihtiyaç duyması durumunda, GraphQL API’niz tarafında gerekli düzenlemeleri yapmanız gerekecektir.
Yukarıda sıraladığım tüm bu işlemleri sizin yerinize halledecek bir ürün olsaydı güzel olmaz mıydı? İşte bu noktada Hasura devreye giriyor!
Hasura Nedir?
Hasura, veritabanınız için GraphQL API’ları oluşturulmasını sağlayan bir servistir. Hasura üzerinden veritabanı tabloları ekleyebilir veya mevcut bir veritabanınızı Hasura’ya bağlayabilirsiniz. Tablolarınızı tanımladıktan hemen sonra Hasura, bu tablolar için API’lar oluşturur. Bu API’lar, veri ekleme, güncelleme ve bu verileri çekerken kullanabileceğiniz filtreleme, sıralama ve sayfalama parametreleri içerir.
Hemen bir uygulama yaparak Hasura’nın işleyişini daha iyi anlayalım!
Mini demomuzda planımızı gayet basit tutalım. Örneğin, bir mağazamız olduğunu ve bu mağaza için stok girişi yapacağımız bir form oluşturalım. Stok girişlerini gerçekleştirdikten sonra bunları bir tabloda göstereceğimiz bir arayüz yapalım. Yazının başlıkları sırasıyla:
- Hasura’da proje oluşturmak
- Tablo veya tabloların oluşturulması
- React projesinin oluşturulması
- Tablolara veri eklenmesi
- Tablolardan verilerin çekilmesi
1. Hasurada Proje oluşturmak
Öncelikle https://cloud.hasura.io'ya giriş yapalım. Giriş yaptıktan sonra yeni bir proje oluşturalım.
Herhangi bir değişiklik yapmadan verilen default ayarlar ile devam edelim.
Proje oluşturulduktan sonra Launch Console diyerek proje konsoluna erişiriz. Burada bize sağlanan birçok sekme bulunmaktadır. Bu yazıda API ve Data sekmelerini kullanacağız.
Şu an elimizde herhangi bir tablo olmadığı için GraphQL işlemi gerçekleştirebileceğimiz bir query veya mutation bulunmamaktadır. Öncelikle tablolarımızı oluşturalım. Bunu iki şekilde yapabiliriz:
Bu yazımızda şimdilik Cloud üzerinde tablolarımızı oluşturacağız.
2. Tablo veya tabloların oluşturulması
Data sekmesine tıkladıktan sonra Connect Database butonuna tıklayalım. Postgres seçeneği üzerinden bize ücretsiz olarak sağlanan Neon Database’ini seçelim.
Neon Database’ine giriş yaptıktan sonra, Hasura tarafına artık tablo ekleyebiliriz. stock adında bir tablo oluşturalım ve bazı alanlar ekleyelim:
- id
- name: Ürün adı
- category: Kategori
- created_at
- updated_at
- supplier: Tedarikçi
- quantity: Miktar
Sütun isimlerini ve tiplerini belirledikten sonra, Add Table diyerek tabloyu oluşturalım.
Şimdi bir React projesi oluşturalım ve Hasura ile uygulamamız arasındaki bağlantıyı sağlayalım.
3. React Projesinin Oluşturulması
yarn create vite
ile bir React + TypeScript uygulaması oluşturalım. Ardından GraphQL sunucumuza bağlanabilmemiz için Apollo Client’ı kurabiliriz.
Apollo Client, veriyi çekmek, güncellemek ve client tarafında state yönetimini sağlamak için kullanabileceğimiz bir state management kütüphanesidir. Kendi içerisinde built-in bir caching mekanizması bulundurur, böylece veriyi yerel olarak client tarafında tutarak network isteklerinin sayısı azaltılır.
yarn ile kurulumunu gerçekleştirelim:
$ yarn add @apollo/client graphql
Daha sonra Hasura bağlantımız için bir ApolloClient instance’ı oluşturalım:
import { ApolloClient, InMemoryCache } from "@apollo/client";
export const client = new ApolloClient({
uri: "<HASURA_ENDPOINT>",
cache: new InMemoryCache(),
headers: {
"x-hasura-admin-secret":
"<X_HASURA_AD
},
})
Hasura endpoint ve x-hasura-admin-secret değerlerini Hasura’nın API sekmesinden kopyalayalım. Bu, Hasura projemizde bulunan tablolara ve Hasura’nın tablolarımız için oluşturmuş olduğu API’lara erişimimizi sağlayacaktır.
Oluşturulan instance’ın tümReact componentlerimiz tarafından erişilebilir olmasını sağlayabilmemiz için Apollo Provider’ı kullanarak tanımlayalım.
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { ApolloProvider } from "@apollo/client";
import { client } from "./clients/apollo/index.ts";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<ApolloProvider client={client}>
<App />
</ApolloProvider>
</React.StrictMode>
);
3. Tablolara veri eklenmesi
Artık tablomuza kayıt ekleyebilir ve tablomuzdan kayıtları çekebiliriz. Apollo’nun sağladığı useMutation
hook'unu kullanarak tabloya kayıt ekleyebiliriz.
Sırasıyla yapacağımız işlemlere bakalım:
- Stok eklemek için mutation tanımlaması
- Form oluşturulması ve mutation’ın tetiklenmesi
- Verilerin çekilme işlemi ve tabloda gösterilmesi
Oluşturduğumuz stok tablosuna kayıt eklemek için öncelikle basit bir form oluşturalım. Bu projede UI kütüphanesi olarak Material Tailwind kullanacağız. Bunu kullanabilmek için Tailwind’i de projeye eklememiz gerekmektedir. Form state yönetimi için ise react-hook-form kullanalım. Tabloyu oluşturduktan sonra Hasura’nın bize bu tablolardan data çekmek veya data’yı güncellemek gibi API’lar sağladığından bahsetmiştik. Hasura’nın API tarafına baktığımızda, mutation ile nasıl veri ekleyebileceğimizi görebiliriz.
https://cloud.hasura.io'dan projemize girelim ve API sekmesinde Query, Mutation veya Subscription’ı seçtiğimizde kullanabileceğimiz API’ları görebiliriz.
Mutation’ı seçtiğimizde stock tablosunun mutation endpoint’lerine erişiriz. Amacımız yeni bir stok eklemek olduğu için insert_stock_one
seçebiliriz. Parametre olarak object parametresi içerisine, tablonun alanlarını ekleriz. Aşağıdaki örneği inceleyebilirsiniz:
Tanımlanan mutation'ı çağırdığımızda, tabloya yeni bir kayıt eklemiş oluruz. Aynı şekilde React tarafında da bu mutation endpoint'ini tanımlayalım ve form’a girdiğimiz değerler ile bu mutation’ın çağrılmasını sağlayarak tabloya kayıt ekleme işlemini gerçekleştirelim.
const ADD_STOCK = gql`
mutation AddStock(
$name: String!
$category: String!
$supplier: String!
$quantity: Int!
) {
insert_stock_one(
object: {
name: $name
category: $category
supplier: $supplier
quantity: $quantity
}
) {
id
name
category
supplier
quantity
}
}
`;
const App = ()=>{
....
}
Bu mutationa göndereceğimiz değerlerin tiplerini belirleyerek, yapılacak işlemin validasyonunu da belirlemiş oluruz.
Örneğin, eğer string olarak tanımladığımız bir parametreye, integer bir değer göndermeye çalışırsak API hata verecektir ve integer tipinde olması gerektiğini belirtecektir.
Tip tanımlamalarının sonunda bulunan ünlem ile, bu alanın zorunlu olarak gönderilmesi gerektiğini belirtiriz. Eğer bu mutation’ı çağırırken, zorunlu değer belirtilmemişse, API yine hata dönecektir ve işlemi gerçekleştirmeyecektir.
useMutation
hook'unu kullanarak mutation fonksiyonunun tanımlanmasını sağlarız:
const ADD_STOCK = gql`
mutation AddStock(
$name: String!
$category: String!
$supplier: String!
$quantity: Int!
) {
insert_stock_one(
object: {
name: $name
category: $category
supplier: $supplier
quantity: $quantity
}
) {
id
name
category
supplier
quantity
}
}
`;
const App = () => {
...
const [addStock, { loading }] = useMutation(ADD_STOCK);
const onSubmit = async (data) => {
try {
await addStock({
variables: {
name: data.name,
category: data.category,
supplier: data.supplier,
quantity: data.quantity,
},
});
} catch (error) {
console.log(error);
}
};
...
Şimdi bir form component’i oluşturalım. Bu form componenti içerisine stok bilgilerini girelim.
Form’u kaydettiğimiz zaman tanımladığımız mutation, form’a girdiğimiz değerler ile tetiklenir ve Hasura tarafında tabloya kayıt eklenir.
4. Tablolardan verilerin çekilmesi
Şimdi bu eklediğimiz verilerin bir tabloda gösterilmesini sağlayalım. Verilerin çekilmesi için useQuery
hook'unu kullanırız. Bunun için de öncelikle query tanımlamasını yapalım:
const GET_STOCK = gql`
query GetStock {
stock {
id
name
category
supplier
quantity
}
}
`;
Daha sonrasında component içerisinde bu hook'u çağıralım:
const {
loading: stockLoading,
data: stockData,
} = useQuery(GET_STOCK);
loading
: veri çekilirken true, çekme işlemi bittikten sonra false döner.data
: Tablodan çekilen data’yı içerisinde bulundurur. Data’yı çektikten sonra bir tablo component’i oluşturalım ve bu data’yı, oluşturacağımız tablo’ya gönderelim.
<StockTable data={stockData} loading={stockLoading} />
Veriyi ekledik ancak veriyi görüntüleyebilmemiz için sayfayı yenilememiz gerekti 😕 Sayfayı yenilemeden, güncel data’yı çekebilmek için useQuery
hook'unun refetch
fonksiyonunu kullanabiliriz. Böylelikle bir veri tabloya eklendikten sonra, refetch ile o tablonun verilerinin tekrar çekilmesi sağlanır 🚀
const App = () => {
const { data, loading, refetch } = useQuery(GET_STOCK);
return (
<div className="p-4 flex flex-col">
<StockModal onClose={() => refetch()} />
<StockTable data={data?.stock || []} loading={loading} />
</div>
);
};
export const StockModal: React.FC<StockModalProps> = ({ onClose }) => {
...
const onSubmit = async (data: Stock) => {
try {
await addStock({
variables: {
name: data.name,
category: data.category,
supplier: data.supplier,
quantity: data.quantity,
},
});
reset();
setOpen(false);
onClose();
toast.success("Stock added successfully", {
duration: 3000,
});
} catch (error) {
toast.error("Error adding stock", {
duration: 3000,
});
}
};
Bu yazımızda kısaca Hasura nedir ve bir React projesi üzerinden nasıl bağlantı sağlayabileceğimize değinmiş olduk. Bir başka yazıda filtreleme ve pagination özelliklerinden nasıl yararlanabiliriz, bunlara bakıyor olacağız 🙌
Bu yazının referansları:
Umarım faydalı bir yazı olmuştur. Eksik veya yanlış olduğunu düşündüğünüz kısımları bana iletirseniz çok sevinirim :)
İletişim kanalları: Twitter — LinkedIn — Mail
Bir sonraki yazıda görüşmek üzere!