Node.js, Chrome V8 JavaScript Engine'i temel alan bir JavaScript çalışma ortamıdır. Bu çalışma ortamı sayesinde bizler Javascript kodlarını kullanmak için tarayıcı kısıtlamalarından kurtulmuş oluruz.
Node.js olay odaklı (event-driven) çalışır. Tek thread kullanmasından, yani aynı anda sadece bir işlem yapabilmesi nedeniyle node.js kendisinden istenilen işleri bir olay döngüsünün içerisinde değerlendirir.
Node.js işlem sırasına koyduğu bir olayın tamamlanmasını beklemeden diğer olayı işleme alabilir, bunun sonucu olarak Node.js iş akışını engellemez.
Node.js asenkron (asynchronous) çalışır. Asenkron çalışma, kod akışının yukarıdan aşağıya ilerlemediği, işlemlerin birbirini beklemediği çalışma türüdür. Bu sayede işlem sırası olaya göre belirlenebilir.
function showPrimeNumbers(lownumber, highnumber){
for(let i = lownumber; i<=highnumber; i++){
let isPrime = true;
for (let j = 2; j <= i; j++){
if (i%j===0 && j !==i){
isPrime = false
}
}
if(isPrime){
console.log(i);
}
}
}
showPrimeNumbers(2, 9)
bize 2 ve 9 arasındaki asal sayıları verir. Bunu terminalde görmek için node uygulama_adiyazılır.
process nesnesi nodeJS e ait global nesnelerden biridir. Bize o anki işlemle ilgili bilgiler verir.
console.log(process)ifadesi js klasörüne ilave edilerek bu bilgilere ulaşılabilir. Çok fazla bilgi verir. Bir kısmı bize gerekir.
process.argv özelliği bize node.exe ve çalışan js dosyasının parametrelerini verir.
console.log(process.argv)koda ilave olunca bu iki veriyi konsola 2 maddeli array olarak yazdırır.
içinde
console.log(process.argv)ifadesi bulunan kodu çalıştırırken terminalde boşluk bırakıp ifade girilirse boşluktan sonraki her maddeyi arraye ekler.
node uygulama_adi 1 12
uygulamanın içindeki kod bir array metodu olan slice ile ilk iki parametresi alınmayacak şekilde yazılırsa çıktı olarak terminalde kodun sonuna yazılan değerleri verir.
console.log(process.argv.slice(2));
process.argv.slice(2)değeri js üzerinden bir değişkene tanımlanır ise bu sayede terminalden veri alınabilir.
asal sayı bulma kodunun başına
const arguments = process.argv.slice(2);ile alınacak değişkenler eklenir.
Son kısma da çıktı almak için
showPrimeNumbers(arguments[0], arguments[1]);uygulanır
Node.js Javascript çalışma ortamının terminal ekranı.
REPL ortamını başlatmak için node komutunu terminal ekranına yazmak yeterli olacaktır.
alertbenzeri bazı kodlar tarayıcıda çalışırken REPL içinde çalışmayabilir. Tam tersi de olabilir.
help - Tüm komutları listeler.
break - Çok satırlı ifadeden çıkar.
ctrl + c - Çalışan komutu durdurur.
ctrl + d - REPL ortamını sonlandırır.
save dosya_adi - REPL ortamındaki kodları dosyaya kaydeder.
load dosya_adi - Dosyadaki kodları REPL ortamına alır.
Asenkron programlama uzun süren bir işlemi beklemeden diğer işlemlere devam edebilmektir, işlemlerin sırasıyla devam etme zorunluluğu yoktur. Önce bir dosya okuyalım, aaa ama bu dosya okumak uzun sürüyor, eee ne yapalım o zaman? Bu işlemin bitmesini beklemeden diğer işleme geçebiliriz. Bir işlem yapılırken diğer işlemler bloklanmaz.
Node.js için temel amaç mümkün olan en kısa sürede beklemeden fazla sayıda işlem yapmaktır.
Aynı anda başladığı işleri hangi sırayla sunacağını da olay döngüsü (event-loop) belirler.
klasik js kod sırasına göre sıralı ilerler (senkron çalışma) ve aynı anda tek bir işlem yapar (single thread)
Node.JS nin ise asenkron bir doğası vardır.
Callback fonksiyonu başka bir fonksiyon içerisinde argüman olarak kullanılabilen fonksiyon anlamına gelmektedir.
const func1 = () => {
console.log('func 1 tamamlandı')
func2();
};
const func2 = () => {
console.log('func 2 tamamlandı')
func3();
};
const func3 = () => {
console.log('func 3 tamamlandı')
};
func1();
bu kod çalıştığında önce func1 aktive olur. Sonra onun içine argüman olarak eklenen func2 aktive olur. Sonra onun içine argüman olarak eklenen func3 aktive olur.
Örnek
const books = [
{name: 'Kitap 1', author: 'Yazar 1'},
{name: 'Kitap 2', author: 'Yazar 2'},
{name: 'Kitap 3', author: 'Yazar 3'},
];
const listBooks = () => {
books.map(book => {
console.log(book.name);
})
};
const addBook = (newBook, callback) => {
books.push(newBook);
callback();
};
addBook({name: 'Kitap 4', author: 'Yazar 4'}, listBooks);
Bir işlemin sonucunu temsil eden bir JS nesnesidir.
Promise bir işlemin sonucunu temsil eden bir Javascript nesnesidir. Promise nesnesi Resolve ve Reject adında iki tane parametre alır ve olumlu durumlarda Resolve ile belirtilen işlemlerin, olumsuz durumlarda da Reject ile belirtilen işlemlerin yapılacağına dair güvence verir. Promise yapısı olumlu sonuçları .then(), olumsuz sonuçları da .catch() ile karşılar.
const promise1 = new Promise((resolve, reject) => {
resolve('Veriler Alındı');
reject('Bağlantı Hatası');
});
ile promise kurulur.
promise1
.then(value => {
console.log(value);
});
ile resorve durumu yakalar.
promise1
.catch(error => {
console.log(error);
});
ile reject durumu yakalar.
promise1
.then(value => {
console.log(value);
}).catch(error => {
console.log(error);
});
ile her iki durumu da yakalar.
Örnek:
const books = [
{name: 'Kitap 1', author: 'Yazar 1'},
{name: 'Kitap 2', author: 'Yazar 2'},
{name: 'Kitap 3', author: 'Yazar 3'},
];
const listBooks = () => {
books.map(book => {
console.log(book.name);
})
};
const addBook = (newBook) => {
const promise1 = new Promise((resolve, reject) => {
books.push(newBook);
resolve(books);
reject('bir hata oluştu');
})
return promise1;
};
addBook({name: 'Kitap 10', author: 'Yazar 10'})
.then(() => {
console.log('yeni liste');
listBooks();
}).catch((error)=>{
console.log(error);
});
Async - Await yapısı ES8 ile birlikte gelmiştir ve Promise yapısının daha anlaşılır bir söz dizimi ile yazılmasıdır.
Bir fonksiyon async anahtar kelimesi ile birlikte tanımlanırsa, fonksiyonun olumlu sonuçlanması sonucunda bir Promise döner. Bir async fonksiyon await anahtar kelimesi ile birlikte kullanılırsa ilgili Promise olumlu bir şekilde dönene kadar async fonksiyonunun çalışması bekletilir.
asenkron fonksiyon (async function) içinde bekle (await) komutu ile daha önce promise kalıbında yazılan verinin await ten sonraki kısmı true ise resolve çıktısı, false ise reject tipik reject hatası alınır. Bu hatayı almamak için tüm yapı try-catch bloğuna alınır.
özellikle sıralı işlemler için kullanılır.
function getData(data) {
return new Promise((resolve, reject) => {
console.log('Veriler alınmaya çalışılıyor..');
if (data) {
resolve('Veriler alındı');
} else {
reject('Veriler alınamadı')
}
})
}
function cleanData(receivedData) {
return new Promise((resolve, reject) => {
console.log('Veriler düzenleniyor..');
if (receivedData) {
resolve('Veriler düzenlendi')
} else {
reject('Veriler düzenlenemedi')
}
})
}
ifadelerinde önce birinci fonksiyon, sonra 2. fonksiyon çalışmalıdır.
Bu ifadelerin sırayla ve doğrulanarak ilerlemesi için promise yapsında:
getData(true)
.then(result => {
console.log(result);
return cleanData(true)
}).then(result =>{
console.log(result)
}).catch(error => {
console.log(error)
});
yapısı kullanılır.
async-await yapısı için ise:
async function processData(){
try{
const receivedData = await getData(false); // devam etmek için bunu bekle.
console.log(receivedData);
const cleanedData = await cleanData(true); // devam etmek için bunu bekle.
console.log(cleanedData);
} catch(error) {
console.log(error)
}
}
processData();
kullanılır.
Node.js uygulaması farklı görevleri olan farklı Javascript dosyalarından oluşur ve Node.js içerdiği tüm Javascript dosyalarına bir modül olarak davranır. Modül genelde belirli özel bir işlevi olan Javascript dosyasıdır. Bu şekilde Node.js uygulamaya ait olan dosyaları farklı bölümlere ayırarak kodun daha modülarize olmasını ve aynı zamanda bu kod kontrolünün ve hata yakalamanın daha kolay olmasını sağlar.
Burada daha önce üzerine konuştuğumuz asal sayılar örneğimizi daha modüler hale getirelim ve sonrasında diğer dosyaların bu modülümüze ulaşmasını sağlayalım.
primeNumber.js => primeNumberModule.js adıyla yeniden kopyalandı ve dizenlendi
primeNumberModule.js içinde:
function showPrimeNumbers(lownumber, highnumber){
for(let i = lownumber; i<=highnumber; i++){
let isPrime = true;
for (let j = 2; j <= i; j++){
if (i%j===0 && j !==i){
isPrime = false
}
}
if(isPrime){
console.log(i);
}
}
}
module.exports = {showPrimeNumbers};
Bu işlem ile fonksiyonu diğer dosyaların kullanımına açıyoruz. {} içine aralarına virgül koyarak birden fazla fonksiyon export edilebilir.
Başka bir js dosyası içinde:
const primeNumbers = require('./primeNumbersModule'); değişkene modül atanır. primeNumbers.showPrimeNumbers(10,22);değişken üzerinden fonksiyon çağırılır.
Birden fazla fonksiyon export ettiğimizde, örneğin:
module.exports = {
showPrimeNumbers,
showFivePrimes
}
const primeNumbers = require('./primeNumbers');
primeNumbers.showPrimeNumbers(10, 22);
primeNumbers.showFivePrimes();
const { showPrimeNumbers, showFivePrimes } = require('./primeNumbers');
showPrimeNumbers(10, 22);
showFivePrimes();
Burada ES6 Import söz dizimini kullanarak da aynı işlemi yerine getirebiliriz. Fonksiyonu export ederken tek bir fonksiyon için default anahtar kelimesini kullanırız.
export default showPrimeNumbers;
import ederken ise bu kez dosya uzantısını kullanmak durumundayız.
import showPrimeNumbers from './primeNumbers.js';
FS (File System) modülü Node.js'in dosya ve klasör işlemleri yaparken kullandığı bir çekirdek modülüdür. Buradaki çekirdek modülü kavramıyla Node.js yazılımıyla birlikte gelmesidir, tekrar oluşturulmasına gerek yoktur ve kullanıma hazırdır. FS modülünü kullanmak için:
import { readFile } from 'node:fs';
yeni node sürümü const fs = require('fs'); kodunu kabul etmiyor. ES6 ya göre yazılması için ise package.json içine "type":"module", eklenmesi gerekiyor.
"type": "module" Hakkında
package.json içine "type":"module", eklenmesi require kullanımını engeller. Sadece ES6 ya göre olan yazımı kabul eder.
Açıklama için tıklayınız
readFile('../password.txt','utf8', (err, data) => {
if (err) console.log(err);
console.log(data);
console.log('dosya okundu')
});
ile password.txt içindeki veri konsola yazdırılır.
import { writeFile } from 'node:fs';
ile writeFile import edildi.
writeFile('../example.txt', 'kodluyoruzzz', 'utf8', (err)=>{
if (err) console.log(err);
console.log('dosya başarılı bir şekilde oluşturuldu')
});
ile example.txt oluşturuldu ve içine kodluyoruzzz yazıldı.
import { appendFile } from 'node:fs';
ile appendFile import edildi.
appendFile('../example.txt', '\nkodluyoruz 2222', 'utf8', (err)=>{
if (err) console.log(err);
console.log('yeni veri eklendi')
});
ile yeni veri eklendi. verinin başındaki \n verinin son satırın altına yeni bir satıra yazılmasını sağlar.
import { unlink } from 'node:fs';
ile unlink import edildi.
unlink('../example.json', (err)=>{
if(err) console.log('silemedim:(');
else console.log('DOSYA SİLİNDİ');
});
ile example.json silindi.
import { mkdir } from 'node:fs';
ile mkdir import edildi.
mkdir('../uploads/json', { recursive: true }, (err)=>{
if(err) console.log(err);
else console.log('Klasörler Oluşturuldu');
})
ile uploads/json dosyaları iç içe oluşturuldu. İç içe oluşturabilmek için { recursive: true }, eklendi. Tek bir klasör oluşturulacaksa buna gerel yok.
import { rmdir } from 'node:fs';
ile rmdir import edildi.
rmdir('../uploads', { recursive: true }, (err)=>{
if(err) console.log(err);
else console.log('Klasörler silindi');
});
ile uploads ve altındaki tüm klasörler silindi. Hem kökü hem de altındakileri silmek için { recursive: true }, eklendi. Tek bir klasör silinecek ise buna gerel yok.
import * as fs from 'node:fs';
ile tüm fs import edilir. Ancak import edilen kodları kullanmak için başına fs. eklenir.
Örnek:
fs.appendFile('../example.txt', '\nkodluyoruz 2222', 'utf8', (err)=>{
if (err) console.log(err);
console.log('yeni veri eklendi')
});
NPM: node package manager. Hem kayıtlı paketlerin olduğu web sayfasını hem de paket indirmeyi sağlayan kod satırı uygulamasını içerir.
paket: bir veya daha fazla modülden oluşan ve içerdiği modüller konusunda bilgi veren package.json dosyasına sahip bir klasör sistemi.
İşimize yarayan paketler npm den hazır indirilebilir.
Tüm bu kodları yönetmemiz için bizim bu bilgileri taşıyan bir dosyaya ihtiyacımız var, işte bu dosyanın adı package.json. Bu dosyayı oluşturmak için:
npm init
yazıp gelen soruları cevaplayabiliriz.
npm init -y
tüm sorular varsayılan olarak doldurulur.
Artık npm den paket indirebiliriz.
npm i paketAdi veya npm install paketAdi
Bu işlemden sonra package.json dosyasında
"dependencies": { benzeri bir ifade yer alır.
"paketAdi": "^4.17.1"
}
npm uninstall paketAdi
Dosya olarak silinen veya gite aktarılmadan bırakılan bağlı paketleri yüklemek için:
npm install
Bu nedenle paket dosyalarını git'e aktarmaya gerek yoktur.
package.json
dosyası içine
"script"
kısmına istediğimiz kısayolu
"kisayolAdi": "dosyaYolu"
şeklinde yazabiliriz.
Kullanmak için komut satırına:
npm kisayolAdi
yazılır.
Node.js ile yazılan bir web uygulaması temelde bir request - response (istek - cevap) döngüsüdür. Bizler internet tarayıcımıza www.google.com yazmakla, aslında uzaktaki sunucuya bir request gönderiyoruz. Uzaktaki sunucu ise bize gördüğümüz Google sayfasını göstererek bir response dönüyor.
Sayfa > incele > ağ yaptığımızda: Request HEADERS içerisinde yaptığımız istek, Response HEADERS içerisinde ise aldığımız cevap ile ilgili bilgiler taşınır. Bunlardan bazıları istek metodu, remote address, cantent-type, path vs..
Sunucunun sahip olduğu IP (Internet Protocol) adresi sunucuya ulaşmamızı sağlar. Aslında biz www.google.com yazmakla arka plandaki DNS sunucusu yardımıyla rakamlardan oluşan IP adresine ulaşır ve chunk ismi verilen parçalar halinde karşı sunucudaki web sayfası kendi bilgisayarımızda gösterilmeye başlar.
TCP/IP (Transmission Control Protocol/Internet Protocol), istemci - sunucu arasında veri iletme, alma birimleri arasında organizasyonu sağlayan, böylece bir yerden diğerine veri iletişimini olanaklı kılan ve farklı protokollerden oluşan yapıya verilen genel addır.
Sunucu ile istemci arasında iletişim için kullanılan protokoldür.
https http protokolünün ek güvenlik önlemlerine sahip versiyonudur.
İstemci ile sunucu arasındaki iletişimin başarılı kurulma durumunu gösterir.
Örn: 404: sayfa bulunamadı.
Node.js ile bir web sunucusu oluşturmanın çeşitli yolları vardır ve biz burada başlangıç olarak Node.js çekirdek modülü olan "http" modülünü kullanacağız. server.js dosyası oluşturup önce bu modülü çağıralım.
import * as http from 'node:http';
createServerhttp modülü ile bir web sunucusu oluşturmak için createServer metodunu kullanacağız. createServer metodu, callback fonksiyon parametreleri olarak request ve response nesnelerini (request - response döngüsünün elemanları) alır. Doğal olarak biz bir web sunucusu oluşturuyorsak bu sunucumuz üzerinde isteklerde bulunmak ve bunlara gerekli cevapları almak isteriz.
const server = http.createServer((req, res)=>{
const url = req.url; // ile istek yapılan url alınır
if(url === '/'){
res.writeHead(200, {'Content-Type': 'text/html'});// ile status pre ve tipi belirtilir
res.write('İndex Sayfası'); //ile cevap iletilir. Cevap tipi html olarak bildirildiği için cevap html elementi olarak da yazılabilir.
} else if (url === '/about'){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('Hakkımda Sayfası');
} else if (url === '/contact'){
res.writeHead(200, {'Content-Type': 'text/html'});
res.write('İletişim Sayfası');
} else {
res.writeHead(404, {'Content-Type': 'text/html'});
res.write('404 Sayfa Bulunamadı');
}
res.end(); // ile cevap sonlandırılır
});
Biz kendi bilgisayarızımı sunucuya çevirdik ve aşağıdaki kod ile portu belirttik
const port = 3000;
server.listen(port, ()=>{
console.log(`Sunucu Port ${port} de başlatıldı.`);
});
konsola node webSunucusu yazdığımız anda localhost:3000 adresi için sunucumuz hazır.
Express.js, Node.js üzerine yazılmış bir web çatısıdır. Express bize node.js ile yazması biraz karmaşık olan işlemleri daha basit bir şekilde oluşturmamızı sağlar.
express'i kurmak için konsola önce
npm init(:daha önce yapılmadıysa)
npm i express(express'in kurulumu) yazılır.
import express from 'express'; // ile import edilir.
const app = express(); // JS de fonksiyonlar da değişkenlere atanabiliyor.
app.get('/', (req, res) => {
res.status(200).send('Hello World')
}); // ile ana sayfaya status code 200 olarak gönderilir ve Hello World aktarılır.
app.get('/about', (req, res) => {
res.status(200).send('About Sayfası')
});
app.get('/contact', (req, res) => {
res.status(200).send('Contact Sayfası')
});
app.get('*', (req, res) => {
res.status(404).send('404 Sayfa Bulunamadı')
}); // ile ana sayfaya status code 404 olarak gönderilir ve 404 Sayfa Bulunamadı aktarılır.
const port = 3000;
app.listen(port, ()=>{
console.log(`Sunucu port ${port} da çalışmaya başladı`);
}); // ile port açılır.
Bunları biliyoruz
prettier - code formatter eklenti olarak indirilir.
ctrl + shift + p -> Prettier: Create Configuration File ile proje dosyasında .prettierrc dosyası oluşturulur.
prettier'in çalışması için gereken dosya npm ile kurulur.
npm init
npm install prettier -D --save-exact --save-exact bizde
yüklü olan prettier ile aynı versiyonu indirmeyi sağlar.
"semi":true her satırın sonuna noktalı virgül ekler.
"singleQuote": true tek tırnak içine almak için.
"trailingComma": "es5"
Bu kurulum kodu yazdıktan sonra tümünü seçip sağ tıklayıp "format document" yapınca kodun istediğimiz gibi görünmesini sağlar.
hazır .gitignore dosyası oluşturmak için: toptal.com/developers/gitignore
Orjinali için:
https://templatemo.com/tm-552-video-catalog
Biz hocanın düzenlediği versiyon ile çalışacağız.
npm i express --save
express dependencies. yani bu kodun çalışması için
elzem.
prettier ise devDependencies. yani kodun çalışması
için gerekli değil. Kodu yazarken bize yardımcı.
const express = require('express');
const app = express(); // ile express import edildi.
app.get('/', (req, res)=>{
res.send('Merhaba') }) // ile kök dizine gönderildi.
const port = 3000
app.listen(port, ()=>{ console.log(`Sunucu ${port} portunda başlatıldı`) }) // 3000 portu açıldı.
her işlemden sonra sunucuyu kapatıp açmayı otomatik hale getirmeye yarar.
npm install --save-dev nodemon
--save-dev: nodemon devDependenciestir.
çalışması için package.json -> script içine:
"start": "nodemon app.js"eklenir.
terminale:
npm startyazarak aktifleştirilir. app.js her kaydedildiğinde port baştan başlatılır.
req-res döngüsündeki herşeye Middleware denir.
Middlewarelar yularıdan aşağı sıra ile çalışır. req-res döngüsü
bitmeden bir sonrakine ilerleyemez. İlerlemeyi sağlama için
next() methodu kullanılır.
get request yapısı da middlewaredir.
const myLogger = (req, res, next)=>{
console.log("Middleware log 1");
next()
}
ile fonksiyonumuz tanımlanır. isteğe cevap olmadığından döngü next()
ile bozulur.
app.use(myLogger);ile fonksiyon kullanıma alınır.
app.use(express.static('public')) ile ekspress için statik dosyalarla çalışabileceği Middleware import edildi.
kodu kullanmak için public isimli dosya oluşturuldu ve statik dosyalar içine yüklendi.
app.get('/', (req, res) => {
res.sendFile(path.resolve(__dirname, 'temp/index.html'));
});
path.resolve() yöntemi, bir yol dizisini veya yol
segmentlerini mutlak bir yola çözümler.
__dirname yürütülmekte olan kaynak dosyayı içeren
dizinin mutlak yoludur.
Kodun işe yaraması için core modül olarak:
const path = require('path'); import edilir.
Template engine bize değişen içeriği html kodu içerisinde dosya uzantısı değiştirilerek kullanmamızı sağlar. Template engineler sayesinde bir static dosyaları ve değieşen dinamik içeriği birlikte kullanabiliriz. Farklı template engineler kullanılabiliriz, biz bu çalışmamızda EJS template engine yapısını kullanacağız.
Terminale:
npm install ejs
JS koduna:
const ejs = require('ejs');
app.set("view engine", "ejs"); ile express'e template
engine olarak esj kullanacağımızı belirtiriz. Bu durumda esj views
klasörünün içine bakar. Bu nedenle bu isimde bir dosya oluşturup html
dosyaları içine konuldu. Dosya uzantıları ejs olarak değiştirildi.
JS içindeki
app.get('/', (req, res) => {
res.sendFile(path.resolve(__dirname, 'temp/index.html')); // yerine
res.render('index'); // yazılır.
});
diğer sayfalar da bu şablona göre eklenir.
Html olarak yazdığımız dosyalardaki ortak kodları birleştirmek için kullanılır.
views klasörünün içinde partials klasörü oluşturulur.
Daha önce .html olarak yazılmış ve .ejs ye dönüştürülmüş sayfalardaki
ortak kodlar için .ejs uzantılı dosyalar partials içinde oluşturulur.
Örnek: _header.ejs
Bu dosyanın içine ortak olan header kodu yapıştırılır.
Yapıştırılan kod diğer sayfalardan silinir ve yerine:
<%- include('partials/_header') -%> yazılır.
Bu kalıp diğer kodlara da uyarlanır.
Bu sayede sayfaların ortak noktaları tek elden yönetilebilir. Örneğin js kodunda link olarak verilen "/about" navbar içindeki "about.html" yerine yazıldığında tüm sayfalarda geçerli olur.
SQL tablolarla çalışır. NoSQL döküman tabanlıdır.
JS json tabanlı NoSQL ile daha rahat çalışır.
SQL de tutulacak bilgiler tüm sütunlarda sabittir. NoSQL de böyle bir şart bulunmaz.
setup ve path eklenmesi videoda var.
Dökümantasyon için tıklayınız.
show dbsmevcut veri tabanlarını gösterir.
useile veri tabanları arasında geçiş yapılabilir ve yeni veri tabanı oluşturulabilir.
use pcat-test-dboluşturulur. İçi boşken db listesinde görünmez.
db.photos.insertOne(
... {title: "photo 1", description: "Photo desc lorem ipsum dolor!",
qty: 20}
... )
ile ilk örnek verimizi girdik. ve karşılığında:
{ "acknowledged" : true,
"insertedId" : ObjectId("63a564428fa9a29730595280") }
çıktısını aldık.
db.photos.find()ile db içindeki photosta yer alan veriye ulaştık. Çıktı olarak:
{ "_id" : ObjectId("63a564428fa9a29730595280"), "title" : "photo 1",
"description" : "Photo desc lorem ipsum dolor!", "qty" : 20 }
aldık. Biz belirmediğimiz halde "_id" : olarak
mongoDB her eklenen ögeye eşsiz ID ekler. Bu ID primary key görevi
görür.
show collectionsile içinde olduğumuz dbin collectionslarını gösterir.
Birden fazla veri ekleme örneği:
db.photos.insertMany( [
... {title: "photo 2", description: "Photo 2 desc", qty: 50},
... {title: "photo 3", description: "Photo 3 desc", qty: 100}, ])
db.photos.find({title: "photo 1"}) title değeri photo 1
olan veriyi verir.
db.photos.find({title: "photo 1", qty: 100}) title
değeri photo 1 olan ve qty değeri 100 olan veriyi
verir. Böyle bir veri yoksa boş döner.
db.photos.find(
{qty: {$lt: 200}}
).limit(2)
qty değeri 200 den az olan 2 veri getirir.
db.photos.updateOne(
... {title: "photo 1"},
... { $set: {qty: 222}} )
title değeri photo 1 olan ilk verinin qty değeri 222 olarak
güncellendi.
db.photos.deleteOne(
... { qty: {$lt: 500}} )
qty değeri 500 den küçük olan değerlerden birini siler.
Asla Kullanma geri dönüşü yok. Onay sormadan siler.
Önce doğru db yi sildiğinden emin ol.
use pcat-test-db
db.dropDatabase()
Mongoose bir ODC (Object Document Mapper) aracıdır.
terminalde:
npm i mongoose
test.js oluşturuldu.
test.js içine:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
ile mongoose çağırıldı. ve Schema metodu değişkene atandı.
database e bağlanmak için:
mongoose.connect('mongodb://localhost/pcat-test-db');
pcat-test-db varsa bağlanır. Yoksa önce yaratır sonra bağlanır.
şema oluşturmak:
const PhotoSchema = new Schema({
title: String,
description: String, });
koleksiyon oluşturmak:
const Photo = mongoose.model('Photo', PhotoSchema);
mongoose 'Photo' verisini alıp photos collectiona dönüştürüyor.
Koleksiyona veri eklemek:
Photo.create({
title: 'Photo Title 1',
description: 'Photo 1 desc. lorem ipsum', });
Verileri okumak:
Photo.find({}, (err, data) => {
console.log(data); });
ile veriler okunur ve konsola yazdırılır.
Yeni mongoose versiyonları callback fonksiyonu desteklemiyor. Bunu yerine aşağıdaki sintax uygulanır.
Photo.find().then(data => console.log(data));
Verileri güncellemek:
const id = '63a57f07705931fa74b54a35'; // ile silinecek
verinin id numarası değişkene atanır.
Photo.findByIdAndUpdate(
id,
{ title: 'Photo Title 1 updated',
description: 'Photo desc 1 updated'},
{ new: true }, // ile konsola yazılan datanın yeni olması sağlanır.
(err, data) => {
console.log(data);
}
);
Verileri silmek
const id = '63a5861957e7afe19e3caa81';
Photo.findByIdAndDelete(id, (err, data) => {
console.log('foto silindi');
});
add.ejs içindeki form istediğimize uygun hale getirildi.
Form elementine method="POST" ve action="/photos" eklendi.
action="/photos" ile yapılan yönlendirmeyi add.js içinde //Routes altında bu kod ile yakalıyoruz:
app.post('/photos', (req, res) => {
console.log(req.body);
res.redirect('/') // yönlendirmesi ile req, res döngüsü sonlandırılır.
});
ile gelen veri konsola yazdırılmaya çalışıldı ancak undefined alındı. Verinin okunması için 2 adet middleware kullanılır.
verinin doğru okunması için middleware:
app.use(express.urlencoded({extended:true})); // url deki datayı okumamızı sağlar.
app.use(express.json()); // url deki datayı jsona çevirir.
Bu noktada formdan gelen bilgiyi yakaladık.
kök dizine models klasörü ve içine Photo.js dosyası oluşturuldu.
Photo.js içinde
const mongoose = require('mongoose');
const Schema = mongoose.Schema; // ile mongoose ve mongoose.Schema import edildi
const PhotoSchema = new Schema({
title: String,
description: String,
image: String,
dateCreated: {
type: Date,
default: Date.now,
},
}); // ile şema oluşturuldu ve default date olarak o anki tarih atandı.
const Photo = mongoose.model('Photo', PhotoSchema); // ile Photo şemasını photos koleksiyonu oluşturmak için kullandık.
module.exports = Photo // ile export ettik.
app.js içine:
const Photo = require('./models/Photo'); // ile import edilir.
const mongoose = require('mongoose'); // ile mongoose import edilir.
mongoose.set('strictQuery', true);
mongoose.connect('mongodb://localhost/pcat-test-db'); // ile database bağlantısı sağlanır.
app.post('/photos', async (req, res) => {
await Photo.create(req.body)
res.redirect('/')
}); // daha önce oluşturduğumuz ve formdan veri almamızı sağlayan kod bu şekilde düzenlenir. Bu sayede formdan gelen veri database e kaydedilir.
app.get('/', async (req, res) => {
const photos = await Photo.find({}).sort('-dateCreated') // ile photos değişkenine databaseten gelen veri atanır. sort içine yazılan parametreye göre veriler sıralanır.
res.render('index', {
photos: photos // ile database üzerinden gelen veri kök sayfaya gönderilir.
});
});
veriyi ilgili template engine içine gömmek için index.ejs içindeki kartların biri hariç hepsini sildik. Kalanı da kendi verimiz için kalıp olarak kullanacağız.
.ejs içinde js kodu kullanmak için <% ile %> içine yazılır.
photos içindeki tüm verileri almak için .ejs içinde for döngüsü kurulur.
<% for (let i = 0; i < photos.length; i++ ){ %> <!-- ile for döngüsü başlatılır. -->
<div class="col-lg-4 col-md-6 col-sm-12 tm-catalog-item">
<div class="position-relative tm-thumbnail-container">
<img src="img/tn-01.jpg" alt="Image" class="img-fluid tm-catalog-item-img">
<a href="video-page.html" class="position-absolute tm-img-overlay">
<i class="fas fa-play tm-overlay-icon"></i>
</a>
</div>
<div class="p-4 tm-bg-gray tm-catalog-item-description">
<h3 class="tm-text-primary mb-3 tm-catalog-item-title"> <%= photos[i].title %></h3> <!-- ile photos[i].title yerleştirildi. -->
<p class="tm-catalog-item-text"><%= photos[i].description %></p> <!-- ile photos[i].description yerleştirildi. -->
</div>
lt;/div>
<% } %> <!-- ile for döngüsü sonlandırılır. -->
MongoDB bizim için her kayıt için ıd oluşturuyor. Biz de onu kullanacağız.
index.ejs içinde daha önce düzenlediğimiz kodda altı çizili değişikliği yapıyoruz.
<% for (let i = 0; i < photos.length; i++ ){ %>
<div class="col-lg-4 col-md-6 col-sm-12 tm-catalog-item">
<div class="position-relative tm-thumbnail-container">
<img src="img/tn-01.jpg" alt="Image" class="img-fluid tm-catalog-item-img">
<a href="photos/<%= photos[i]._id %>" class="position-absolute tm-img-overlay"> <!-- bu kısımda _id linke eklendi -->
<i class="fas fa-play tm-overlay-icon"></i>
</a>
</div>
<div class="p-4 tm-bg-gray tm-catalog-item-description">
<h3 class="tm-text-primary mb-3 tm-catalog-item-title"> <%= photos[i].title %></h3>
<p class="tm-catalog-item-text"><%= photos[i].description %></p>
</div>
</div>
<% } %>
app.js içine
app.get('/photos/:id', async (req, res) => { // ile yakaladığımız id üzerinden req alıyoruz.
const photo = await Photo.findById(req.params.id) // ile ilgili ıd ye ait veriyi photo değişkenine atadık.
res.render('photo', {
photo
})
}); // ile değişkeni photo.ejs ye gönderdik.
photo.ejs içinde
başlık kısmını <%= photo.title %> ile ve tanım kısmını <%= photo.description %> ile değiştiriyoruz.
sayfadaki css yapısındaki bozulmayı düzeltmek için dosya yolunu relative yapacağız. Bunun için partials dosyasında link olarak yazmadığımız tüm linklerin başına / koyuyoruz.
Bunun için express-fileupload modülünü kullanacağız.
terminale:
npm i express-fileupload
const fileUpload = require('express-fileupload'); app.use(fileUpload());
name="image" type="file"
encType="multipart/form-data"forma görsel yüklemeyi sağlar.
app.post('/photos', async (req, res) => {...}
kodunun içine:
console.log(req.files.image);
yazılarak aşağıdaki çıktı konsola yazdırılır.
{
name: 'car_unsplash.jpg',
data: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 01 00 48 00 48 00 00 ff e1 00 22 45 78 69 66 00 00 4d 4d 00 2a 00 00 00 08 00 01 01 12 00 03 00 00 00 01 00 01 ... 637876 more bytes>,
size: 637926,
encoding: '7bit',
tempFilePath: '',
truncated: false,
mimetype: 'image/jpeg',
md5: 'bb9f681142d25dee6e6f251188018846',
mv: [Function: mv]
}
app.js içine fs çağırılır.
const fs = require('fs');
Yukarıda bilgileri alabildiğimizi gördükten sonra app.post(...) aşağıdaki gibi düzenlenir.
app.post('/photos', async (req, res) => {
const uploadDIR = 'public/uploads'; // ile upload edilecek dosya belirtilir.
if(!fs.existsSync(uploadDIR)){ // eğer dosya yok ise:
fs.mkdirSync(uploadDIR) // oluştur.
} // Bu işlem tamamlanmadan devam edilmemesi için fonksiyon senkron (Sync) yazılır.
let uploadedImage = req.files.image; // ile yüklenen dosyanın bilgileri uploadedImage değişkenine atanır.
let uploadPath = __dirname + '/public/uploads/' + uploadedImage.name; // ile yüklenen dosyanın yüklenme yolu değişkene atanır. Dosya oluşturmak için fs modülü import edilir ve kullanılır.
uploadedImage.mv(uploadPath, async () => { // ile yüklenen dosyanın ekleneceği yer belirtilrir.
await Photo.create({
...req.body,
image: '/uploads/' + uploadedImage.name,
}); // ile database üzerine veri eklenir.
res.redirect('/');
});
});
ile ana sayfaya yönlenirilir ve kod sonlandırılır.
Görselin indexte ve tekli foto sayfasında görünmesi için template içine görsel tanımlanır.
img src="<%= photo.image %>"
index sayfasında son eklenenin en üstte görünmesi için .sort() kullanılır.
app.get('/', async (req, res) => {
const photos = await Photo.find({}).sort('-dateCreated'); // ile dateCreated verisine göre tersten (azalan) sıralanır. Başında "-" olmasaydı artan sıralanırdı.
......})
views içine edit.ejs oluşturulur.
add.ejs içeriği kopyalanıp edit.ejs içine yapıştırılıp düzenlenir.
photo.ejs içinde edit butonu link olarak düzenlenir:
<a href="/photos/edit/<%= photo._id %>" class="btn btn-primary p-0 mb-4 tm-btn-animate tm-btn-download tm-icon-download"><span>Update Details</span></a>
app.js routes içine:
app.get('/photos/edit/:id', async (req, res) => {
const photo = await Photo.findOne({ _id: req.params.id });
res.render('edit', {
photo,
});
});
edit.ejs içinde title ve description alanları düzenlenir:
<div class="form-group">
<input type="text" name="title" value="<%= photo.title %>" class="form-control rounded-0" placeholder="Photo Title">
</div>
<div class="form-group">
<textarea rows="8" name="description" class="form-control rounded-0" placeholder="Photo Description" required><%= photo.description %></textarea>
</div>
Bu noktada put request (değiştir) işlemine ihtiyacımız var ancak pek çok tarayıcı sadece get ve post request üzerinden çalışır. Bu nedenle put requesti post request ile yapacağız. Bunun için method-override modülünü kullanacağız.
konsola:
npm i method-override
const methodOverride = require('method-override'); app.use(methodOverride('_method'));
edit.ejs form etiketinde action:
action="/photos/<%= photo._id %>?_method=PUT"olarak güncellendi. Bu sayede
method="POST" olmasına rağmen yöntem PULL olarak request edildi.
edit.ejs formdan gelen put requesti karşılama için add.js router içine:
app.put('/photos/:id', async (req, res) => {
const photo = await Photo.findOne({ _id: req.params.id });
photo.title = req.body.title;
photo.description = req.body.description; // ile değişiklikleri ekledik.
photo.save(); // ile değişiklikleri database e kaydettik.
res.redirect(`/photos/${req.params.id}`) // ile fotoğrafın sayfasında döndük.
});
Delete butonunu kullanabilmek için buton link haline getirilir ve aşağıdaki düzenleme yapılır.
<a
href="/photos/<%= photo._id %>?_method=DELETE" // ile linkten delete request alınır.
class="btn btn-danger p-0 tm-btn-animate tm-btn-download tm-icon-download"
onclick="return confirm('ARE YOU SURE?')" // ile delete işlemi için popup comfirm kutusu açılır.
>
<span>Delete Photo</span>
</a>
DELETE metodunun çalışması için middleware alanında kullanılacak metotların belirtilmesi gerekir. İlgili kod aşağıdakine modifiye edilir.
app.use(
methodOverride('_method', {
methods: ['POST', 'GET'], // ile gerekli metotlar eklenir.
})
);
Delete requestin alınması, ve çalışması için app.js router alanına:
app.delete('/photos/:id', async (req, res) => { // ile delete request karşılanır.
const photo = await Photo.findOne({ _id: req.params.id }); // ile silinecek data tanımlanır.
let deletedImage = __dirname + '/public' + photo.image; // ile silinecek dosyanın yolu tanımlanır.
fs.unlinkSync(deletedImage); // ile dosya silinir.
await Photo.findByIdAndRemove(req.params.id); // ile database'ten data silinir.
res.redirect('/');
});
Bu noktada dosya eklemeden data girmeye çalışırsak hata alırız ve server donar. Bunu engellemek için input type="file" alanına required eklenir.
Aynı isimde iki dosya yüklediğimizde upload klasöründe tek dosya oluşuyor. Bunu engelleme için dosya adının başına id veya tarih (Date.now() vs) eklenip uniqe hale getirilebilir. Bunun için tarihi değişkene atamak mantıklı. Aksi takdirde tarihteki saniyelik sapma dosya adını değiştirir.
MVC - Model View Controller - uygulama kodunu Model, View ve Controller olmak üzere birbirine bağlı üç öğeye ayrılmasını içeren bir yazılım mimari yapısıdır.
Uygulamanın veri yapısını ve veri tabanı ile ilişkisini tanımlar. Schema "şablon" yapısı sayesinde veri özellikleri belirlenir.
Uygulamanın son kullanıcılara görünen bölümünü temsil eder. Son kullanıcıya gösterilecek veri özelleştirilebilir.
Son kullanıcıdan gelen isteklerin uygun View'e yönlendirilmesi kontrol edilir. İstek, cevap işleyicisi olarak da tanımlanır.
Kök dizine controllers adında bir klasör oluşturuldu. İçine photoControllers.js dosyası oluşturuldu. app.js içindeki yönlendirme dosyaları bu dosyaya fonksiyon içine atılır. ve export edilir. Fonksiyonun olduğu photoControllers.js app.js içine import edilir.
photoController içinde:
exports.getAllPhotos = async (req, res) => {
const photos = await Photo.find({}).sort('-dateCreated');
res.render('index', {
photos: photos,
});
};
ile asenkron fonksiyon app.js içinden taşınılır. const photoController = require('./controllers/photoControllers'); ile photoControllers.js import edilir.app.get('/', photoController.getAllPhotos ); ile fonksiyon çağırılır.
Bu işlem database ile photo fonksiyonunu yönettiğimiz tüm fonksiyonlara uygulanır.
Taşınan fonksiyonların çalışması için gereken modeller de photoControllers.js içine import edilir.
const Photo = require('../models/Photo');
Sadece sayfa çağıran fonksiyonlar ise controllers/pageControllers.js içine taşınır.
Kodu taşıdıktan sonra dosya konumu değiştiği için dosya bağlantılarının da güncellenmesi gerekir.
Tüm fotoğrafların anasayfada karşımıza çıkmaması için pagination (sayfalama) kullanılan yöntemlerden biridir.
req.query
req.query bizim tarayıcı üzerinden yaptığımız sorgunun node.js üzerinden algılanmasını sağlar.
sorgu örneği: tarayıcıda:
http://localhost:3000/?user=test&pass=1234
konsolda okumak için: ilgili sayfanın app.get fonksiyonuna:
console.log(req.query);
{ user: 'test', pass: '1234' } şeklinde key-value alınır. Buradan alınan değere göre işlem yapılabilir.
photoControler.js içinde index sayfamızı yöneten asenkron fonksiyon aşağıdaki gibi düzenlenir:
exports.getAllPhotos = async (req, res) => {
const page = req.query.page || 1; // ile sayfadan page olarak alınacak sorgu page değişkenine tanımlanır. varsayılan olarak 1 atanır (bunun için veya anlamında || kullanılır.)
const photosPerPage = 2; // bir sayfada görünmesi istenilen fotoğraf sayısı değişkene atanır.
const totalPhotos = await Photo.find().countDocuments(); // ile toplam veri sayısı değişkene atanır.
const photos = await Photo.find({})
.sort('-dateCreated')
.skip((page-1) * photosPerPage) // ile daha önceki sayfalarda gösterilmesi gerekenler atlanır.
.limit(photosPerPage); // ile sayfada gösterilmek istenen kadar veri çekilir. fazlası çekilmez.
res.render('index', {
photos: photos,
current: page, // ile mevcut sayfa bilgisi gönderilir.
pages: Math.ceil(totalPhotos/photosPerPage) // ile toplam sayfa bilgisi gönderilir.
});
};
index.ejs içindeki Catalog Paging Buttons alanı aşağıdaki gibi düzenlenir:
<% if (pages>0){ %> <!-- sayfa sayısı 0dan büyükse butonları ekler. -->
<ul class="nav tm-paging-links">
<% for (i=1; i<=pages; i++){ %><!-- ile sayfa numaralarını çağırır. Bu işlemi toplam sayfa sayısına kadar tekrarlar. -->
<% if(i == current){ %> <!-- ile sayfa numarası açık olan sayfa ile aynı olan butonu aşağıdaki gibi gösterir. -->
<li class="nav-item active"><a href="/?page=<%= i %>" class="nav-link tm-paging-link"><%= i %></a></li><!-- Link olarak req.query ile page verisini gönderir. -->
<% } else { %><!-- ile yukarıdaki kurala uymayan sayfa numaralarını aşağıdaki gibi gösterir. -->
<li class="nav-item"><a href="/?page=<%= i %>" class="nav-link tm-paging-link"><%= i %></a></li><!-- Link olarak req.query ile page verisini gönderir. -->
<% } %>
<% } %>
</ul>
<% } %>
Atlas Cloud, mongoDB geliştiriceleri tarafından geliştirilen bir veritabanı bulut hizmetidir, https://account.mongodb.com/account/login?nds=true adresinden giriş yapıyoruz.
sayfaya üye ol > ilerle > new project > create project > build a database > free > ayarlar > create
kurulan cluster için > connect > connect your application
çıkan ekranda yer alan "Add your connection string into your application code" altındaki link app.js içindeki mongoose lokal bağlantı kodunu atlasa yönlendirmek için kullanılır.
mongoose.connect('mongodb+srv://arslan_ng:narniag7A.@cluster0.z8aaudl.mongodb.net/pcat-db?retryWrites=true&w=majority');
kablosuz ağ WLRN3 ve WLRN 2 ise bağlanmaz. WINET e geç.
üye ol > crate > Web Service
github ile bağlıyoruz.
RENDER ortamının istediği portu seçebilmesi için:
const port = process.env.PORT || 5000;
package.json içinde script alanı:
"scripts": { olarak güncellendi. Bu şekilde nodemon modülü aradan çıkartıldı.
"start": "node app.js"
},
gereksiz yer kaplamaması için video banner fotoğraf ile değiştirildi. video silindi. upload klasöründeki resimler de silindi.
istenilen proje github üzerinden render tarafından çekildi.
Hazır dosyaları temp içine aldık.
konsola:
npm init
npm i express
app.js içine:
express import:
const express = require('express');
const app = express();
const port = 3000;
app.listen(port, ()=>{
console.log(`App started on port ${port}`)
})
konsola:
npm i --save-dev nodemonveya
npm i -D nodemongeliştirme aracı olarak nodemon yükler.
package.json da "scripts" alanında test silinir ve
"start": "nodemon app.js"
ile nodemon üzerinden app.js start komutuna işlenir.
daha önceki projeden .prettierrc dosyası bu projeye kopyalanır. Bu sayede her sağ tıklayıp format document dediğimizde .js kod düzenlemesi daha düzenli görünür.
git reposite oluşturuldu ve github ile bağlandı.
developer.mozilla.org/en-US/docs/Web/HTTP/Status
HTTP status code o an yapılan işlem hakkında bilgi verir.
örnek html status gönderimi:
app.get('/', (req, res) => {
res.status(200).send('index sayfası');
});
Konsola:
npm i ejsile ejs yüklenir
app js Template Engine alanına:
app.set("view engine", "ejs"); app.use(express.static("public"));
kök dizine public klasörü oluşturulur ve tempin içindeki .html harici içine eklenir.
kök dizine views klasörü oluşturulur ve .html dosyalar içine alınır. uzantılar .ejs ye çevirilir.
views içine partials klasörü oluşturulur. Bu alanda tüm sayfalarda ortak olacak kısımlar tutulur.
_header.ejs
_navigation.ejs
_footer ejs gibi.
Bunların içine tüm templatelerde ortak olacaklar atanır. sonra buradan çekilir. Bunun için:
<%- include('partials/_header'); -%> kalıbı kullanılır.
partials içindeki linkler düzenlenir.
Navbar üzerinden aktif olan sayfanın linkini farklı göstermek için active klası kullanılır. Bunu dinamik olarak yapmak için önce page_name bilgisi app.js içinden response içinde gönderilir. Sonra bu bilgi _navigation.ejs içinde yakalanır ve koşul true ise active olarak dönmesi sağlanır.
app.get('/', (req, res) => {
res.status(200).render('index', {
page_name: "index"
});
});
ile page_name: 'index' olarak alınır.
<li class="nav-item <%= page_name === 'index' && 'active' %>"><a class="nav-link" href="/">Home</a></li>
<li class="nav-item <%= page_name === 'about' && 'active' %>"><a class="nav-link" href="/about">About Us</a></li>
ile sayfa adı sınanır ve true ise active çıktısı verir.
ileri okuma için tıklayınız.
controllers/pageController.js oluşturuldu.
app.js router alanında router fonksiyonların içindeki req-res fonksiyonlar pageController.js içine taşınır ve buradan export edilir.
routes/pageRouter.js içine pageController require edilir. Bu kısımda route işlemi yapılacağından express modülü de çağırılır.
pageController.js:
const express = require('express')
const pageController = require('../controllers/pageController') // ile import işlemleri yapılır.
const router = express.Router() // ile Router() fonksiyonu değişkene atanır.
router.route('/').get(pageController.getIndexPage)
router.route('/about').get(pageController.getAboutPage) // ile route işlemleri tanımlanır.
module.exports = router
ile değişkene tanımlanan fonksiyon export edilir.
app.js içine router modülü çağırılır. router alanı aşağıdaki gibi düzenlenir:
app.use('/', pageRoute);
konsola:
npm i mongoose
app.js içine mongoose require edilir.
const mongoose = require('mongoose');
Database connect için: mongoose.connect('mongodb://localhost/smartedu-db').then(() => console.log('DB Connected!'));
Dinamik olmasını istediğimiz veriler için model oluşturulur.
Model adları genelde büyük yazılır.
models/Course.js oluşturuldu ve içine mongoose import edildi.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CourseSchema = new Schema({
name: {
type: String, // veri tipi string.
unique: true, // eşsiz
required: true, // doldurulması zorunlu.
},
description: {
type: String,
required: true,
trim: true, // Baştaki ve sondaki boşlukları görmezden gel.
}
createdAt: {
type: Date, // veri tipi tarih.
default: Date.now(), // varsayılan değer olarak işlemin yapıldığı tarihi alır.
},
});
ile şema oluşturulur.
const Course = mongoose.model('Course', CourseSchema); // ile model oluşturuldu
module.exports = Course; // ile export edildi.
controllers/courseController.js dosyası oluşturuldu. İçine:
exports.createCourse = async (req, res) => { // ile asenkron createCourse fonksiyonu export edilir.
try{ // işlemde hata varsa yakalaması için tüm işlem try-catch yapısına taşınır.
const course = await Course.create(req.body); // ile yeni kurs yazılır.
res.status(201) // ile status code 201-created gönderilir.
.json({
status: 'succest',
course
}) // diğer veriler hazır olmadığından şimdilik bu veri gönderilir.
} catch (error) {
res.status(400) // ile status code 400-bad request gönderilir.
.json({
status: 'fail',
error,
});
}
};
routes/courseRoute.js oluşturuldu. içine:
const express = require('express')
const courseController = require('../controllers/courseController')
const router = express.Router()
router.route('/').post(courseController.createCourse) // ile post request createCourse fonksiyonunu çalıştırır.
module.exports = router
app.js içine courseRoute import edilir. routes alanına:
app.use('/courses', courseRoute); eklendi.
Application Program Interface
API'ler, iki yazılım bileşeninin belirli tanımlar ve protokoller aracılığıyla birbiriyle iletişim kurmasına olanak tanıyan mekanizmalardır. Örneğin, meteoroloji müdürlüğünün yazılım sistemi, günlük hava durumu verilerini içerir. Telefonunuzdaki hava durumu uygulaması, API'ler aracılığıyla bu sistemle "konuşur" ve telefonunuzda size günlük hava durumu güncellemelerini gösterir.
Frontend hazır değilken backend tarafını test etmek için kullanılır. requesti simüle eder ve gelen response u gösterir.
postman.com/downloads/ -> download ve setup
uygulama içinden + ile yeni untitled request oluşturulur.
ilgili request seçilir ve gönderilecek url girilir.
ilgili veriler body alanına json olarak yazılır.
send ile gönderilir.
Verinin alınması için gelen url req.body üzerinden middleware ile işlenmeli. Bunun için courseController içine middleware olarak:
app.use(express.json()) // for parsing application/json
app.use(express.urlencoded({ extended: true })) // for parsing application/x-www-form-urlencoded
gelen response uygulamada görülür.
courses.ejs template engine göre düzenlenir. courses linki _navigation.ejs içinde güncellenir.
courseRoute.js içine:
router.route('/').get(courseController.getAllCourses); eklenir. Bu sayede "/courses" için get request yapılırsa courseController.getAllCourses çağırılır.
courseController.js içine:
exports.getAllCourses = async (req, res) => {
try {
const courses = await Course.find(); // ile veritabanından tüm kurslar alınır.
res.status(200).render('courses', {
courses,
page_name: 'courses'
}); // courses.ejs, courses bilgisi ve page_name bilgisi gönderilir.
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
courses.ejs içindeki kusrların gösterildiği taslaklardan biri bırakıldı. Kalanlar silindi. Bırakılan taslak aşağıdaki for döngüsü içine alındı.
<% for(let i=0; itaslağın name kısmı:... taslak ... <% } %>
<%= courses[i].name %>desctiption kısmı:
<%= courses[i].description.substring(0, 100) + "..." %>olarak güncellenir. Bu sayede get request ile alınan courses bilgisi kullanılmış olur. .substring(0, 100) metodu ile courses sayfasında description kısmında görünecek karakter sayısı sınırlandırılır.
course-single.ejs adı course.ejs yapılır ve template engine'e uygun düzenlenir.
courses.ejs içindeki link:
/courses/<%= courses[i]._id %>
_navigation.ejs, _header.ejs ve _footer.ejs içindeki lokal linklerin başına / eklenir ki ulaşılabilinsin.
courseRoute.js içinde:
router.route('/:id').get(courseController.getCourse); yönlendirmesi yapılır. /courses/_id olarak gelen get request courseController.getCourse fonksiyonuna yönlendirilir.
courseController.js içine:
exports.getCourse = async (req, res) => {
try {
const course = await Course.findById({_id: req.params.id}); // ile request olarak gelen id deki course bilgisi course değişkenine atanır.
res.status(200).render('course', {
course,
page_name: 'courses'
}); // ile course.ejs render edilir ve course değişkenindeki bilgi gönderilir.
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
course.ejs içinde uygun alanlara
<%= course.name %>ve
<%= course.description %>eklenir. Bu sayede course bilgisi sayfada dinamik olarak kullanılır
Tekil kurs sayfalarını _id parametersi ile çağırdığımızda bu ID url de 6095037e031db830c0a724ee benzer _id parametresinin görünmesine neden olur. Bunun yerinde daha anlamlı olan bir slug ifadesinin bulunmasını isteriz. Bunun için slugify paketinden faydalanacağız.
terminale:
npm i slugify
models/Course.js içine:
const slugify = require('slugify'); ile slugfy import edilir.
slug:{
type: String,
unique: true
}
ilave edilir.
CourseSchema.pre('validate', function(next){
this.slug = slugify(this.name, {
// ile isim slug a çevirilir. Bu işlem için arrow fonksiyon kullanılmaz çünkü arrowda this alınmaz.
lower: true, // ile hepsi küçük harfe dönüştürülür.
strict: true // ile stirng olmayan karakterler atılır.
});
next(); // ile fonksiyon bir sonrakine devam ettirilir.
})
Sayfayı slug üzerinden request etmek için courses.esj içindeki link:
/courses/<%= courses[i].slug %>olarak düzenlenir.
router.route('/:slug').get(courseController.getCourse); olarak düzenlenir.
exports.getCourse = async (req, res) => {
try {
const course = await Course.findOne({slug: req.params.slug});
....
}
...
}
olarak düzenlenir.
Kategori için model oluşturup, bu modeli course modeli ile bağlayacağız.
models/Category.js oluşturuldu. İçine: (Course.js model alındı.)
const mongoose = require('mongoose');
const slugify = require('slugify');
const Schema = mongoose.Schema;
const CategorySchema = new Schema({
name: {
type: String,
unique: true,
required: true,
},
slug: {
type: String,
unique: true,
},
});
CategorySchema.pre('validate', function (next) {
this.slug = slugify(this.name, {
lower: true,
strict: true,
});
next();
});
const Category = mongoose.model('Category', CategorySchema);
module.exports = Category;
Course.js CourseSchema içine:
category: {
type:mongoose.Schema.Types.ObjectId,
ref:'Category'
}
ilave edilerek course ile category modelleri ilişkilendirilir.
controllers/categoryController.js oluşturuldu. içine:
const Category = require('../models/Category')
exports.createCategory = async (req, res) => {
try {
const category = await Category.create(req.body);
res.status(201).json({
status: 'succest',
category,
});
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
ile create fonksiyonu oluşturuldu.
routes/courseRoute.js oluşturuldu. içine:
const express = require('express')
const categoryController = require('../controllers/categoryController')
const router = express.Router()
router.route('/').post(categoryController.createCategory);
module.exports = router
ile post request create fonksiyonuna yönlendirildi.
app.js içine: categoryRoute import edildi. router alanına:
app.use('/categories', categoryRoute); ilave edilir.
postman ile category verisi oluşturuldu. Sonra bu kategorilerle ilişkili course oluşturuldu.
Kategorileri courses sayfasında listelemek için courseController.js içine Category modeli import edilir. getAllCourses içine:
const categories = await Category.find();ile categories değişkenine database içindeki ilgili veri tanımlanır. render fonksiyonu içine eklenerek response edilir. Bu cevap aşağıdaki for döngüsü ile courses.ejs içinde yakalanır.
courses.ejs içinde ilgili alan:
<h3 class="widget-title">Categories</h3>
<ul>
<% for(let i=0; i<categories.length; i++){ %>
<li><a href="/courses?categories=<%= categories[i].slug %>"><%= categories[i].name %></a></li>
<% } %>
</ul>
olarak düzenlendi. link olarak verilen sorgu courseController.js içinde yakalanır ve sadece o kategorideki kursların gönderilmesini sağlar. Bunun için:
courseController.js içinde getAllCourses fonksiyonuna aşağıdaki ekleme yapılır.
const categorySlug = req.query.categories; // ile gönderilen query yakalanır.
const category = await Category.findOne({slug: categorySlug}) // ile databaseten ilgili Category bulunur.
let filter = {}
if(categorySlug){
filter = {category:category._id}
} // ile queryden veri gelme durumu sorgulanır. Gelmediyse filter değişkeni boş kalır. Değilse slug bilgisi ile databaseten çekilen verinin _id değeri category keyine value olarak atanır.
const courses = await Course.find(filter); // ifadesi güncellenir. if döngüsünden filter dolu çıkarsa buna göre göndereceği verileri filtreler.
İlk önce register sayfasını oluşturacağız. Buradaki tek amacımız sayfayı görebilmek olduğu için işlemleri pageController.js ve pageRoute.js ile yapacağız.
pageController.js içine:
exports.getRegisterPage = (req, res) => {
res.status(200).render('register', {
page_name: "register"
});
}
pageRoute.js içine:
router.route('/register').get(pageController.getRegisterPage)
register.html register.ejs ye dönüştürülür ve template engine'e göre düzenlenir. _navigation.ejs'deki link düzenlenir:
<li><a class="hover-btn-new log <%= page_name === 'register' && 'orange' %>" href="/register"><span><i class="fa fa-user-plus" aria-hidden="true"></i></span></a></li>
models/User.js dosyası oluşturulur. İçine:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
}
});
const User = mongoose.model('User', UserSchema);
module.exports = User;
controller/authControler.js dosyası oluşturulur. İçine:
const User = require('../models/User')
exports.createUser = async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).json({
status: 'succest',
user,
});
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
routes/userRoute.js oluşturulur. İçine:
const express = require('express')
const authController = require('../controllers/authController')
const router = express.Router()
router.route('/singup').post(authController.createUser);
module.exports = router
app.js içine Routes alanına:
app.use('/users', userRoute); eklendi.
register.ejs form alanı aşağıdaki gibi düzenlendi.
<form method="POST" action="users/singup">
Database'e gönderilen password bilgisini şifrelenmesini sağlar.
terminale:
npm i bcrypt
models/User.ejs içine bcrypt import edilir. new Schema fonksiyonunu altına aşadaki middleware eklenir.
UserSchema.pre('save', function(next){
const user = this;
bcrypt.hash(user.password, 10, (error, hash) => {
user.password = hash;
next();
})
})
Bu konuyla ilgili ileride karşılaşacağımız hata ve çözümü için tıklayın
pageController.js içine:
exports.getLoginPage = (req, res) => {
res.status(200).render('login', {
page_name: "login"
});
}
login.html, login.ejs ye dönüştürülür ve template engine'e göre düzenlenir.
_navigation.ejs içinde ilgili kısım:
<li><a class="hover-btn-new log <%= page_name === 'login' && 'orange' %> mr-2" href="/login"><span><i class="fa fa-sign-in" aria-hidden="true"></i></span></a></li>
olarak düzenlenir.
pageRoute.js içine:
router.route('/login').get(pageController.getLoginPage); eklenir.
authController.js içine:
const bcrypt = require('bcrypt'); // ile bcrypt import edilir.
exports.loginUser = async (req, res) => {
try {
const {email, password} = req.body; // ile formdan email ve password bilgisi alınır.
let user = await User.findOne({email: email}) // ile email bilgisi ile db üzerinde user olma durumu kontrol edilir.
if(user){
bcrypt.compare(password, user.password, (err, same) =>{ // user var ise password bilgisinin eşleşmesi kontrol edilir.
if(same){
// USER SESSION
res.status(200).send("you are logged in") // password bilgisi eşleşiyorsa "you are logged in" bilgisi sayfaya gönderilir. Daha sonra buraya başka işlem gelecek.
}
})
}
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
authController.js için bilgi göndermesi için login.ejs içindeki form ve userRoute.js düzenlenir.
login.ejs:
<form method="POST" action="users/login">olarak güncellenir.
router.route('/login').post(authController.loginUser); eklenir.
Session kavramı Türkçeye oturum olarak çevrilir ve kişiye özel içerik oluşturmak için kullanıcı bilgilerinin sunucu tarafında saklanmasını sağlayan araçlardır. Node.js tarafında express-session paketini kullanacağız.
session datası sunucuda saklanır. sadece session id cookie içinde saklanır.
terminale:
npm i express-session
app.js içine import edilir.
var session = require('express-session');
app.use(session({
secret: 'keyboard_cat_rambo',
resave: false,
saveUninitialized: true,
}))
yazılır.
session bilgisini yakalamak için authController.js loginUser fonksiyonundaki //USER SESSION kısmına:
req.session.userID = user._ideklenir.
res.status(200).redirect('/'); olarak index sayfasına yönlendirebilir. console.log(req.session.userID) eklenebilir.
Bazı bilgilerin loginden sonra bazılarının da loginden önce görünmesini isteriz. Bunun için:
app.js içine // Global Variable alanı oluşturup (middleware alanının üstüne) içine:
global.userIN = null;yazılır. null değeri if içinde false verir.
app.use('*', (req, res, next)=>{
userIN = req.session.userID;
next();
})
girilir. Bu sayede her durumda userIN, req.session.userID değeri varsa alır. yoksa yine null kalır. herhangi bir response olmadığından next() ile sonraki fonksiyona geçilir.
_navigation.ejs içinde login ve register butonlarının sadece userIN false iken çıkması için:
<% if(!userIN){ %>
<ul class="nav navbar-nav navbar-right">
<li><a class="hover-btn-new log <%= page_name === 'login' && 'orange' %> mr-2" href="/login"><span><i class="fa fa-sign-in" aria-hidden="true"></i></span></a></li>
<li><a class="hover-btn-new log <%= page_name === 'register' && 'orange' %>" href="/register"><span><i class="fa fa-user-plus" aria-hidden="true"></i></span></a></li>
</ul>
<% } %>
userIN true iken (kullanıcı login yaptığında) logout butonunu görünmesi için:
<% if(userIN){ %>
<ul class="nav navbar-nav navbar-right">
<li><a class="hover-btn-new log <%= page_name === 'login' && 'orange' %> mr-2" href="/login"><span><i class="fa fa-sign-out" aria-hidden="true"></i></span></a></li>
</ul>
<% } %>
Aynı durum dashboard linki için kullanıcı varsa gösterilecek şekilde uyarlanır:
<% if(userIN){ %>
<li class="nav-item"><a class="nav-link" href="dashboard.html">Dashboard</a></li>
<% } %>
authController.js içine:
exports.logoutUser = (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
};
router.route('/logout').get(authController.logoutUser)
<ul class="nav navbar-nav navbar-right">
<li>
<a class="hover-btn-new log mr-2" href="/users/logout">
<span><i class="fa fa-sign-out" aria-hidden="true"></i></span>
</a>
</li>
</ul>
Burada şöyle bir sorunumuz var, sunucuyu tekrar başlattığımızda ilgili session yani oturumu kaybediyoruz. Bunun engellemek için connect-mongo paketini indireceğiz. Bu sayede session bilgilerini mongoDB üzerinde saklayabilyoruz. Sonrasında ise bu session bilgisini kaydedeceğimiz veritabanı bağlantısını yazacağız.
terminale:
npm i connect-mongo
app.js içine import için:
const MongoStore = require('connect-mongo');
app.js middleware alanına: app.use(session({ içine
store: MongoStore.create({ mongoUrl: 'mongodb://localhost/smartedu-db' }) parametre olarak eklenir.
Bu işlemden sonra sunucu bağlantısı kopup yeniden de bağlansa kullanıcı login ise login kalır.
dashboard.esj dosyası template engine'e göre düzenlenir.
işlem login işlemi ile yapılacağından işlemleri login işlemine uyumlu dosyalara yerleştirilecek.
authController.js içine:
exports.getDashboardPage = (req, res) => {
res.status(200).render('dashboard', {
page_name: "dashboard"
});
}
_navigation içinden dashboard linki: "/users/dashboard" olarak güncellenir ve class active bilgisi için düzenleme yapılır.
<li class="nav-item <%= page_name === 'dashboard' && 'active' %>"><a class="nav-link" href="/users/dashboard">Dashboard</a></li>
userRoute.js içine:
router.route('/dashboard').get(authController.getDashboardPage);
authController.js içindeki loginUser fonksiyonunun redirect adresi '/users/dashboard' olarak güncellendi.
dashboard sayfasına user bilgisini göndermek için authController.js içindeki getDashboardPage fonksiyonu aşağıdaki gibi güncellenir.
exports.getDashboardPage = async(req, res) => {
const user = await User.findOne({_id: req.session.userID})
res.status(200).render('dashboard', {
page_name: "dashboard",
user: user
});
}
dashboard.ejs içine ilgili yere
<%= user.name %>eklenerek bilgi sayfaya yerleştirilir.
login ve logout durumuna göre görünmesini istemediğimiz sayfaların linklerinin başka sayfaya redirect yapması için middleware yazacağız.
/users/dashboard
middleware/authMiddleware.js dosyası oluşturulur. İçine:
const User = require('../models/User');
module.exports = (req, res, next) => { // ile tüm içerik export edilir.
User.findById(req.session.userID, (err, user) => { // ile user sorgulanır.
if (err || !user) return res.redirect('/login'); // user yoksa '/login' sayfasına yönledirilir.
next(); // ile if geçersizse sonraki fonksiyona geçilir.
});
};
userRoute.js içine authMiddleware import edilir.
const authMiddleware = require('../middlewares/authMiddleware');
userRoute.js dashboard route fonksiyonu aşağıdaki gibi güncellenir.
router.route('/dashboard').get(authMiddleware, authController.getDashboardPage); ilk fonksiyon "authMiddleware" next alırsa (if geçersizse) işlem sonrakine geçer. Yoksa authMiddleware içindeki res.redirect('/login'); çalışır.
/login
/register
middleware/redirectMiddleware.js dosyası oluşturulur. İçine:
module.exports = (req, res, next) => {
if (req.session.userID) return res.redirect('/');
next();
};
pageRoute.js içine redirectMiddleware import edilir.
const redirectMiddleware = require('../middlewares/redirectMiddleware');
pageRoute.js içinde güncelleme yapıldı.
router.route('/register').get(redirectMiddleware ,pageController.getRegisterPage);
router.route('/login').get(redirectMiddleware ,pageController.getLoginPage);
Önce models/user.js içine role bilgisi eklenir.
role: {
type: String,
enum: ["student", "teacher", "admin"],
defult: "student",
},
Bilginin alınması için register.ejs form içine:
<div class="offset-1 col-lg-10 col-md-10 col-sm-10">
<select class="form-control" name="role" id="">
<option>student</option>
<option>teacher</option>
</select>
</div>
middlewares/roleMiddleware.js içine: br
module.exports = (roles) => { // fonksiyonun kullanıldığı yerde argüman olarak bu parametre (roles) array şeklinde kullanılacak.
return (req, res, next) => {
const userRole = req.body.role;
if(roles.includes(userRole)) {
next();
} else {
return res.status(401).send('YOU CANT DO THIS');
}
}
}
courseRoute createCourse yönlendirmesine admin veya teacher olma şartı konulur:
router.route('/').post(roleMiddleware(["teacher", "admin"]), courseController.createCourse);
register ekranında kayıt sonrası login sayfasına yönlendirmesi için, authController.js içinde createUser respose değeri aşağıdaki gibi güncellenir.
exports.createUser = async (req, res) => {
try {
const user = await User.create(req.body);
res.status(201).redirect('/login')
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
user.role bilgisine göre /dashboard içinde içeriği ayarlamak için if(user.role === '') kalıbı kullanılabilir.
<% if (user.role === 'student'){ %>
<div class="all-title-box">
<div class="container text-center">
<h1><%= user.name %><span class="m_1">STUDENT</span></h1>
</div>
</div>
<% } %>
<% if (user.role === 'teacher'){ %>
<div class="all-title-box">
<div class="container text-center">
<h1><%= user.name %><span class="m_1">Teacher</span></h1>
</div>
</div>
<% } %>
dashboard.ejs if (user.role === 'student') iken görünecek kısmı yukarıda, if (user.role === 'teacher') iken görünecek kısmı aşağıda olacak şekilde tekrar düzenlenir.
dashboard.ejs teacher kısmına buton eklenir:
<div class="col-lg-12 text-center ">
<button class="btn btn-lg btn-warning rounded-0 text-white" href="#" data-toggle="modal" data-target="#addCourse"><span>CREATE NEW COURSE</span></button>
</div>
Modal içinde daha önceden oluşturduğumuz kategorileri görebilmek için authController.ejs içine Categories import edilir. dashboard içine göndermek için getDashboardPage fonksiyonuna tüm categories çağırılır ve render kısmına eklenerek gönderilir.
exports.getDashboardPage = async(req, res) => {
const user = await User.findOne({_id: req.session.userID})
const categories = await Category.find()
res.status(200).render('dashboard', {
page_name: "dashboard",
user: user,
categories: categories
});
}
<select class="form-control" name="category">
<u>
<% for (let i=0; i< categories.length; i++) { %>
<option value="<%= categories[i]._id %>"><%= categories[i].name %></option>
<% } %>
</u>
</select>
formu post ederken form tagı içinde:
method="POST" action="/coursesgirilmeli
createCourse route aşamasında (courseRoute.ejs içinde) user.role sorgular. Bunu da form içinde göndermemiz gerekir. bunun için form alanının içine:
<div class="form-group">
<div class="col-sm-12">
<input type="hidden" name="role" class="form-control" value="<%= user.role %>">
</div>
</div>
Kurs yaratıldığında courses sayfasına yönlendirmesi için ilgili redirect createCourse fonksiyonunda düzenlenir:
exports.createCourse = async (req, res) => {
try {
const course = await Course.create(req.body);
res.status(201).redirect('/courses');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
Oluşturulan kursların /courses sayfasında son oluşturulanın en üstte görünmesi için courseController.js içinde getAllCourses fonksiyonudaki const courses = await Course.find(filter) ibaresine ".sort('-createdAt');" eklenir.
const courses = await Course.find(filter).sort('-createdAt');
dashboard.ejs içinden görülebilir.
Course.js modeline
user: {
type:mongoose.Schema.Types.ObjectId,
ref:'User'
}
eklenir.
courseController.ejs içinde createCourse içinde mevcut user yakalanır:
const course = await Course.create({
name: req.body.name,
description: req.body.description,
category: req.body.category,
user: req.session.userID
});
user bilgisine uygun olan coursesi dashboarda eklemek için authController.ejs getDashboardPage fonksiyonuna courses aktif kullanıcıya göre filtrelenerek tanımlanır:
const courses = await Course.find({user: req.session.userID})
res.status(200).render('dashboard', {
page_name: "dashboard",
user: user,
categories: categories,
courses: courses
});
dashboard.ejs içinde courses bilgisi for döngüsü ile yakalanır ve her course için name ve description ilgili alanlara konulur.
<% for(let i=0; i<courses.length; i++){ %>
...
<%= courses[i].name %>
...
<%= courses[i].description %>
...
<% } %>
Tekli kurs sayfasında course.user bilgisinden user.name alabilmek için courseController.ejs içinde getCourse fonksiyonunda aşağıdaki güncelleme yapılır:
const course = await Course.findOne({ slug: req.params.slug }).populate('user'); <%= course.user.name %>
Öğrencinin kurslara kayıt olası, kendi dashboard sayfasında katıldığı kursları görmesi ve yönetmesi işlemleri
User.js içinde UserSchema alanına aşağıdaki course bilgisi eklenir:
courses: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Course'
}]
courses array olarak oluşturuldu. Course.js modeli ile bağlandı.
course.ejs içine aşağıdaki form eklendi:
<form method="POST" action="/courses/enroll">
<input type="hidden" name="course_id" value="<%= course._id %>">
<button class="btn btn-large btn-warning text-white"><span>ENROLL</span></button>
</form>
courseController.js içinde aşağıdaki fonksiyon yazıldı:
exports.enrollCourse = async (req, res) => {
try {
const user = await User.findById(req.session.userID);// ile şu an aktif olan user bilgisi alındı.
await user.courses.push({_id: req.body.course_id}); // ile tıklanılan sayfanın course_id bilgisi user.courses kısmına eklendi.
await user.save(); // ile user için yapılan değişiklik kaydedildi.
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
courseRoute.j içine aşağıdaki kod eklenerek course.ejs ile ourseController.enrollCourse fonksiyonu bağlandı.
router.route('/enroll').post(courseController.enrollCourse);
user'a kayıtlı courses bilgisini almak için authController.js içindeki getDashboardPage fonksiyonunun ilgili kısmına .populate('courses'); eklenir.
exports.getDashboardPage = async(req, res) => {
const user = await User.findOne({_id: req.session.userID}).populate('courses');
...
dashboard.ejs student kısmında aşağıdaki for döngüsü yazılır ve ilgili bilgiler ilgili kısımlara girilir.
<% for(let i=0; i... ...<%= user.courses[i].name %>... ...<%= user.courses[i].slug %>... ...<%= user.courses[i].description %>... ... <% } %>
gelişim sürecinde modellerimiz üzerinde de değişiklik yaptık. Database üzerine eski tarihli gönderdiğimiz verilerde bu bilgiler eksik kaldı. Bu nedenle categories alanı hariç database i temizledik. Yenilerini oluşturduk.
dashboard student kısmında description kısmının altına aşağıdaki buton eklenir:
<form method="POST" action="/courses/release">
<input type="hidden" name="course_id" value="<%= user.courses[i]._id %>">
<button class="btn btn-large btn-danger text-white"><span>RELEASE</span></button>
</form>
courseController.js içine releaseCourse fonksiyonu yazılır.
exports.releaseCourse = async (req, res) => {
try {
const user = await User.findById(req.session.userID);
await user.courses.pull({_id: req.body.course_id});
await user.save();
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
courseRoute içinde aşağıdaki yönlendirme ile buton ile fonksiyon bağlanır.
router.route('/release').post(courseController.releaseCourse);
courseController.js getCourse içinde user bilgisi göndermek için:
exports.getCourse = async (req, res) => {
try {
const user = await User.findById(req.session.userID);
...
yazılır ve user render içinde gönderilir.
course.ejs içindeki enroll butonu if bloğuna alınır:
<% if(user && !user.courses.includes(course._id)) { %>
...ENROLL...
<% } %>
user bilgisi hiç yoksa veya user.courses içinde olduğumuz course için _id içeriyorsa buton görünmez.
courses.ejs search alanı aşağıdaki gibi düzenlenir:
<div class="widget-search">
<div class="site-search-area">
<form method="GET" id="site-searchform" action="/courses">
<div>
<input class="input-text form-control" name="search" id="search-k" placeholder="Search..." type="text">
<button id="searchsubmit" value="Search" type="submit"></button>
</div>
</form>
</div>
</div>
Buradan gelen bilgi courseRoute.js üzerinden courseController.getAllCourses fonksiyonuna aktarılır.
Bilginin filtrelemede kullanılması için fonksiyon aşağıdaki hale güncellenir.
exports.getAllCourses = async (req, res) => {
try {
const categorySlug = req.query.categories;
const query = req.query.search; // ile search alanındaki bilgi alınır.
const category = await Category.findOne({slug: categorySlug})
let filter = {}
if(categorySlug){
filter = {category:category._id}
}
if(query) { // query varsa
filter = {name:query} // filter.name olarak atanır.
}
if(!query && !categorySlug){
filter.name = ""
filter.category = null
}
const courses = await Course.find({
$or:[ // bu ifade mongodb için veya demek.
{name: {$regex: '.*' + filter.name + '.*', $options: 'i'}}, // ile name başı ve sonu farketmeksizin filter.name ifadesini içerenleri sorgular.
{category: filter.category}
]
}).sort('-createdAt');
const categories = await Category.find();
res.status(200).render('courses', {
courses,
categories,
page_name: 'courses',
});
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
courses.ejs içindeki search alanı aynı şekilde course.ejs için de kullanılır.
courses.ejs içindeki Categories alanı aynı şekilde course.ejs için de kullanılır. Çalışması için courseController.getCourse içinde
const categories = await Category.find();eklenir ve render içinde categories gönderilir.
courses sayfasında kursu oluşturan teacher kullanıcının adını görmek için courseController.getAllCourses fonksiyonundaki ilgili kısım aşağıdaki gibi güncellenir.
const courses = await Course.find({
$or:[
{name: {$regex: '.*' + filter.name + '.*', $options: 'i'}},
{category: filter.category}
]
}).sort('-createdAt').populate('user');
.populate('user') ile courses ile gönderilen user bilgisini almak için courses.ejs içindeki ilgili alana:
<%= courses[i].user.name %> eklenir.
contact.html contact.ejs ye dönüştürülür.
contact.ejs template engine'e göre düzenlenir.
_navigation.ejs içinde ilgili linkler düzenlenir.
<li class="nav-item <%= page_name === 'contact' && 'active' %>"><a class="nav-link" href="/contact">Contact</a></li>
pageRoute.js içinde ilgili yönlendirme yapıldı.
router.route('/contact').get(pageController.getContactPage);
pageController.js içinde getContactPage fonksiyonu yazılır.
exports.getContactPage = (req, res) => {
res.status(200).render('contact', {
page_name: "contact"
});
}
contact.ejs içindeki form isteğe göre düzenlenir. Bize name, email ve message yeterli. Form ayarlarını da aşağıdaki gibi düzenleriz:
<form id="contactform" action="/contact" method="POST">
.....
.....
</form>
formdan gelen bilgiyi almak için (şimdilik konsola yazdıracağız) pageController.js içinde sendEmail fonksiyonu yazılır.
exports.sendEmail = (req, res) => {
console.log(req.body);
}
formdan gelen bilgi routeController.js içinden ilgili fonksiyona yönlendirilir.
router.route('/contact').post(pageController.sendEmail);
Bunun için ücretli servisler var. Biz bu uygulamada gmailin ücretsiz hizmetinden faydalanacağız. Bunun için kendi mail adresimizi kullanacağız.
Mail göndermek için önce nodemailer adında bir node modülü kullanacağız.
terminale:
npm i nodemailer
modül pageController.js çine import edilir.
const nodemailer = require("nodemailer");
pageController.sendEmail fonksiyonu aşağıdaki gibi düzenlenir.
exports.sendEmail = async(req, res) => {
const outputMessage = `
<h1>Mail Details</h1>
<ul>
<li>Name: ${req.body.name}</li>
<li>Email: ${req.body.email}</li>
</ul>
<h1>Message</h1>
<p>${req.body.message}</p>
`; // bu kısım mailimizin gövdesi olacak.
let transporter = nodemailer.createTransport({
host: "smtp.gmail.com", // gmaile yönlendiriyoruz.
port: 465, // portu 465 e ayarlıyoruz.
secure: true, // true for 465, false for other ports
auth: {
user: "drmuratgokduman@gmail.com", // gmail accont: maili gönderecek olan adres
pass: "eqocuuyuqcrxxyhc" // gmail password yerine google account içinde güvenlik kısmında uygulama şifrelerine girilir ve uygulama şifresi oluşturulur. uygulama: posta cihaz: windows bilgisayar
},
});
let info = await transporter.sendMail({
from: '"Smart Edu Contact Form" <drmuratgokduman@gmail.com>', // gönderen adres
to: "drmuratgokduman@gmail.com", // alan adres
subject: "Smart Edu Contact Form New Message ✔", // konu
html: outputMessage, // mesajın gövdesi: yukarıda oluşturmuştuk
});
console.log("Message sent: %s", info.messageId);
console.log("Preview URL: %s", nodemailer.getTestMessageUrl(info));
res.status(200).redirect('contact');
}
nodemailer anasayfasında konuyla ilgili başka mail opsiyonları da var (cc, bcc, dosya eki vs)
Projemizde herhangi bir şekilde bir değişiklik yaptığımızda, yeni bir kurs oluşturduğumuzda veya mail gönderimi gibi işlemler sonucunda kullanıcıya geri bildirimde bulunmak için kullanılır.
Bu işlem için connect-flash modülü kullanılır.
terminale:
npm i connect-flash
app.js içine import edilir.
const flash = require('connect-flash');
app.js middleware alanına:
app.use(flash());
flash dan gelen mesajı lokalde bir değişkene kaydetmek için middleware alanına:
app.use((req, res, next)=>{
res.locals.flashMessages = req.flash();
next();
})
pageController.sendMail fonksiyonunun içine en sondaki redirect komutundan önce:
req.flash("success", "We received your message succesfuly"); eklenir.
Başarısız işlemlerde hata mesajı yazdırabilmek için pageController.sendMail fonksiyonunun içeriği try catch bloğuna alınır. catch kısmına:
req.flash("error", "Something happened!");
res.status(200).redirect('contact');
eklenir.
Mesajı contact.ejs içinde görmek için istenilen yere:
<% if(flashMessages){ %>
<% if(flashMessages.success){ %>
<div class="alert alert-success">
<%= flashMessages.success %>
</div>
<% } else if(flashMessages.error){ %>
<div class="alert alert-danger">
<%= flashMessages.error %>
</div>
<% } %>
<% } %>
Her şey normalken hata alıyorsan ağı kontrol et. şirket ağı bazı fonksiyonları kısıtlıyor.
courseController.courseCreate fonksiyonunda try bloğunun en altındaki redirect fonksiyonundan önce:
req.flash('success', `${course.name} Has Been Created Succesfuly`); eklenir.
req.flash('error', `Something Happened!`);
res.status(400).redirect('/courses');
flash ile gelen bilgiyi yakalamak için courses.ejs içine mail gönderiminde eklediğimiz kodu kullanabiliriz. Bildirimi görmek istediğimiz yere ekleriz.
Doldurulması zorunlu alanlar için html input kodlarına öz nitelik olarak required eklenebilir.
Biz bu çalışmada express-validator modülü ile form kontrolü yapacağız.
terminale:
npm i express-validator
userRoute.js içine import edilir:
const { body } = require('express-validator'); bize bu kısımda sadece body fonksiyonu gerekecek.
userRoute.js içindeki singup post request routeu aşağıdaki gibi düzenlenir.
router.route('/singup').post(
[
body('name').not().isEmpty().withMessage('Please Enter Your Name'), // bu kısımda formdan gelen name parametresi boş ise "Please Enter Your Name" mesajı gönderilir.
],
authController.createUser
);
Gönderilen hata mesajı authControler.createUser catch tarafında yakalanır. Bunun için önce express-validator import edilir.
const { validationResult } = require('express-validator');
const errors = validationResult(req);
console.log(errors);
konsolda bir array içinde bir obje yazdırılır. bize gereken hata mesajı için: console.log(errors.array()[0].msg);
Aldığımız hata mesajını bir önceki konudaki flash metodu ile ilgili template'e göndereceğiz
bunun için catch bloğunun en altına:
req.flash('error', errors.array()[0].msg);
res.status(400).redirect('/register')
eklenir.
Gönderilen mesaj register.ejs içinde istenilen yere daha önceki kodun aynısı olarak girilir:
<% if(flashMessages){ %>
<% if(flashMessages.success){ %>
<div class="alert alert-success">
<%= flashMessages.success %>
</div>
<% } else if(flashMessages.error){ %>
<div class="alert alert-danger">
<%= flashMessages.error %>
</div>
<% } %>
<% } %>
userRoute.js içine:
const User = require('../models/User');
userRoute.js içindeki singup post request routeu diğer inputlardan gelen veriyi de değerlendirmesi için aşağıdaki gibi güncellenir.
router.route('/singup').post(
[
body('name').not().isEmpty().withMessage('Please Enter Your Name'),
body('email').isEmail().withMessage('Please Enter Valid Email') // bu kısım mail adresi değil ise mesaj gönder.
.custom((userEmail)=> { // bu kısımdaki sınamayı kendimiz yazdık. Mail adresinin databasete zaten olması durumunda hata mesajı gönderir. Sınamanın gerçekleşebilmesi için User modeli import edilir
return User.findOne({email: userEmail}).then(user => {
return Promise.reject('Email is already exist')
})
}),
body('password').not().isEmpty().withMessage('Please Enter Your Password'), // password kısmı boş ise hata mesajı verir.
],
authController.createUser
);
Gelen birden fazla mesajın hepsinin yazılabilmesi için authController.createUser catch blogu içine for döngüsü yazılır.
} catch (error) {
const errors = validationResult(req);
for (let i=0; i<errors.array().length; i++){
req.flash('error', errors.array()[i].msg);
}
res.status(400).redirect('/register')
}
Bu kısımda emaile sahip user olma durumu ve password kontrol authController.loginUser fonksiyonunda zaten olduğu için userRoute içinde tekrar kontrol etmeye gerek yoktur.
if(user) bloğunun else bloğuna:
req.flash('error', 'User Is Not Exist');
res.status(200).redirect('/login');
yazılarak user yoksa alınacak mesajı gönderir.
if(user) içindeki if(same) bloğunun else bloğu içine:
req.flash('error', 'Your Password Is Not Correct!!');
res.status(200).redirect('/login');
yazılarak password doğru değilse alınacak mesajı gönderir.
login.esj içinde istenilen yerde bu mesajlar aşağıdaki kod ile yakalanır.
<% if(flashMessages){ %>
<% if(flashMessages.success){ %>
<div class="alert alert-success">
<%= flashMessages.success %>
</div>
<% } else if(flashMessages.error){ %>
<div class="alert alert-danger">
<%= flashMessages.error %>
</div>
<% } %>
<% } %>
validation için detaylı okuma için github.com/validatorjs ve express-validator.github.io/docs
student içine course enroll edildiğinde database üzerindeki password verisi değişiyor.
Bu durumu çözmek için google üzerinde arama yapıyoruz:
how to prevent refreshing password mongoose
Bulunan stackoverflow linki.
Linkteki cevabın referans verdiği doküman
stackoverflow da cevap olarak verilen kod null ifadesi silindikten ve userSchema UserSchema
olarak kodumuzla uyumlu hale getirildiğinde işimizi görüyor.
UserSchema.pre('save', function(next) {
const user = this;
if (!user.isModified('password')) return next();
bcrypt.genSalt(10, function(err, salt) {
if (err) return next(err);
bcrypt.hash(user.password, salt, function(err, hash) {
if (err) return next(err);
user.password = hash;
next();
});
});
});
Kendi kodumuzu incelediğimizde her save işleminden önce password bilgisinin tekrar hash edildiğini görüyoruz. Bunu engellemek için koda bir if koşulu eklememiz yeterli olacaktır.
UserSchema.pre('save', function (next) {
const user = this;
if(!user.isModified('password')) return next();
bcrypt.hash(user.password, 10, (error, hash) => {
user.password = hash;
next();
});
});
delete ve update butonlarını eklemek için dashboard.ejs içinde teacher kısmındaki kurs kartlarına aşağıdaki kod eklenir:
<div class="clearfix">
<ul style="list-style-type: none;">
<li style="float: left;">
<button class="btn btn-primary rounded-0 text-white"><span>UPDATE</span></button>
</li>
<li style="float: right;">
<button class="btn btn-danger rounded-0 text-white"><span>DELETE</span></button>
</li>
</ul>
</div>
delete ve update işlemleri için method-override paketi indirilir. Terminale:
npm i method-override
app.js içine method-override import edilir.
const methodOverride = require('method-override')
app.use(
methodOverride('_method', {
methods: ['POST', 'GET'],
})
);
dashboard.ejs delete butonu aşağıdaki gibi modifiye edilir:
<li style="float: right;"><a
href="/courses/<%= courses[i].slug %>?_method=DELETE"
onclick="return confirm('ARE YOU SURE?')"
class="btn btn-danger rounded-0 text-white"><span>DELETE</span></a></li>
courseController.js içine
exports.deleteCourse = async (req, res) => {
try {
const course = await Course.findOneAndRemove({slug:req.params.slug})
req.flash('error', `${course.name} Has Been Remote Succesfuly`);
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
fonksiyon ile komutu bağlamak için courseRoute.js içinde
router.route('/:slug').delete(courseController.deleteCourse);
Gönderilen flash mesajı dashboard.ejs içinde istenilen yere eklenir.
Bir önceki çalışmada method-override paketini kurmuştuk. Bunun üzerinden devam edeceğiz.
update butonuna tıkladığımızda bir modal açılacak ve içinde tıkladığımız kursun bilgileri yazacak.
CREATE NEW COURSE için kullandığımız ve sayfanın en altına yazdığımız modalı kopyalayıp kurs listelemek için kullandığımız for döngüsünün en altına yapıştırıyoruz. Bu sayede modal ilgili kursun bilgilerini alabiliyor.
dashboard.ejs içinde UPDATE butonu aşağıdaki gibi güncellenir:
<li style="float: left;">
<button class="btn btn-primary rounded-0 text-white"
data-toggle="modal" data-target="#updateCourse<%= courses[i]._id %>" // bu kısım modal için gerekli. data-target ile modal id aynı olmalı.
>
<span>UPDATE</span>
</button>
</li>
modal içindeki değişiklikler:
<div class="modal fade" id="updateCourse<%= courses[i]._id %>" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
...
<form method="POST" action="/courses/<%= courses[i].slug %>?_method=PUT" class="form-horizontal">
...
<input type="text" name="name" value="<%= courses[i].name %>" class="form-control" placeholder="Course Name">
...
<textarea rows="8" name="description" class="form-control" placeholder="Course Description" required><%= courses[i].description %></textarea>
...
...
courseController.js içine:
exports.updateCourse = async (req, res) => {
try {
const course = await Course.findOne({slug:req.params.slug})
course.name = req.body.name;
course.description = req.body.description;
course.category = req.body.category;
course.save();
req.flash('success', `${course.name} Has Been Update Succesfuly`);
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
routeController.js içine:
router.route('/:slug').put(courseController.updateCourse); eklenir.
Bu çalışmada ayrı admin alanı oluşturmak yerine dashboard alanını kullanacağız.
Admin kullanıcısını student olarak yaratıp database üzerinden manuel admin yaptık.
dashboard.ejs student alanını kopyalayıp teacher alanının altına yapıştırdık. ve user.role === 'admin' olarak güncelledik.
for döngüsünü sildik ve yerine bu tamplate için kullanılan bootstrap yapısına uygun bir tablo örneği yapıştırdık.
Tüm user bilgisini dashboard.ejs de kullanabilmek için authController.getDashboardPage fonksiyonuna:
const users = await User.find()ile tüm kurslar çağırılır ve render alanında gönderilen verilere
users
eklenir.
users bilgisi tablo içinde yakalanır:
<table class="table">
<thead>
<tr>
<th scope="col">ID#</th>
<th scope="col">Email</th>
<th scope="col">Role</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<% for(let i=0; i<users.length; i++){ %>
<tr>
<th scope="row"><%= users[i]._id %></th>
<td><%= users[i].email %></td>
<td><%= users[i].role %></td>
<td>DELETE</td> <!-- DELETE butonu daha sonra aktif hale getirilecek -->
</tr>
<% } %>
</tbody>
</table>
DELETE butonu için:
<td>
<a
href="/users/<%= users[i]._id %>?_method=DELETE"
onclick="return confirm('ARE YOU SURE?')"
class="btn btn-danger rounded-0 text-white">
<span>DELETE</span>
</a>
</td>
authController.js içine:
exports.deleteUser = async (req, res) => {
try {
await User.findByIdAndRemove(req.params.id);
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
userRoute.js içine:
router.route('/:id').delete(authController.deleteUser);
role: teacher olan bir kullanıcıyı kaldırdığımızda, onun oluşturduğu kurslar kalmaya devam eder ve hata alırız. Bu nedenle teacher kaldırıldığında ona bağlı kurslar da kaldırılsın isteriz. Bu nedenle authController.deleteUser fonksiyonunu aşağıdaki gibi modifiye ederiz.
exports.deleteUser = async (req, res) => {
try {
await User.findByIdAndRemove(req.params.id);
await Course.deleteMany({ user: req.params.id });
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
dashboard.ejs admin alanındaki tablonun altına kendi kopyasını oluşturup modifiye edip üzerinde çalışacağız.
<div class="row mt-5">
<h2>Categories</h2>
<table class="table">
<thead>
<tr>
<th scope="col">ID#</th>
<th scope="col">Categories</th>
<th scope="col">Add</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<% for(let i=0; i<categories.length; i++){ %>
<tr>
<th scope="row"><%= categories[i]._id %></th>
<td><%= categories[i].name %></td>
<td>ADD</td> <!-- add butonu daha sonra düzenlenecek -->
<td>
<a
href="/categories/<%= categories[i]._id %>?_method=DELETE"
onclick="return confirm('ARE YOU SURE?')"
class="btn btn-danger rounded-0 text-white">
<span>DELETE</span>
</a>
</td>
</tr>
<% } %>
</tbody>
</table>
</div><!-- end row -->
categoryController içine:
exports.deleteCategory = async (req, res) => {
try {
await Category.findByIdAndRemove(req.params.id);
res.status(200).redirect('/users/dashboard');
} catch (error) {
res.status(400).json({
status: 'fail',
error,
});
}
};
fonksiyon ile isteği bağdaştırmak için categoryRoute.js içine:
router.route('/:id').delete(categoryController.deleteCategory);
add butonu:
<button class="btn btn-warning rounded-0 text-white" href="#" data-toggle="modal" data-target="#addCategory"><span>ADD</span></button>
categoryController.createCourse fonksiyonunu daha önce yazmıştık. Sadece
res.status(201).redirect('/users/dashboard'); yönlendirmesi eklendi.
ilgili route daha önce yazılmıştı.
pageController.getIndexPage fonksiyonu içinde anasayfada kullanmak için bazı bilgiler göndereceğiz.
exports.getIndexPage = async (req, res) => {
const courses = await Course.find().sort('-createdAt').limit(2); // kursları oluşturulma sırasına göre tersten sıralıyoruz ve iki tanesini alıyoruz.
const totalCourses = await Course.find().countDocuments(); // toplam kurs sayısı
const totalStudents = await User.countDocuments({role: 'student'}); // toplam öğrenci sayısı
const totalTeachers = await User.countDocuments({role: 'teacher'}); // toplam öğretmen sayısı
res.status(200).render('index', {
page_name: 'index',
courses,
totalCourses,
totalStudents,
totalTeachers,
});
};
Gönderilen veriler anasayfada yerleştirilerek kullanılır.
daha önceki projenin aynısı.
Yeni database e bağlanırlen tüm veriler sıfırlanacağı için, index sayfasında courses verilerini if(courses[0]) benzeri koşullara alıyoruz.
connect için kullandığımız mongodb bağlantısını middleware içindeki kısmı update ederken kullanmayı unutma.
bağlantı için:
mongoose
.connect('mongodb+srv://arslan_ng:narniag7A.@cluster0.cqw1stg.mongodb.net/?retryWrites=true&w=majority')
.then(() => console.log('DB Connected!'));
session middleware için:
app.use(
session({
secret: 'keyboard_cat_rambo',
resave: false,
saveUninitialized: true,
store: MongoStore.create({ mongoUrl: 'mongodb+srv://arslan_ng:narniag7A.@cluster0.cqw1stg.mongodb.net/?retryWrites=true&w=majority' }),
})
);