Django ve React ile Full Stack — React

Barış Dede
8 min readAug 5, 2020

--

Bu yazı Django ve React Full Stack — Django yazısının devamı niteliğindedir. Eğer o kısmı okumadıysanız veya backend ortamınız hazır değil ise, Django kullanarak geliştirdiğim — geliştirirken notlarımı paylaştığım — yazıya buradan ulaşabilirsiniz.

Biliyorum, çok kapsamlı bir başlık. Olabildiğince basit tutacağım.

Bu yazıda bir kitap uygulamasını, Full Stack olarak Django ve React kullanarak ilk kurulumdan son ürüne kadar yapacağız. Bir ürün üzerinde göstermek adına bir CRUD(Create,Read,Update,Delete) uygulamasını, güzel ve basit bir giriş-geliştirme-ürün serisi yapmayı umuyorum. İki taraftan da (back end ve front end) çok fazla bilgi karmaşasına boğmadan, çalışır bir uygulamayı elde ederken aldığım notları, izlediğim adımları sizlerle paylaşmak istiyorum.

Ne, nedir gibi sorular üzerinde çok durmamaya, sıkmadan, amaca yönelik anlatmaya çalışacağım. Kullandığım her komut için bilmeyenler olabilir diye link vereceğim, kendi dökümantasyon sayfalarından hızlı bir şekilde fikir sahibi olup yazıya geri dönebilirsiniz.

Django kısaca Python tabanlı ücretsiz ve açık kaynak kodlu, model-template-view mimarisini temel alan bir web kütüphanesidir.

React web projelerinizin kullanıcı arayüzlerini, component bazlı olarak oluşturmanızı sağlayan bir JavaScript kütüphanesidir.

Giriş #1

Django ile backend yapımızı kurduk, HTML ile verilerimizi gördük. Şimdi işleri biraz daha kızıştırmanın zamanı geldi. O yazıyı hala okumadıysanız buradan okuyabilirsiniz.

Projemizin içerisinde React uygulamamızı kurarak başlayabiliriz.

Kurulum

Projemizin içerisine girip, komut satırınanpx create-react-app frontend kodunu yazarak kurulumumuzu başlatıyoruz. Frontend adında bir proje oluşturma sebebim, dosyaların birbirine girmeyip daha anlaşılır görünmesini istemem. Node dosyaları ve Django dosyaları ayrı klasörlerde olsun istiyorum ki erişmesi ve düzenlemesi kolay olsun.

Kurulum tamamlandıktan sonra cd frontend ve npm start komutlarını çalıştırıyoruz. Proje çalıştığında otomatik olarak http://localhost:3000/ adresini varsayılan tarayıcıda açacaktır. Açmazsa elle giriş yapabilirsiniz, 127.0.0.1:3000 adresinin de aynı yere ulaşacağını söylememe gerek yok diye düşünüyorum. Bu adresler dışında eğer farklı bir port üzerinden çalıştırırsa terminal ekranında erişim adresleri görünecektir.

Ne kadar bilmem gerekiyor?

Kurulumumuzu gerçekleştirdik fakat buradan sonrasına devam etmeden önce bilmemiz gerekenler var. Eğer ilk defa React ile karşılaşıyorsanız veya daha önce JavaScript yazmadıysanız, biraz zorlanabilirsiniz. Sadece React özelinde değil, JavaScript olarak ele alırsak: asenkron çalışan bir dile adaptasyon sağlamak bir miktar can sıkıcı olabiliyor. JavaScript tümüyle yukarıdan aşağıya doğru işlenen bir dil değildir. Altta yazdığınız kod en üstte yazdığınızdan daha önce çalışırsa çok şaşırmayın. O yüzden en azından JavaScript başlangıcı yapmanız sizin için faydalı olacaktır. https://javascript.info/ adresinde başlıklar halinde güzel bir şekilde anlatılmış. En azından yazının ilerleyen zamanlarında okuduğunuzu anlayabilecek kadar bilgi edinmeniz, yazının devamında işinizi kolaylaştıracaktır.

Fonksiyonel Bileşenler(Functional Components)

Arayüzümüzü tasarlarken fonksiyonel bileşenler(functional components) kullanacağız. Tercih etme sebebimiz kodumuzun okunabilirliği, yenilenebilirliği ve test edilebilirliği açısından bize kolaylıklar sağlaması.

Class Components ve Functional Components farkları gibi şeylere fazla değinmek istemiyorum. Sadece bu ikisinin farkı ile ilgili bile, sağladığı faydalar/zararlar ile ilgili inceleme yapmak hedefimizden şaşırtacaktır bizleri. O sebepten, bu konuda ayrı bir inceleme yapmak isterseniz aşağıdaki linki takip edebilirsiniz.

