Express.js Veritabanı Mimarisi: 7 Adımda Kapsamlı 2026 Rehberi
Yazar: Burak Balkı | Kategori: Database | Okuma Süresi: 57 dk
Bu kapsamlı 2026 rehberi, Express.js ile sağlam veritabanı mimarisi oluşturmanın temel prensiplerini, ileri seviye tekniklerini ve en iyi uygulamalarını deta...
## Giriş: Express.js Veritabanı Mimarisi ile Geleceğe Yönelik Çözümler
Modern web uygulamalarının kalbinde verimli ve ölçeklenebilir bir veritabanı mimarisi yatar. Günümüzün dinamik dijital dünyasında, özellikle 2026 itibarıyla, performans ve güvenilirlik her zamankinden daha kritik. Express.js, Node.js ekosisteminin en popüler ve esnek web çatılarından biri olarak, veritabanı entegrasyonu ve mimari tasarım konusunda geliştiricilere geniş olanaklar sunar. Peki, bu esnekliği kullanarak nasıl sağlam, yüksek performanslı ve sürdürülebilir veritabanı yapıları kurabiliriz?
Bu kapsamlı 2026 rehberinde, 10 yılı aşkın süredir edindiğim tecrübelerle Express.js tabanlı uygulamalarınız için en iyi veritabanı mimarisi yaklaşımlarını detaylıca inceleyeceğiz. Sistem tasarımı kararlarından performans optimizasyonuna, güvenlik pratiklerinden gerçek dünya örneklerine kadar her adımı ele alarak, projenizi bir sonraki seviyeye taşıyacak bilgi ve araçları sunmayı hedefliyorum. Artık sadece çalışan değil, aynı zamanda ölçeklenebilir ve bakımı kolay sistemler inşa etmenin zamanı geldi. Hadi, Express.js ile veritabanı mimarisinin derinliklerine dalalım ve 2026'nın en iyi uygulamalarını keşfedelim.
## Express.js Nedir?
Express.js, Node.js için minimalist ve esnek bir web uygulama çatısıdır. Web ve mobil uygulamalar için sağlam API'ler ve HTTP yardımcı programları kümesi sağlayarak, hızlı ve kolay bir şekilde web uygulamaları oluşturmaya olanak tanır.
Express.js, middleware tabanlı yapısı sayesinde istek-cevap döngüsünü kolayca yönetmenizi, yönlendirme (routing) işlemlerini basitleştirmenizi ve uygulama mantığınızı modüler bir şekilde organize etmenizi sağlar. 2026 itibarıyla, hafif yapısı, geniş topluluk desteği ve Node.js'in olay güdümlü, engellemeyen (non-blocking) G/Ç modeliyle mükemmel uyumu sayesinde, yüksek performanslı ve ölçeklenebilir sunucu tarafı uygulamaları geliştirmek için tercih edilen bir çözüm olmaya devam etmektedir. Özellikle mikroservis mimarilerinde ve RESTful API geliştirmede yaygın olarak kullanılır.
## Neden Express.js Kullanmalısınız?
Express.js'in popülaritesi, sunduğu bir dizi avantajdan kaynaklanmaktadır. Bir bilgisayar mühendisi ve full stack developer olarak, üretim ortamında Express.js ile çalışırken bu avantajları defalarca deneyimledim. İşte 2026'da Express.js kullanmanız için temel nedenler:
* **Hız ve Performans**: Node.js'in V8 JavaScript motoru üzerindeki engellemeyen G/Ç modeli sayesinde Express.js uygulamaları yüksek eşzamanlılık (concurrency) ve düşük gecikme süreleri sunar. Bu, özellikle veritabanı operasyonlarının yoğun olduğu API servisleri için kritik bir performans avantajı sağlar.
* **Esneklik ve Minimalizm**: Express.js 'pil dahil değildir' (batteries not included) felsefesini benimser. Bu, yalnızca ihtiyacınız olan bileşenleri seçebileceğiniz anlamına gelir. Veritabanı entegrasyonu için ORM'ler (Object-Relational Mappers) veya ODM'ler (Object-Document Mappers) gibi birçok üçüncü taraf kütüphanesiyle kolayca entegre olabilir.
* **Geniş Ekosistem ve Topluluk**: npm (Node Package Manager) üzerinden erişilebilen binlerce kütüphane ve middleware sayesinde, neredeyse her türlü ihtiyacınız için hazır çözümler bulabilirsiniz. 2026'da da aktif bir geliştirici topluluğu, sorunlarınıza hızlıca yanıt bulmanızı ve en iyi uygulamaları takip etmenizi sağlar.
* **Ölçeklenebilirlik**: Express.js, yatay ölçeklenebilirlik için tasarlanmıştır. Birden fazla Express.js örneğini bir yük dengeleyici (load balancer) arkasında çalıştırarak yüksek trafikli uygulamaları kolayca yönetebilirsiniz. Veritabanı bağlantı havuzları ve asenkron operasyonlar bu ölçeklenebilirliği destekler.
* **Öğrenme Kolaylığı**: JavaScript bilen geliştiriciler için Express.js'in öğrenme eğrisi oldukça düşüktür. Minimal API'si ve anlaşılır dokümantasyonu sayesinde, kısa sürede üretim seviyesinde uygulamalar geliştirmeye başlayabilirsiniz.
**Kimler İçin Uygun, Kimler İçin Değil?**
Express.js, RESTful API'ler, mikroservisler, gerçek zamanlı uygulamalar (WebSockets ile), tek sayfa uygulamalarının (SPA) arka uçları ve hızlı prototipleme için idealdir. Ancak, çok fazla hazır bileşen veya katı bir yapı arayan büyük kurumsal uygulamalar için NestJS gibi daha opinionated (görüşlü) framework'ler daha uygun olabilir. Yine de, doğru mimari kararlar ve best practice'ler ile Express.js her ölçekte projeye adapte edilebilir.
## Express.js Veritabanı Katmanı vs Alternatifler (2026)
Veritabanı etkileşimleri, bir uygulamanın en kritik bileşenlerinden biridir. Express.js'te bu katmanı nasıl tasarlayacağınız, uygulamanızın performansını ve sürdürülebilirliğini doğrudan etkiler. Aşağıdaki tabloda, Express.js'in veritabanı katmanını diğer popüler Node.js framework'leri olan NestJS ve Koa ile karşılaştırıyoruz.
| Özellik | Express.js (Ham Sürücü/ORM) | NestJS (ORM Odaklı) | Koa (Middleware Odaklı) |
| :------------------ | :--------------------------------------------------------- | :--------------------------------------------------------- | :------------------------------------------------------------- |
| **Veritabanı Yaklaşımı** | Ham sürücü (örn. `pg`, `mongodb`), ORM/ODM (örn. Sequelize, Mongoose) seçimi serbest. | TypeORM, Mongoose gibi güçlü ORM/ODM entegrasyonu standart. | Ham sürücü veya ORM/ODM seçimi serbest, Express.js'e benzer. |
| **Öğrenme Eğrisi** | Orta (Veritabanı sürücüsü/ORM bilgisi gerektirir). | Orta-Yüksek (TypeScript, OOP, IoC prensipleri gerektirir). | Orta (Async/await ve Koa'nın middleware yapısı). |
| **Ekosistem** | Geniş ve esnek, istediğiniz kütüphaneyi seçme özgürlüğü. | Daha kısıtlı ama entegre ve güçlü modüller. | Express.js'e benzer, daha küçük ancak aktif topluluk. |
| **Topluluk Desteği**| Çok büyük ve aktif. | Hızla büyüyen ve aktif. | Orta büyüklükte ve aktif. |
| **Kurumsal Destek** | Esneklik sayesinde kurumsal çözümlere adapte edilebilir. | Kurumsal düzeyde uygulamalar için daha yapılandırılmış. | Daha çok mikroservis ve hafif API'ler için tercih edilir. |
| **Kullanım Alanı** | REST API'ler, mikroservisler, esnek projeler. | Büyük ölçekli, kurumsal, yapılandırılmış uygulamalar. | Hafif API'ler, mikroservisler, yüksek performanslı middleware. |
**Yorum**: Express.js, veritabanı katmanında size en fazla özgürlüğü sunar. Bu, hem bir avantaj hem de bir sorumluluktur. Doğru araçları seçmek ve mimari kararları dikkatlice vermek, uygulamanızın geleceği için kritik öneme sahiptir. NestJS, daha opinionated bir yapı sunarak veritabanı entegrasyonunu daha "kutudan çıktığı gibi" hale getirirken, Koa Express.js'e benzer bir esneklik sunar ancak `async/await` üzerine daha fazla odaklanır.
## Kurulum ve İlk Adımlar (2026)
Express.js ve bir veritabanı (örneğin PostgreSQL) ile başlamak oldukça basittir. Bu bölümde, temel bir Express.js projesi oluşturup PostgreSQL veritabanına nasıl bağlanacağınızı adım adım göstereceğim. 2026 itibarıyla Node.js 20.x veya 22.x LTS sürümünün kurulu olduğunu varsayıyorum.
**Ön Gereksinimler:**
* Node.js (20.x veya 22.x LTS önerilir) ve npm kurulu.
* PostgreSQL veritabanı sunucusu kurulu ve çalışır durumda.
* `psql` komut satırı aracı veya bir veritabanı yönetim aracı (örn. DBeaver, pgAdmin).
**Adım 1: Proje Oluşturma ve Bağımlılıkları Yükleme**
Yeni bir proje dizini oluşturun ve içine gidin. Ardından `npm init -y` ile `package.json` dosyasını oluşturun ve gerekli paketleri yükleyin.
```bash
mkdir express-db-app-2026
cd express-db-app-2026
npm init -y
npm install express pg dotenv
```
* `express`: Web çatımız.
* `pg`: PostgreSQL veritabanı sürücüsü.
* `dotenv`: Ortam değişkenlerini yönetmek için.
**Adım 2: Veritabanı Oluşturma ve Yapılandırma**
PostgreSQL'de yeni bir veritabanı ve tablo oluşturalım. `psql` veya tercih ettiğiniz bir araçla bağlanın:
```sql
CREATE DATABASE express_db_2026;
\c express_db_2026;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
INSERT INTO users (name, email) VALUES
('Burak Balkı', 'burak.balki@example.com'),
('Ayşe Yılmaz', 'ayse.yilmaz@example.com');
```
**Adım 3: Ortam Değişkenlerini Ayarlama**
Proje kök dizininizde `.env` adında bir dosya oluşturun ve veritabanı bağlantı bilgilerinizi buraya ekleyin. **Güvenlik nedeniyle hassas bilgileri doğrudan koda yazmaktan kaçının.**
```dotenv
DB_USER=your_pg_user
DB_HOST=localhost
DB_DATABASE=express_db_2026
DB_PASSWORD=your_pg_password
DB_PORT=5432
```
**Adım 4: Veritabanı Bağlantısını Yapılandırma**
`src/config/db.js` adında bir dosya oluşturarak veritabanı bağlantı havuzunu (connection pool) yapılandıralım. Bu, her istek için yeni bir bağlantı açıp kapatmak yerine, bağlantıları yeniden kullanarak performansı artırır.
```javascript
// src/config/db.js
require('dotenv').config();
const { Pool } = require('pg');
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_DATABASE,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
max: 20, // Maksimum eşzamanlı bağlantı sayısı
idleTimeoutMillis: 30000, // Bir bağlantının havuzda boşta kalabileceği maksimum süre
connectionTimeoutMillis: 2000, // Bir istemcinin bağlantı almayı bekleyeceği maksimum süre
});
pool.on('error', (err, client) => {
console.error('Veritabanı bağlantı havuzunda beklenmeyen hata:', err);
process.exit(-1); // Uygulamayı sonlandır
});
console.log('PostgreSQL bağlantı havuzu 2026 başarıyla yapılandırıldı.');
module.exports = pool;
```
**Adım 5: Express Uygulamasını Başlatma**
`src/app.js` dosyasını oluşturun ve Express uygulamasını başlatın.
```javascript
// src/app.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(express.json()); // JSON istek gövdelerini ayrıştırmak için
app.get('/', (req, res) => {
res.send('Express.js Veritabanı Mimarisi 2026 Uygulaması Çalışıyor!');
});
app.listen(PORT, () => {
console.log(`Sunucu 2026 yılında http://localhost:${PORT} adresinde çalışıyor.`);
});
module.exports = app; // Testler için dışa aktar
```
**Adım 6: Uygulamayı Çalıştırma**
`index.js` adında ana bir dosya oluşturarak uygulamayı başlatın:
```javascript
// index.js
require('dotenv').config(); // Ortam değişkenlerini en başta yükle
const app = require('./src/app');
const pool = require('./src/config/db'); // Bağlantı havuzunu başlat
const PORT = process.env.PORT || 3000;
// Veritabanı bağlantısını test et
pool.query('SELECT NOW()', (err, res) => {
if (err) {
console.error('Veritabanı bağlantı testi başarısız:', err.stack);
} else {
console.log('Veritabanı bağlantısı başarılı:', res.rows[0].now);
}
});
app.listen(PORT, () => {
console.log(`Express.js sunucusu 2026 yılında http://localhost:${PORT} adresinde dinliyor.`);
});
```
Şimdi terminalde `node index.js` komutunu çalıştırarak uygulamanızı başlatabilirsiniz. Veritabanı bağlantısının başarılı olduğuna dair bir mesaj görmelisiniz.
## Temel Kullanım ve Örnekler (2026)
Artık veritabanı bağlantımız olduğuna göre, Express.js uygulamamızda temel CRUD (Create, Read, Update, Delete) operasyonlarını nasıl gerçekleştireceğimize bakalım. Bu örnekler, `src/routes/userRoutes.js` ve `src/controllers/userController.js` gibi ayrı modüllere bölerek daha temiz bir mimari oluşturmayı hedefleyecektir.
**Problem**: Kullanıcıları listeleme, ekleme, güncelleme ve silme işlemleri için RESTful API uç noktaları oluşturmak.
**Çözüm**: Controller ve Router katmanlarını kullanarak modüler bir yapı kurmak.
**Adım 1: Controller Oluşturma (`src/controllers/userController.js`)**
Controller, iş mantığını ve veritabanı etkileşimini içerir.
```javascript
// src/controllers/userController.js
const pool = require('../config/db');
exports.getAllUsers = async (req, res) => {
try {
const result = await pool.query('SELECT * FROM users ORDER BY created_at DESC');
res.status(200).json(result.rows);
} catch (err) {
console.error('Kullanıcıları getirirken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
exports.createUser = async (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ message: 'Ad ve e-posta zorunludur.' });
}
try {
const result = await pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[name, email]
);
res.status(201).json(result.rows[0]);
} catch (err) {
console.error('Kullanıcı oluşturulurken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
exports.getUserById = async (req, res) => {
const { id } = req.params;
try {
const result = await pool.query('SELECT * FROM users WHERE id = $1', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
}
res.status(200).json(result.rows[0]);
} catch (err) {
console.error('Kullanıcı getirilirken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
exports.updateUser = async (req, res) => {
const { id } = req.params;
const { name, email } = req.body;
try {
const result = await pool.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *',
[name, email, id]
);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
}
res.status(200).json(result.rows[0]);
} catch (err) {
console.error('Kullanıcı güncellenirken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
exports.deleteUser = async (req, res) => {
const { id } = req.params;
try {
const result = await pool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Kullanıcı bulunamadı.' });
}
res.status(200).json({ message: 'Kullanıcı başarıyla silindi.', deletedUser: result.rows[0] });
} catch (err) {
console.error('Kullanıcı silinirken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
```
**Adım 2: Router Oluşturma (`src/routes/userRoutes.js`)**
Router, URL yollarını controller fonksiyonlarına bağlar.
```javascript
// src/routes/userRoutes.js
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
router.get('/', userController.getAllUsers);
router.post('/', userController.createUser);
router.get('/:id', userController.getUserById);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);
module.exports = router;
```
**Adım 3: Ana Uygulamaya Yönlendiriciyi Ekleme (`src/app.js`)**
`src/app.js` dosyasını güncelleyerek `userRoutes`'u uygulamaya dahil edin.
```javascript
// src/app.js (Güncellenmiş)
const express = require('express');
const app = express();
const userRoutes = require('./routes/userRoutes');
app.use(express.json());
app.get('/', (req, res) => {
res.send('Express.js Veritabanı Mimarisi 2026 Uygulaması Çalışıyor!');
});
// Kullanıcı rotalarını ekle
app.use('/api/users', userRoutes);
// Hata yakalama middleware'i (en sona eklenir)
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Beklenmeyen bir hata oluştu!');
});
module.exports = app;
```
Artık uygulamanızı yeniden başlatıp ( `node index.js` ) aşağıdaki API uç noktalarını test edebilirsiniz:
* `GET /api/users`
* `POST /api/users` (Body: `{"name": "Yeni Kullanıcı", "email": "yeni@example.com"}`)
* `GET /api/users/1`
* `PUT /api/users/1` (Body: `{"name": "Güncel İsim", "email": "guncel@example.com"}`)
* `DELETE /api/users/1`
Bu yapı, sorumlulukların ayrılması (Separation of Concerns) prensibini uygulayarak kodunuzu daha okunabilir ve yönetilebilir hale getirir. 2026'da büyük ölçekli projelerde bu tür modüler yapılar vazgeçilmezdir.
## İleri Seviye Teknikler (2026)
Üretim ortamında çalışan Express.js uygulamaları için sadece temel CRUD işlemleri yeterli değildir. Daha sağlam, bakımı kolay ve ölçeklenebilir bir veritabanı mimarisi için ileri seviye teknikleri ve tasarım desenlerini uygulamak kritik öneme sahiptir. Son projemde, bu yaklaşımları uygulayarak veritabanı etkileşimlerinde %40'a varan performans artışı ve hata oranlarında önemli düşüşler gözlemledim.
### 1. Repository Pattern (Depo Deseni)
**Problem**: Controller'ların doğrudan veritabanı sürücüsüyle (`pool.query`) etkileşime girmesi, iş mantığı ile veri erişim katmanını birbirine bağımlı hale getirir. Bu, test edilebilirliği zorlaştırır ve veritabanı teknolojisi değiştiğinde büyük refactoring gerektirebilir.
**Çözüm**: Repository pattern, veri erişim mantığını soyutlar. Controller'lar doğrudan veritabanıyla değil, bir arayüz (interface) aracılığıyla etkileşim kurar. Bu, veritabanı teknolojisini değiştirdiğinizde (örn. PostgreSQL'den MongoDB'ye geçiş) yalnızca repository uygulamasını güncellemeniz gerektiği anlamına gelir.
```javascript
// src/repositories/userRepository.js
const pool = require('../config/db');
class UserRepository {
constructor(dbPool) {
this.pool = dbPool;
}
async findAll() {
const result = await this.pool.query('SELECT * FROM users ORDER BY created_at DESC');
return result.rows;
}
async findById(id) {
const result = await this.pool.query('SELECT * FROM users WHERE id = $1', [id]);
return result.rows[0];
}
async create(name, email) {
const result = await this.pool.query(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[name, email]
);
return result.rows[0];
}
async update(id, name, email) {
const result = await this.pool.query(
'UPDATE users SET name = $1, email = $2 WHERE id = $3 RETURNING *',
[name, email, id]
);
return result.rows[0];
}
async delete(id) {
const result = await this.pool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]);
return result.rows[0];
}
}
module.exports = new UserRepository(pool); // Singleton olarak dışa aktar
```
Controller'ınızı güncelleyin (`src/controllers/userController.js`):
```javascript
// src/controllers/userController.js (Repository ile güncellenmiş)
const userRepository = require('../repositories/userRepository');
exports.getAllUsers = async (req, res) => {
try {
const users = await userRepository.findAll();
res.status(200).json(users);
} catch (err) {
console.error('Kullanıcıları getirirken hata:', err.stack);
res.status(500).json({ message: 'Sunucu hatası', error: err.message });
}
};
// Diğer controller metotları da benzer şekilde userRepository kullanacak
```
### 2. Unit of Work Pattern (İş Birimi Deseni)
**Problem**: Birden fazla veritabanı operasyonunun tek bir atomik işlem (transaction) içinde yürütülmesi gerektiğinde, her repository'nin kendi işlem mantığını yönetmesi karmaşıklığa yol açar.
**Çözüm**: Unit of Work, bir iş birimi içindeki tüm veritabanı operasyonlarını tek bir işlem olarak yönetir. Tüm operasyonlar başarılı olursa commit edilir, aksi takdirde rollback yapılır. Bu, veri tutarlılığını sağlar.
```javascript
// src/unitOfWork/unitOfWork.js
const pool = require('../config/db');
const UserRepository = require('../repositories/userRepository');
class UnitOfWork {
constructor() {
this.client = null;
this.userRepository = null;
}
async begin() {
this.client = await pool.connect();
await this.client.query('BEGIN');
this.userRepository = new UserRepository(this.client); // İşlem bazlı repository
}
async commit() {
await this.client.query('COMMIT');
this.client.release();
this.client = null;
}
async rollback() {
await this.client.query('ROLLBACK');
this.client.release();
this.client = null;
}
get users() {
if (!this.userRepository) {
throw new Error('UnitOfWork başlatılmadı. begin() metodunu çağırın.');
}
return this.userRepository;
}
}
module.exports = UnitOfWork;
```
Controller'da kullanımı:
```javascript
// src/controllers/userController.js (UnitOfWork ile)
const UnitOfWork = require('../unitOfWork/unitOfWork');
exports.transferFunds = async (req, res) => {
const { senderId, receiverId, amount } = req.body;
const uow = new UnitOfWork();
try {
await uow.begin();
// Örnek: Gerçek bir senaryoda bu user repository'si üzerinden hesap bakiyeleri güncellenir
// Bu örnekte sadece logluyoruz, gerçek veritabanı etkileşimi için daha fazla mantık gerekir.
console.log(`2026: ${senderId} ID'li kullanıcıdan ${receiverId} ID'li kullanıcıya ${amount} aktarılıyor.`);
// await uow.users.updateBalance(senderId, -amount);
// await uow.users.updateBalance(receiverId, amount);
await uow.commit();
res.status(200).json({ message: 'Para transferi başarıyla tamamlandı.' });
} catch (err) {
await uow.rollback();
console.error('Para transferinde hata:', err.stack);
res.status(500).json({ message: 'Para transferi başarısız oldu.', error: err.message });
}
};
```
### 3. Veri Doğrulama (Data Validation)
**Problem**: Gelen API isteklerindeki verilerin doğru formatta ve geçerli olduğundan emin olmak.
**Çözüm**: `joi` veya `express-validator` gibi kütüphaneler kullanarak istek gövdelerini (request body) ve parametrelerini (parameters) doğrulamak. Bu, veritabanına geçersiz veri yazılmasını önler ve güvenlik açıklarını azaltır.
```bash
npm install joi
```
```javascript
// src/middleware/validationMiddleware.js
const Joi = require('joi');
const userSchema = Joi.object({
name: Joi.string().min(3).max(100).required(),
email: Joi.string().email().required(),
});
exports.validateUser = (req, res, next) => {
const { error } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ message: error.details[0].message });
}
next();
};
```
Router'da kullanımı (`src/routes/userRoutes.js`):
```javascript
// src/routes/userRoutes.js (Validasyon ile güncellenmiş)
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { validateUser } = require('../middleware/validationMiddleware');
// POST ve PUT isteklerinde validasyonu uygula
router.post('/', validateUser, userController.createUser);
router.put('/:id', validateUser, userController.updateUser);
// Diğer rotalar...
```
Bu ileri seviye teknikler, 2026 yılında kurumsal düzeyde uygulamalar geliştirirken Express.js projelerinizin daha sürdürülebilir, test edilebilir ve güvenli olmasını sağlar. Ekibimizde bu yaklaşımları benimsediğimizde, yeni geliştiricilerin projeye adaptasyon süresinin kısalığını ve hata oranlarının düştüğünü net bir şekilde gördük.
## Best Practices & Anti-Patterns (2026)
Express.js ile veritabanı mimarisi tasarlarken, yılların tecrübesiyle sabitlenmiş bazı en iyi uygulamalar ve kaçınılması gereken anti-pattern'lar bulunmaktadır. Bu bölümde, 2026'nın güncel yaklaşımlarını dikkate alarak bunları inceleyeceğiz.
**✅ DOĞRU Uygulamalar:**
* **Veritabanı Bağlantı Havuzu Kullanın**: Her istek için yeni bir veritabanı bağlantısı açmak ve kapatmak yerine, `pg` gibi sürücülerin sağladığı bağlantı havuzlarını kullanın. Bu, bağlantı açma/kapatma yükünü ortadan kaldırarak performansı önemli ölçüde artırır. **Neden?** Bağlantı oluşturma maliyetlidir ve havuzlar, performansı artırırken veritabanı üzerindeki yükü azaltır.
* **Katmanlı Mimari (Layered Architecture)**: Uygulamanızı `router`, `controller`, `service` (iş mantığı), `repository` (veri erişimi) ve `model` (veri şeması) gibi mantıksal katmanlara ayırın. **Neden?** Sorumlulukların ayrılması, kodun okunabilirliğini, test edilebilirliğini ve bakımını kolaylaştırır. Özellikle 2026 gibi karmaşık sistemlerin yaygın olduğu bir dönemde bu, projenin sürdürülebilirliği için vazgeçilmezdir.
* **Asenkron Programlama (`async/await`)**: Veritabanı operasyonları gibi G/Ç yoğun işlemlerde `async/await` kullanın. Callback cehenneminden kaçının ve kodunuzu daha okunabilir hale getirin. **Neden?** Node.js'in engellemeyen doğasını tam olarak kullanmanızı sağlar ve eşzamanlılık yönetimini basitleştirir.
* **Veri Doğrulama (Data Validation)**: Gelen tüm kullanıcı girişlerini sunucu tarafında mutlaka doğrulayın (`joi`, `express-validator`). **Neden?** Güvenlik açıklarını (örn. SQL enjeksiyonu) önler, uygulamanıza tutarlı ve geçerli veri girişini garanti eder.
* **Hata Yönetimi (Error Handling)**: Global bir hata yakalama middleware'i (`app.use((err, req, res, next) => { ... })`) ve spesifik hata türleri için özel hata sınıfları kullanın. **Neden?** Uygulamanızın beklenmeyen durumları graceful bir şekilde ele almasını sağlar ve kullanıcıya dostça hata mesajları sunar.
* **Güvenli Ortam Değişkenleri**: Veritabanı kimlik bilgileri, API anahtarları gibi hassas bilgileri `.env` dosyaları veya ortam değişkenleri aracılığıyla yönetin. Kod tabanına doğrudan yazmaktan kaçının. **Neden?** Güvenlik risklerini azaltır ve farklı ortamlar (geliştirme, test, üretim) arasında kolay yapılandırma sağlar.
* **SQL Enjeksiyonuna Karşı Koruma**: Veritabanı sorgularında parametreli sorgular veya ORM'ler/ODM'ler kullanın. Asla kullanıcı girdilerini doğrudan SQL sorgularına eklemeyin. **Neden?** SQL enjeksiyonu, 2026'da hala en yaygın ve tehlikeli güvenlik açıklarından biridir.
* **İşlem Yönetimi (Transactions)**: Birden fazla veritabanı operasyonunun atomik olarak gerçekleşmesi gereken durumlarda (örn. para transferi), veritabanı işlemlerini kullanın. **Neden?** Veri tutarlılığını garanti eder; ya tüm operasyonlar başarılı olur ya da hiçbiri olmaz.
* **Loglama (Logging)**: Uygulama ve veritabanı etkileşimlerindeki önemli olayları ve hataları loglayın (`winston`, `pino`). **Neden?** Sorun giderme, performans analizi ve güvenlik denetimi için hayati öneme sahiptir.
**❌ YANLIŞ Uygulamalar (Anti-Patterns):**
* **Controller'da Doğrudan Veritabanı Mantığı**: Controller'ların iş mantığı ve veritabanı erişimini doğrudan içermesi. **Neden?** Kodun karmaşıklığını artırır, test edilebilirliği zorlaştırır ve bağımlılıkları artırır.
* **Her İstekte Yeni Veritabanı Bağlantısı**: Her API isteği için yeni bir veritabanı bağlantısı oluşturup kapatmak. **Neden?** Performans düşüşüne yol açar ve veritabanı kaynaklarını gereksiz yere tüketir.
* **Kullanıcı Girdisini Güvensizce Sorguya Ekleme**: Kullanıcıdan gelen verileri doğrudan SQL veya NoSQL sorgularına eklemek. **Neden?** SQL/NoSQL enjeksiyonu riskini artırır ve ciddi güvenlik zafiyetlerine yol açar.
* **Hata Yönetimini İhmal Etmek**: Hataları yakalamamak veya genel, bilgilendirici olmayan hata mesajları döndürmek. **Neden?** Uygulamanın çökmesine neden olabilir, kullanıcı deneyimini kötüleştirir ve sorun gidermeyi imkansız hale getirir.
* **Senkron Veritabanı Operasyonları**: Node.js'in asenkron yapısını göz ardı ederek senkron veritabanı çağrıları yapmak. **Neden?** Node.js'in tek iş parçacıklı doğası nedeniyle uygulamayı bloke eder ve performansı düşürür.
## Yaygın Hatalar ve Çözümleri (2026)
Express.js ve veritabanı entegrasyonunda geliştiricilerin sıkça karşılaştığı bazı sorunlar ve bunların çözümleri bulunmaktadır. 2026'da bile bu temel sorunlar hala karşımıza çıkabilmektedir. Ekibimizde bu tür sorunlarla karşılaştığımızda, aşağıdaki yaklaşımları kullanarak hızla çözüm buluruz.
**1. Hata: `Error: connect ECONNREFUSED 127.0.0.1:5432`**
* **Sebep**: Uygulama, belirtilen portta veritabanı sunucusuna bağlanamıyor. Genellikle veritabanı sunucusu çalışmıyor, yanlış port kullanılıyor veya güvenlik duvarı bağlantıyı engelliyor.
* **Çözüm**:
1. Veritabanı sunucunuzun (örn. PostgreSQL) çalıştığından emin olun. `sudo systemctl status postgresql` (Linux) veya servisler panelinden kontrol edin.
2. `.env` dosyasındaki `DB_PORT` değerinin doğru olduğundan emin olun (varsayılan PostgreSQL portu 5432'dir).
3. Güvenlik duvarı ayarlarınızı kontrol edin ve veritabanı portuna erişime izin verin.
**2. Hata: N+1 Sorgu Problemi**
* **Sebep**: Bir ana sorgu çalıştırıldıktan sonra, her ana öğe için ek alt sorguların çalıştırılması. Örneğin, 100 kullanıcıyı sorgulayıp sonra her kullanıcının siparişlerini ayrı ayrı sorgulamak 1 ana + 100 alt = 101 sorguya neden olur.
* **Çözüm**:
1. **JOIN Kullanımı**: İlişkili verileri tek bir sorguda getirmek için SQL JOIN ifadelerini kullanın.
2. **Veri Yükleme Stratejileri**: ORM kullanıyorsanız, `eager loading` (hevesli yükleme) veya `lazy loading` (tembel yükleme) stratejilerini doğru şekilde yapılandırın. `eager loading`, ilişkili verileri ana sorguyla birlikte getirerek N+1 sorununu çözer.
```javascript
// N+1 Anti-Pattern (Her kullanıcı için ayrı sorgu)
// const users = await pool.query('SELECT * FROM users');
// for (const user of users.rows) {
// user.orders = await pool.query('SELECT * FROM orders WHERE user_id = $1', [user.id]);
// }
// Çözüm: JOIN ile tek sorgu
const result = await pool.query(`
SELECT u.id as user_id, u.name, u.email, o.id as order_id, o.amount, o.created_at as order_created_at
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
ORDER BY u.created_at DESC;
`);
// Bu verileri daha sonra uygulama katmanında gruplayabilirsiniz.
```
**3. Hata: `UnhandledPromiseRejectionWarning`**
* **Sebep**: `async/await` ile kullanılan bir Promise'in reddedilmesi (reject) durumunda hata yakalanmaması.
* **Çözüm**: Tüm `async` fonksiyon çağrılarını `try...catch` blokları içine alın. Global h