Fonksiyon ve Sınıf Bileşenleri : https://tr.reactjs.org/docs/components-and-props.html

Bileşen tabanlı geliştirme fikri bizi kod fazlalığı ve dağınıklığından kurtaracak.

İlk kurulumda karşımıza gelen en basit fonksiyonel bileşenimiz bu şekilde. Fonksiyonumuz return değeri olarak bir dom hazırlıyor ve gönderiyor. Temiz ve basit görünüyor değil mi? Bu fonksiyona kitap listemizi düzenli bir şekilde bastırabileceğimiz elementlerimizi eklemeye başlayabiliriz.

Fonksiyonumuzun döndürüğü elementleri ihtiyacımız olan şekilde değiştirdik. Basit bir html bilgisi ile bunu yaptık. React kullanırken yaptığımız tek fark class isimlerini verirken kullandığımız className parametresi oldu — şimdilik.

Ekran

Hooks — useEffect ve useState

Hooks React’ın 16.8~ sonrası kullanıma sunduğu, sınıf kullanmadan React özelliklerini kullanmamıza olanak sağlayan yapı. (https://reactjs.org/docs/hooks-intro.html#no-breaking-changes)

React lifecycle’ında componentDidMount , componentDidUpdate ve componentWillUnmount metodları yer alıyor. Hooks ile artık bu metodları useEffect kullanıyoruz.

Import işlemlerimizi yaparak işe koyulalım.

import {useState, useEffect } from 'React'

Fonksiyonumuz içerisinde ilk state’imizi tanımlayalım.

const [books, setBooks] = useState([])

booksadında bir state tanımlayıp içerisine varsayılan olarak boş bir dizi gönderdik. Bu işlem sınıf bileşenlerinde şu şekildeydi:

class Test extends React.Component{
constructor(props){
super(props)
this.state = {
books : []
}
}
}

Fonksiyonel bileşen kullanmanın ilk faydasını da böylelikle görmüş olduk.

Şimdi de bu state’imizin içerisine bir veri yollayıp kullanmayı deneyelim. Bu işlem için de useEffect kullanacağız.

useEffect (() => {
setBooks([
{
"name":"Otostopçunun Galaksi Rehberi",
"author": "Douglas Adams",
"description": "Lorem ipsum"
},
{
"name":"Hikayeler",
"author": "Edgar Allan Poe",
"description": "Lorem ipsum sit door amet"
}
])
},[])

books state’in içerisine iki adet veri yerleştirdik. Bu verileri JSX içerisinde bir döngüye sokarak ekrana bastıralım. Bu işlemi de Array.prototype.map kullanarak yapacağız.

books.map((book,index) => {
return (
<div className="book-item">
<h2>{book.name}</h2>
<p>{book.author}</p>
<p>{book.description}</p>
</div>
)}
)}

Kodumuzu çalıştırdığımızda artık ekranda elimizdeki verileri görebiliyoruz. Fakat biz bu verileri django ile hazırladığımız API’den alacağız. Django tarafında pure javascript ile XMLHttpRequst işlemi ile verilerimizi almıştık. Yine aynı kodu çalıştırabiliriz fakat bir adım daha ileriye giderek bu sefer farklı bir adresten de veri almamızı sağlayan Fetch API kullanacağız.

Fetch API

Django ile hazırladığımı REST API üzerinden verilerimizi alacağız. Eğer bu seriyi başından beri okumadıysanız ve farklı bir backend altyapısı kullanıyorsanız da bu işlem aynı olacaktır.

useEffect içerisinde kitaplarımızı tanımladığımız kısmı silip yerine API’ye bir istek atıp oradan gelen verilerimizi ekleyen kodumuzu yazacağız.

useEffect (() => {
async function fetchBooks() {
const res = await fetch("http://127.0.0.1:8000/books/");
res.json()
.then(res => setBooks(res.response))
.catch(err => setErrors(err));
}
fetchBooks();
},[])

Async fonksiyon hakkında daha fazla bilgiye bu adresten ulaşabilirsiniz. Fakat basit bir örnek ile açıklamak gerekirse:

getBooks = () => {
console.log('first book');
setTimeout(() => {
console.log('second book');
},3000);
console.log('third book');}
getBooks ();

JavaScript, senkron ve single-thread yapıya sahiptir. Yukarıdaki kodda first book çıktısını verdikten sonra third book ve sonra 3 saniye bekleyip second book çıktısını ekrana verecektir. Biz o 3 saniyeyi beklemesini istediğimiz için async fonksiyon kullanımını tercih ettik. Async yapı içerisinde await’i de barındırdığı için bir işlemi tamamlamadan diğerine geçmesini engellemiş oluyoruz.

Şimdi react tarafına dönecek olursak (Django ve React’ın çalıştığını varsayıyorum) tarayıcıyı açtığımızda karşımıza şöyle bir ekran gelecek:

CORS Headers

Açılımı Cross-Origin Resource Sharing(Kökenler arası kaynak paylaşımı) olan CORS Headers, bir kaynağın farklı bir kökende çalışan bir web uygulamasının erişim izinlerini kontrol eden mekanizmadır. (CORS)

API kaynağımızın bu erişim iznini vermesi için Django tarafında bir yapılandırma gerçekleştirmemiz gerekiyor.

pipenv install django-cors-headers kodunu backend tarafında çalıştırarak django-cors-headers uygulamamızı kuruyoruz. Kurulum detaylarını dökümantasyon sayfasından inceleyebilirsiniz.

Kurulum tamamlandıktan sonra settings.py dosyamızda bulunan INSTALLED_APPS içerisine corsheaders ‘ı ekliyoruz ve MIDDLEWARE içerisine de 'corsheaders.middleware.CorsMiddleware' kodumuzu ekliyoruz. Şimdi de hangi alanların erişebileceğini belirlememiz gerekiyor. Bunu iki şekilde yapabiliriz: bütün adresleri kabul ederek veya bir izin verilenler listesi vererek.

settings.py dosyamıza CORS_ORIGIN_ALLOW_ALL yazarsak isteyen bütün adresler verilerimize erişebilir. Sadece istediğimiz alanlarda sınırlandırmak istiyorsak:

CORS_ORIGIN_WHITELIST = [
"https://example.com",
"https://sub.example.com",
"http://localhost:8080",
"http://127.0.0.1:9000"
]

şeklinde bir liste de belirleyebiliriz. Bunun dışında bir regex pattern’ı belirlememize de olanak sağlıyor.

Kaydetiğimizde API’dan gelen veriler artık tarayıcıda listelenebilecek.

Öyle ya da böyle çalıştırdığımıza göre biraz daha sadeleştirme işlemi yapabiliriz. Unutmayalım ki bileşen bazlı çalışıyoruz. Kitaplarımızı direkt döngü içerisinde ekrana bastırmıştık. Şimdi bu verilerimizi ayrı bir bileşen olarak ele alalım.

Book adında yeni bir fonksiyon oluşturup bunun return değerini kitaplarımızı listelediğimiz elementler olarak atıyoruz.

Gelen prop’lar içerisinden book isimli prop’u aldık ve artık ekrana bastırabiliriz. App içerisindeki map kodumuzun arasına <Book book={item} key={index} /> kodunu eklediğimizde yine ekran çıktımız aynı şekli alacaktır.(book değişkeninin adını karışıklık olmaması için item olarak değiştirdim)

Tekrar eden değerler için React bizden bir seçici göndermemizi istediği için key içerisine index değerimizi gönderip çalıştırdık.

Şimdi de kitap ekleme sayfamızı React tarafına taşıyalım. Yeni bir bileşen oluşturup içerisine form elementlerimizi yerleştiriyoruz.

Axios

Basit bir şekilde formumuzu oluşturduk. Formumuzu api içerisine post metodu ile göndermek için axios kullanacağız. Bunu yaparken yine React Hooks’tan faydalanacağız. Giriş değerlerimizi state içerisinde tutacağız ve formu kaydet butonuna bastığımızda gönderimi yakalayıp istediğimiz işlemleri axios ile yaptıracağız.

CSRF Token

Formu gönderdiğimizde bir şey kaydedemediğimizi göreceğiz, 400 hata kodu döndürecek. Bunun ilk sebebi backend kodumuz bizden csrf token bekliyor. Dökümantasyon sayfasında ajax ile nasıl csrf token gönderebileceğimiz bilgisi yer alıyor. Oradaki getCookie fonksiyonunu kodumuza ekliyoruz. axios ile de bu token bilgisini header ile gönderiyoruz. Varsayılan çerez adı csrftoken bunu dilerseniz settings.py sayfasından özelleştirebiliyorsunuz. React kodumuzu düzenlediğimizde aşağıdaki hali alıyor.

Yukarıdaki formumuzu gönderdiğimizde tarayıcının network ekranından artık csrftoken bilgisini gönderdiğimizi görebiliriz. Fakat o da ne!? Hala 400 Bad request dönüyor. Backend kodumuza gidip serialize ettiğimiz değeri kontrol ettiğimizde farkediyoruz ki kaydetmeye çalıştığımız değer boş. BooksSerializer(data=request.POST or None)

print(request.POST) kodu bize <QueryDict: []> çıktısını veriyor. Bizim artık ihtiyacımız olan değer request.POST değil, request.data Serialize ettiğimiz veriyi request.data ile değiştirip kodumuzu çalıştırdığımızda artık React tarafından verimizi gönderip kaydedebiliyoruz.

CSRF Token ile ilgili daha fazla parametre ve daha detaylı bilgi için bu sayfayı ziyaret edebilirsiniz.

React Routing

Son olarak da görmek istediğimiz kitaplara ait özel linkler olsun istiyoruz. Bu işlemleri yine tek bileşende elle yapabilirdim fakat basit bir router yapısı da kurmadan geçmek istemedim.

Öncelikle kurulum yaparak başlıyoruz: npm install react-router-dom

Ben kodları yazarken bütün bileşenleri tek sayfada yaptım, bunları ayrı ayrı sayfalar ve klasörler içerisinde yaparak import { ComponentName } from 'path/file' şeklinde sayfaya dahil ederek <ComponentName /> şeklinde de kullanabilirsiniz. App bileşenimiz kitaplarımızı listeliyor ve üst tarafta da kitap oluşturma formumuzu gösteriyordu. Bu sayfaların hepsini birbirinden ayırarak basit bir router kuralım. App bileşenimiz kitapları listelediğimiz sayfa olarak kalsın. Sayfamızın sonundaki export default App kodunu da silelim varsayılan olarak o bileşenimiz renderlanmasın. Router yapımız şu şekilde:

Tabi benim gibi, önce kodu yazıp, kullandığınız kütüphaneleri sayfaya çağırmayı unutmayın.

import {
BrowserRouter as Router,
Switch,
Route,
Link
} from "react-router-dom";

İlk açılacak sayfamız Landing bileşeni olacağı için index.js dosyasından çağırdığımız bileşeni de Landing olarak değiştiriyoruz.

Sayfayı yenilediğimizde sayfanın en üstünde liste şeklinde, oluşturduğumuz menü linklerini göreceğiz. Ana dizinde olduğumuz için de <App/> bileşeni çağırılacak ve kitaplarımız listelenecek.

Tekil kitap gösterme sayfamız için de kodumuzu yazalım. Bu işlem bütün kitapları listelediğimiz kodumuz ile aynı. Tek fark, back end tarafından bir status bilgisi gönderip ona göre eğer istenen kitap veritabanımızda yoksa hata mesajı göstereceğiz. Backend kodumuzu yazarken veri bulunamadıysa sayfamızın 404 döndürmesini istemiştik. Şuan header bilgileri ile çok haşır neşir olmamak adına dönen veriye bir durum kodu (“ok”,”error”) ekleyerek bunu kontrol ederek ekrana mesaj bastıracağız.

Artık localhost:3000/book/1 adresine girdiğimizde, eğer veritabanında 1 id’li bir veri var ise ekrana bastıracak. Bir hata oluşur veya durum kodumuz hatalı gelirse ekrana “Something went wrong” mesajını bastıracak.

Ana sayfada kitapları listelerken başlıklarını da link olarak ekleyelim, sayfalar arası gezintimiz rahat olsun.

Kitap listeleme kodumuzu şu şekilde değiştiriyoruz:

Evet, artık kitaplarımız listelenirken başlıklarına tıklayarak detay sayfalarına gidebiliriz.

Bütün işlerimizi tamamladıktan sonra optimize, yayına koyacağımız dosyalarımızı almak için npm run build komutunu çalıştırıyoruz ve build adında bir klasöre statik dosyalarımızı çıkartıyor.

Şuana kadar herhangi bir CSS kodu yazmadan düz bir şekilde çıktılarımızı gördük. İlerleyen zamanlarda görsel olarak da eklemeler yapacağım. Yazının niteliğini kaybetmemesi için, bir de bu tarafa doğru kapı aralayıp boğmak istemiyorum. Projenin son halini DjangoReact linkinden inceleyebilir, dilerseniz katkıda bulunabilirsiniz.

Soru veya sorunlarınız için bana Twitter üzerinden ulaşabilirsiniz.

Happy codding! :)

Github, Twitter, Instagram : @bari5d

--

--