📌 Ne Zaman Kullanilir?
- ✅ Real-time API, microservices, full-stack JS, WebSocket, CLI araçları
- ⚠️ CPU-intensive islemler için dikkatli ol (Worker Threads kullan)
- ❌ Basit CRUD (Laravel daha hızlı baslangic)
Önerilen Kullanım: Express/Fastify + TypeScript + PostgreSQL/MongoDB Alternatifler: Laravel (PHP), Django (Python), ASP.NET Core (C#)
Node.js Backend Mastery Guide — Practical Reference (EN + TR)
Complete English + Turkish learning guide for Node.js backend development covering core modules, Express.js, REST APIs, databases, authentication, testing, and deployment. (Node.js backend geliştirme için kapsamli rehber: core moduller, Express.js, REST API, veritabani, kimlik doğrulama, test ve deploy.)
1) Node.js Nedir?
Node.js, Chrome'un V8 JavaScript motorunu kullanan, sunucu tarafinda (server-side) JavaScript calistiran bir runtime ortamidir.
Key Concepts (Temel Kavramlar):
- V8 Engine: Google'in C++ ile yazilmis JavaScript motoru. JS kodunu makine koduna derler (compiles to machine code).
- Non-blocking I/O: Dosya okuma, veritabani sorgusu, ag istekleri gibi islemler ana thread'i bloklamaz. (Main thread'i mesgul etmeden isler yapar.)
- Event Loop: Tek thread uzerinde çalışan, callback kuyruklarini yoneten mekanizma. (Single-threaded but handles thousands of concurrent connections.)
- Event-Driven Architecture: Olaylara dayali mimari — bir işlem tamamlandiginda callback cagrilir.
Neden Node.js? (Why Node.js?)
| Avantaj | Açıklama |
|---|---|
| Tek dil (JS everywhere) | Frontend ve backend ayni dil |
| Hızlı I/O | Non-blocking yapiyla yüksek performans |
| npm ekosistemi | 2M+ paket, dev community |
| Real-time uygulamalar | WebSocket, chat, live dashboard |
| Mikroservis mimarisi | Hafif, hızlı startup |
| Laravel biliyorsan | MVC pattern benzer, route/middleware konsepti ayni |
Event Loop Nasil Calisir:
┌───────────────────────────┐
┌─>│ timers │ (setTimeout, setInterval)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ (I/O callbacks)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ (incoming connections, data)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ (setImmediate)
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ close callbacks │ (socket.on('close'))
│ └─────────────┬─────────────┘
└─────────────────┘// Event loop ornegi
console.log("1 - Sync");
setTimeout(() => {
console.log("2 - setTimeout (macro task)");
}, 0);
Promise.resolve().then(() => {
console.log("3 - Promise (micro task)");
});
console.log("4 - Sync");
// Cikti: 1, 4, 3, 2
// Micro task'lar (Promise) macro task'lardan (setTimeout) once calisirSummary: Node.js = V8 + libuv + non-blocking I/O. Tek thread ama event loop sayesinde binlerce bağlantı yonetir.
2) Kurulum & Ortam
nvm (Node Version Manager)
# nvm kurulumu (Linux/Mac)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
# Terminal'i yeniden ac veya:
source ~/.bashrc
# Node surumlerini yonet
nvm install 22 # LTS surumu kur
nvm install 20 # Baska bir surum
nvm use 22 # Aktif surumu degistir
nvm alias default 22 # Varsayilan surum ayarla
nvm ls # Kurulu surumleri listele
nvm ls-remote # Mevcut tum surumleri gor
# Proje bazli surum (.nvmrc dosyasiyla)
echo "22" > .nvmrc
nvm use # .nvmrc'den okurnpm vs yarn vs pnpm
| Özellik | npm | yarn | pnpm |
|---|---|---|---|
| Hiz | Orta | Hızlı | En hızlı |
| Disk kullanimi | Fazla | Fazla | Az (hard link) |
| Monorepo | workspaces | workspaces | workspaces (en iyi) |
| Lock dosyasi | package-lock.json | yarn.lock | pnpm-lock.yaml |
| Varsayilan | Node ile gelir | Ayri kurulum | Ayri kurulum |
# pnpm kurulumu (onerilen)
npm install -g pnpm
# yarn kurulumu
npm install -g yarn
# Temel komut karsilastirmasi
npm install # yarn install # pnpm install
npm install express # yarn add express # pnpm add express
npm run dev # yarn dev # pnpm dev
npx create-app # yarn dlx create-app # pnpm dlx create-appProje Baslangici
# Yeni proje olustur
mkdir my-api && cd my-api
npm init -y
# veya interaktif
npm init
# TypeScript ile baslamak istersen
npm init -y
npm install -D typescript @types/node ts-node nodemon
npx tsc --initSummary: nvm ile sürüm yonet, pnpm hiz ve disk için ideal. Projeye .nvmrc ekle, takimla uyum sagla.
3) Node.js Core Modulleri
fs (File System)
const fs = require("fs");
const fsPromises = require("fs/promises"); // Promise-based API
// --- Senkron okuma (Sync - bloklayici, kucuk isler icin) ---
const data = fs.readFileSync("config.json", "utf-8");
console.log(JSON.parse(data));
// --- Asenkron okuma (Callback) ---
fs.readFile("config.json", "utf-8", (err, data) => {
if (err) throw err;
console.log(data);
});
// --- Promise-based (onerilen) ---
async function readConfig() {
try {
const data = await fsPromises.readFile("config.json", "utf-8");
return JSON.parse(data);
} catch (err) {
console.error("Dosya okunamadi:", err.message);
}
}
// --- Dosya yazma ---
await fsPromises.writeFile("output.txt", "Merhaba Node.js!", "utf-8");
// --- Dosya ekleme (append) ---
await fsPromises.appendFile("log.txt", `[${new Date().toISOString()}] Event\n`);
// --- Klasor islemleri ---
await fsPromises.mkdir("uploads/images", { recursive: true });
const files = await fsPromises.readdir("./src");
const stats = await fsPromises.stat("app.js");
console.log(stats.isFile(), stats.size, stats.mtime);
// --- Dosya silme ---
await fsPromises.unlink("temp.txt");
await fsPromises.rm("old-folder", { recursive: true, force: true });
// --- Dosya/klasor varligini kontrol et ---
try {
await fsPromises.access("config.json");
console.log("Dosya mevcut");
} catch {
console.log("Dosya bulunamadi");
}path
const path = require("path");
path.join("/users", "fahri", "docs", "file.txt");
// => /users/fahri/docs/file.txt
path.resolve("src", "index.js");
// => /absolute/path/to/src/index.js
path.basename("/users/fahri/app.js"); // => app.js
path.basename("/users/fahri/app.js", ".js"); // => app
path.extname("photo.png"); // => .png
path.dirname("/users/fahri/app.js"); // => /users/fahri
path.parse("/users/fahri/app.js");
// => { root: '/', dir: '/users/fahri', base: 'app.js', ext: '.js', name: 'app' }
// __dirname ve __filename (CommonJS)
console.log(__dirname); // Bu dosyanin klasoru
console.log(__filename); // Bu dosyanin tam yolu
// ES Modules icin:
// import { fileURLToPath } from 'url';
// const __filename = fileURLToPath(import.meta.url);
// const __dirname = path.dirname(__filename);http
const http = require("http");
const server = http.createServer((req, res) => {
const { method, url } = req;
if (method === "GET" && url === "/") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Merhaba!" }));
} else if (method === "GET" && url === "/health") {
res.writeHead(200);
res.end("OK");
} else {
res.writeHead(404);
res.end("Not Found");
}
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`Server ${PORT} portunda calisiyor`);
});os
const os = require("os");
console.log("Platform:", os.platform()); // linux, darwin, win32
console.log("Mimari:", os.arch()); // x64, arm64
console.log("CPU Sayisi:", os.cpus().length);
console.log("Toplam RAM:", (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), "GB");
console.log("Bos RAM:", (os.freemem() / 1024 / 1024 / 1024).toFixed(2), "GB");
console.log("Home:", os.homedir());
console.log("Hostname:", os.hostname());
console.log("Uptime:", (os.uptime() / 3600).toFixed(1), "saat");events (EventEmitter)
const EventEmitter = require("events");
class OrderService extends EventEmitter {
placeOrder(order) {
// Siparis islemlerini yap
console.log(`Siparis alindi: #${order.id}`);
// Olaylari tetikle (emit)
this.emit("order:created", order);
this.emit("notification:send", {
to: order.email,
subject: `Siparis #${order.id} onaylandi`,
});
}
}
const orderService = new OrderService();
// Olay dinleyicileri (listeners)
orderService.on("order:created", (order) => {
console.log(`Stok guncelleniyor: ${order.product}`);
});
orderService.on("notification:send", (notification) => {
console.log(`Email gonderiliyor: ${notification.to}`);
});
// once: sadece bir kere dinle
orderService.once("order:created", () => {
console.log("Ilk siparis kutlamasi!");
});
orderService.placeOrder({
id: 1001,
product: "Laptop",
email: "fahri@example.com",
});child_process
const { exec, execSync, spawn } = require("child_process");
// exec: Shell komutu calistir (kucuk ciktilar icin)
exec("ls -la", (err, stdout, stderr) => {
if (err) {
console.error("Hata:", err.message);
return;
}
console.log(stdout);
});
// execSync: Senkron calistir
const result = execSync("node --version", { encoding: "utf-8" });
console.log("Node surumu:", result.trim());
// spawn: Buyuk ciktilar ve stream icin (onerilen)
const child = spawn("find", [".", "-name", "*.js"]);
child.stdout.on("data", (data) => {
console.log(`stdout: ${data}`);
});
child.stderr.on("data", (data) => {
console.error(`stderr: ${data}`);
});
child.on("close", (code) => {
console.log(`Islem bitti, cikis kodu: ${code}`);
});Summary: Core modulleri framework'suz bile cok sey yapabilirsin.
fs/promiseskullan,path.joinile platformlar-arasi yol oluştur.
4) npm & Paket Yönetimi
package.json Yapisi
{
"name": "my-api",
"version": "1.0.0",
"description": "REST API projesi",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js",
"test": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"build": "tsc",
"db:migrate": "prisma migrate dev",
"db:seed": "node src/seeds/index.js"
},
"dependencies": {
"express": "^4.21.0",
"mongoose": "^8.5.0",
"dotenv": "^16.4.0"
},
"devDependencies": {
"nodemon": "^3.1.0",
"jest": "^29.7.0",
"eslint": "^9.0.0"
},
"engines": {
"node": ">=20.0.0"
},
"license": "MIT"
}dependencies vs devDependencies
# dependencies: Production'da gereken paketler
npm install express mongoose dotenv
# devDependencies: Sadece gelistirme icin
npm install -D nodemon jest eslint typescript
# Production kurulumu (devDependencies haric)
npm install --production
# veya
NODE_ENV=production npm installSemantic Versioning (SemVer)
MAJOR.MINOR.PATCH
^ ^ ^
| | └── Bug fix (hata duzeltme)
| └──────── Yeni ozellik (backward-compatible)
└─────────────── Breaking change (kirilma degisikligi)
^4.21.0 → >=4.21.0 <5.0.0 (minor ve patch guncellenebilir)
~4.21.0 → >=4.21.0 <4.22.0 (sadece patch guncellenebilir)
4.21.0 → tam bu surum (sabitlenmis)Faydali npm Komutları
# Paket yonetimi
npm install # Tum bagimliliklar kur
npm install express # Paket ekle
npm install -D nodemon # Dev dependency olarak ekle
npm uninstall express # Paketi kaldir
npm update # Tum paketleri guncelle
# Bilgi ve kontrol
npm list --depth=0 # Kurulu paketleri gor
npm outdated # Guncellenebilir paketler
npm audit # Guvenlik taramasi
npm audit fix # Guvenlik sorunlarini duzelt
# npx: Paket kurmadan calistir
npx create-react-app my-app
npx prisma migrate dev
npx eslint --init
# Script calistirma
npm run dev # package.json scripts
npm test # kisayol: npm run test
npm start # kisayol: npm run start
# Global paketler
npm install -g pm2 nodemon
npm list -g --depth=0.npmrc Dosyasi
# Proje kokune .npmrc ekle
save-exact=true # Tam surum numaralariyla kaydet
engine-strict=true # engines uyumsuzlugunda hata verSummary:
package.jsonprojenin kalbi. SemVer'i anla,npm auditile güvenlik tara, scripts ile is akisini otomatiklestir.
5) Express.js
Kurulum ve Temel Yapi
npm install express
npm install -D nodemon// src/index.js
const express = require("express");
const app = express();
// --- Built-in Middleware ---
app.use(express.json()); // JSON body parsing
app.use(express.urlencoded({ extended: true })); // Form data parsing
app.use(express.static("public")); // Statik dosyalar
// --- Basit Route ---
app.get("/", (req, res) => {
res.json({ message: "API calisiyor", status: "ok" });
});
// --- Server baslat ---
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server http://localhost:${PORT} adresinde calisiyor`);
});Routing
// --- Route parametreleri ---
app.get("/users/:id", (req, res) => {
const { id } = req.params;
res.json({ userId: id });
});
// --- Query parametreleri ---
// GET /search?q=node&page=2
app.get("/search", (req, res) => {
const { q, page = 1, limit = 10 } = req.query;
res.json({ query: q, page: Number(page), limit: Number(limit) });
});
// --- Route gruplama (Router) ---
const userRouter = express.Router();
userRouter.get("/", listUsers);
userRouter.get("/:id", getUser);
userRouter.post("/", createUser);
userRouter.put("/:id", updateUser);
userRouter.delete("/:id", deleteUser);
app.use("/api/users", userRouter);
// --- Route zincirleme ---
app.route("/api/posts")
.get(listPosts)
.post(createPost);
app.route("/api/posts/:id")
.get(getPost)
.put(updatePost)
.delete(deletePost);Middleware
// --- Custom middleware: Loglama ---
function requestLogger(req, res, next) {
const start = Date.now();
res.on("finish", () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} - ${duration}ms`);
});
next(); // Sonraki middleware'e gec (ONEMLI!)
}
app.use(requestLogger);
// --- Auth middleware ---
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ error: "Token gerekli" });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: "Gecersiz token" });
}
}
// Belirli route'lara middleware uygula
app.get("/api/profile", authenticate, (req, res) => {
res.json({ user: req.user });
});
// Route grubuna uygula
const protectedRouter = express.Router();
protectedRouter.use(authenticate);
protectedRouter.get("/dashboard", getDashboard);
protectedRouter.get("/settings", getSettings);
app.use("/api", protectedRouter);
// --- Role-based middleware ---
function authorize(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: "Yetkiniz yok (Forbidden)" });
}
next();
};
}
app.delete("/api/users/:id", authenticate, authorize("admin"), deleteUser);Request & Response
app.post("/api/users", (req, res) => {
// --- Request ---
console.log(req.body); // POST body (JSON)
console.log(req.params); // URL parametreleri
console.log(req.query); // Query string
console.log(req.headers); // Basliklar
console.log(req.cookies); // Cookie'ler (cookie-parser gerekir)
console.log(req.ip); // Client IP
console.log(req.method); // GET, POST, PUT, DELETE
console.log(req.path); // URL path
// --- Response ---
res.status(201).json({ id: 1, name: "Fahri" }); // JSON yanit
res.send("Plain text"); // Text yanit
res.sendFile("/path/to/file.pdf"); // Dosya gonder
res.download("/path/to/report.xlsx"); // Dosya indirt
res.redirect("/api/users"); // Yonlendir
res.status(204).end(); // Bos yanit (No Content)
res.cookie("token", "abc123", { httpOnly: true }); // Cookie set et
});Error Handling Middleware
// --- 404 handler (tum route'lardan sonra) ---
app.use((req, res) => {
res.status(404).json({
error: "Not Found",
message: `${req.method} ${req.path} bulunamadi`,
});
});
// --- Global error handler (4 parametre SART) ---
app.use((err, req, res, next) => {
console.error(err.stack);
const statusCode = err.statusCode || 500;
res.status(statusCode).json({
error: err.message || "Internal Server Error",
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
});
});Summary: Express = route + middleware + req/res. Middleware sirasi önemli!
next()unutma. Error handler en sona.
6) REST API Geliştirme
Proje Yapisi (Klasor Organizasyonu)
my-api/
src/
index.js # Entry point
app.js # Express app config
config/
database.js # DB baglantisi
index.js # Tum config
routes/
index.js # Route birlestirici
userRoutes.js
postRoutes.js
controllers/
userController.js
postController.js
models/
User.js
Post.js
middleware/
auth.js
validate.js
errorHandler.js
utils/
ApiError.js
asyncHandler.js
validators/
userValidator.js
tests/
user.test.js
.env
.env.example
package.jsonapp.js
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const morgan = require("morgan");
const routes = require("./routes");
const errorHandler = require("./middleware/errorHandler");
const app = express();
// --- Global Middleware ---
app.use(helmet()); // Guvenlik basliklari
app.use(cors()); // Cross-Origin izinleri
app.use(morgan("dev")); // HTTP request loglama
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true }));
// --- Routes ---
app.use("/api", routes);
// --- Health Check ---
app.get("/health", (req, res) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});
// --- Error Handler ---
app.use(errorHandler);
module.exports = app;Controller Pattern
// src/controllers/userController.js
const User = require("../models/User");
const ApiError = require("../utils/ApiError");
const asyncHandler = require("../utils/asyncHandler");
// Tum kullanicilari getir
exports.getUsers = asyncHandler(async (req, res) => {
const { page = 1, limit = 10, sort = "-createdAt" } = req.query;
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find().sort(sort).skip(skip).limit(Number(limit)),
User.countDocuments(),
]);
res.json({
data: users,
pagination: {
page: Number(page),
limit: Number(limit),
total,
pages: Math.ceil(total / limit),
},
});
});
// Tek kullanici getir
exports.getUser = asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) {
throw new ApiError(404, "Kullanici bulunamadi");
}
res.json({ data: user });
});
// Kullanici olustur
exports.createUser = asyncHandler(async (req, res) => {
const user = await User.create(req.body);
res.status(201).json({ data: user });
});
// Kullanici guncelle
exports.updateUser = asyncHandler(async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true, // Guncellenmis dokumani dondur
runValidators: true,
});
if (!user) {
throw new ApiError(404, "Kullanici bulunamadi");
}
res.json({ data: user });
});
// Kullanici sil
exports.deleteUser = asyncHandler(async (req, res) => {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
throw new ApiError(404, "Kullanici bulunamadi");
}
res.status(204).end();
});Validation (Zod)
npm install zod// src/validators/userValidator.js
const { z } = require("zod");
const createUserSchema = z.object({
name: z.string().min(2, "Isim en az 2 karakter olmali"),
email: z.string().email("Gecerli bir email girin"),
password: z.string().min(8, "Sifre en az 8 karakter olmali"),
role: z.enum(["user", "admin"]).default("user"),
age: z.number().int().min(18).max(120).optional(),
});
const updateUserSchema = createUserSchema.partial(); // Tum alanlar opsiyonel
module.exports = { createUserSchema, updateUserSchema };// src/middleware/validate.js
function validate(schema) {
return (req, res, next) => {
const result = schema.safeParse(req.body);
if (!result.success) {
const errors = result.error.errors.map((e) => ({
field: e.path.join("."),
message: e.message,
}));
return res.status(400).json({ error: "Validation hatasi", details: errors });
}
req.body = result.data; // Temizlenmis veriyi kullan
next();
};
}
module.exports = validate;// Route'ta kullanim
const validate = require("../middleware/validate");
const { createUserSchema } = require("../validators/userValidator");
router.post("/users", validate(createUserSchema), userController.createUser);Validation (Joi Alternatifi)
const Joi = require("joi");
const createUserSchema = Joi.object({
name: Joi.string().min(2).max(50).required(),
email: Joi.string().email().required(),
password: Joi.string().min(8).pattern(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/).required(),
role: Joi.string().valid("user", "admin").default("user"),
});
// Middleware
function validateJoi(schema) {
return (req, res, next) => {
const { error, value } = schema.validate(req.body, { abortEarly: false });
if (error) {
const details = error.details.map((d) => ({
field: d.path.join("."),
message: d.message,
}));
return res.status(400).json({ error: "Validation hatasi", details });
}
req.body = value;
next();
};
}Summary: Route -> Validate -> Controller -> Model. Clean architecture = kolay test, kolay bakim. Laravel'deki FormRequest = buradaki validate middleware.
7) Veritabani Entegrasyonu
MongoDB + Mongoose
npm install mongoose// src/config/database.js
const mongoose = require("mongoose");
async function connectDB() {
try {
await mongoose.connect(process.env.MONGODB_URI, {
// Mongoose 7+ icin bu opsiyonlar artik varsayilan
});
console.log("MongoDB baglandI");
} catch (err) {
console.error("MongoDB baglanti hatasi:", err.message);
process.exit(1);
}
}
// Baglanti olaylarini dinle
mongoose.connection.on("disconnected", () => {
console.log("MongoDB baglantisi kesildi");
});
module.exports = connectDB;// src/models/User.js
const mongoose = require("mongoose");
const bcrypt = require("bcryptjs");
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Isim zorunlu"],
trim: true,
minlength: [2, "Isim en az 2 karakter"],
},
email: {
type: String,
required: [true, "Email zorunlu"],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, "Gecerli email girin"],
},
password: {
type: String,
required: [true, "Sifre zorunlu"],
minlength: 8,
select: false, // Sorgularda sifre gelmez
},
role: {
type: String,
enum: ["user", "admin"],
default: "user",
},
isActive: {
type: Boolean,
default: true,
},
},
{
timestamps: true, // createdAt, updatedAt otomatik
toJSON: {
transform(doc, ret) {
delete ret.password;
delete ret.__v;
return ret;
},
},
}
);
// --- Pre-save hook: Sifre hashleme ---
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// --- Instance method: Sifre karsilastirma ---
userSchema.methods.comparePassword = async function (candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
// --- Static method: Email ile bul ---
userSchema.statics.findByEmail = function (email) {
return this.findOne({ email }).select("+password");
};
// --- Index ---
userSchema.index({ email: 1 });
userSchema.index({ createdAt: -1 });
module.exports = mongoose.model("User", userSchema);PostgreSQL + Prisma
npm install prisma @prisma/client
npx prisma init// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([email])
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}# Migration calistir
npx prisma migrate dev --name init
# Client olustur
npx prisma generate
# Studio (GUI)
npx prisma studio// src/config/prisma.js
const { PrismaClient } = require("@prisma/client");
const prisma = new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "warn", "error"] : ["error"],
});
module.exports = prisma;// Prisma CRUD ornekleri
const prisma = require("../config/prisma");
// Olustur
const user = await prisma.user.create({
data: { name: "Fahri", email: "fahri@example.com", password: hashedPw },
});
// Oku (iliski ile birlikte)
const userWithPosts = await prisma.user.findUnique({
where: { id: 1 },
include: { posts: true },
});
// Listele (filtreleme + sayfalama)
const users = await prisma.user.findMany({
where: { role: "USER", name: { contains: "fah", mode: "insensitive" } },
orderBy: { createdAt: "desc" },
skip: 0,
take: 10,
select: { id: true, name: true, email: true, _count: { select: { posts: true } } },
});
// Guncelle
const updated = await prisma.user.update({
where: { id: 1 },
data: { name: "Fahri Aydin" },
});
// Sil
await prisma.user.delete({ where: { id: 1 } });
// Transaction
const [post, user] = await prisma.$transaction([
prisma.post.create({ data: { title: "Yeni Yazi", authorId: 1 } }),
prisma.user.update({ where: { id: 1 }, data: { name: "Updated" } }),
]);Summary: MongoDB = esnek şema (Mongoose ile), PostgreSQL = ilişkisel (Prisma ile). Prisma type-safe + migration + studio sağlar. Laravel'deki Eloquent = Mongoose/Prisma.
8) Authentication
JWT Authentication
npm install jsonwebtoken bcryptjs// src/utils/jwt.js
const jwt = require("jsonwebtoken");
function generateTokens(user) {
const accessToken = jwt.sign(
{ id: user.id, email: user.email, role: user.role },
process.env.JWT_SECRET,
{ expiresIn: "15m" }
);
const refreshToken = jwt.sign(
{ id: user.id },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: "7d" }
);
return { accessToken, refreshToken };
}
function verifyToken(token, secret) {
return jwt.verify(token, secret);
}
module.exports = { generateTokens, verifyToken };// src/controllers/authController.js
const User = require("../models/User");
const { generateTokens, verifyToken } = require("../utils/jwt");
const ApiError = require("../utils/ApiError");
const asyncHandler = require("../utils/asyncHandler");
// Kayit (Register)
exports.register = asyncHandler(async (req, res) => {
const { name, email, password } = req.body;
// Email kontrolu
const existing = await User.findOne({ email });
if (existing) {
throw new ApiError(409, "Bu email zaten kayitli");
}
const user = await User.create({ name, email, password });
const tokens = generateTokens(user);
res.status(201).json({
message: "Kayit basarili",
user: { id: user._id, name: user.name, email: user.email },
...tokens,
});
});
// Giris (Login)
exports.login = asyncHandler(async (req, res) => {
const { email, password } = req.body;
// +password: select: false olan alani dahil et
const user = await User.findByEmail(email);
if (!user || !(await user.comparePassword(password))) {
throw new ApiError(401, "Email veya sifre hatali");
}
const tokens = generateTokens(user);
// Refresh token'i httpOnly cookie'de sakla
res.cookie("refreshToken", tokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 gun
});
res.json({
message: "Giris basarili",
user: { id: user._id, name: user.name, role: user.role },
accessToken: tokens.accessToken,
});
});
// Token yenile
exports.refreshToken = asyncHandler(async (req, res) => {
const token = req.cookies.refreshToken;
if (!token) {
throw new ApiError(401, "Refresh token bulunamadi");
}
const decoded = verifyToken(token, process.env.JWT_REFRESH_SECRET);
const user = await User.findById(decoded.id);
if (!user) {
throw new ApiError(401, "Kullanici bulunamadi");
}
const tokens = generateTokens(user);
res.cookie("refreshToken", tokens.refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000,
});
res.json({ accessToken: tokens.accessToken });
});
// Cikis (Logout)
exports.logout = (req, res) => {
res.clearCookie("refreshToken");
res.json({ message: "Cikis yapildi" });
};Auth Middleware
// src/middleware/auth.js
const { verifyToken } = require("../utils/jwt");
const ApiError = require("../utils/ApiError");
function authenticate(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new ApiError(401, "Giris yapmaniz gerekiyor");
}
try {
const token = authHeader.split(" ")[1];
req.user = verifyToken(token, process.env.JWT_SECRET);
next();
} catch (err) {
if (err.name === "TokenExpiredError") {
throw new ApiError(401, "Token suresi dolmus");
}
throw new ApiError(401, "Gecersiz token");
}
}
function authorize(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
throw new ApiError(403, "Bu islem icin yetkiniz yok");
}
next();
};
}
module.exports = { authenticate, authorize };// Route'larda kullanim
const { authenticate, authorize } = require("../middleware/auth");
router.post("/auth/register", authController.register);
router.post("/auth/login", authController.login);
router.post("/auth/refresh", authController.refreshToken);
router.post("/auth/logout", authController.logout);
// Korumali route'lar
router.get("/users", authenticate, authorize("admin"), userController.getUsers);
router.get("/profile", authenticate, userController.getProfile);Summary: JWT = access token (kisa omurlu, header'da) + refresh token (uzun omurlu, httpOnly cookie'de). bcrypt ile sifre hashle, middleware ile koru.
9) Dosya Islemleri & Upload
fs/promises (Modern Dosya Islemleri)
const fs = require("fs/promises");
const path = require("path");
// --- Klasor icerigini recursive listele ---
async function listFilesRecursive(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await listFilesRecursive(fullPath)));
} else {
files.push(fullPath);
}
}
return files;
}
// --- Dosya kopyalama ---
async function copyFile(src, dest) {
await fs.mkdir(path.dirname(dest), { recursive: true });
await fs.copyFile(src, dest);
console.log(`Kopyalandi: ${src} -> ${dest}`);
}
// --- JSON dosyasi okuma/yazma ---
async function readJSON(filePath) {
const data = await fs.readFile(filePath, "utf-8");
return JSON.parse(data);
}
async function writeJSON(filePath, data) {
await fs.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
}
// --- Dosya boyutunu okunabilir formata cevir ---
async function getFileSize(filePath) {
const stats = await fs.stat(filePath);
const units = ["B", "KB", "MB", "GB"];
let size = stats.size;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(2)} ${units[unitIndex]}`;
}
// --- Gecici dosya olustur ve temizle ---
async function withTempFile(fn) {
const tmpPath = path.join(
require("os").tmpdir(),
`tmp-${Date.now()}-${Math.random().toString(36).slice(2)}`
);
try {
await fn(tmpPath);
} finally {
try {
await fs.unlink(tmpPath);
} catch {
// Dosya zaten silinmis olabilir
}
}
}
// --- Dosya izinlerini kontrol et ---
async function isReadable(filePath) {
try {
await fs.access(filePath, fs.constants.R_OK);
return true;
} catch {
return false;
}
}
async function isWritable(filePath) {
try {
await fs.access(filePath, fs.constants.W_OK);
return true;
} catch {
return false;
}
}Dosya Upload (multer)
npm install multer// src/middleware/upload.js
const multer = require("multer");
const path = require("path");
const ApiError = require("../utils/ApiError");
// --- Disk storage ---
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, "uploads/");
},
filename: (req, file, cb) => {
const uniqueSuffix = `${Date.now()}-${Math.round(Math.random() * 1e9)}`;
const ext = path.extname(file.originalname);
cb(null, `${file.fieldname}-${uniqueSuffix}${ext}`);
},
});
// --- Dosya filtresi ---
const fileFilter = (req, file, cb) => {
const allowedTypes = ["image/jpeg", "image/png", "image/webp", "application/pdf"];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new ApiError(400, "Desteklenmeyen dosya tipi"), false);
}
};
const upload = multer({
storage,
fileFilter,
limits: {
fileSize: 5 * 1024 * 1024, // 5MB limit
files: 5, // Maksimum 5 dosya
},
});
module.exports = upload;// Route'larda kullanim
const upload = require("../middleware/upload");
// Tek dosya
router.post("/upload/avatar", upload.single("avatar"), (req, res) => {
res.json({
message: "Dosya yuklendi",
file: {
filename: req.file.filename,
size: req.file.size,
mimetype: req.file.mimetype,
},
});
});
// Birden fazla dosya
router.post("/upload/gallery", upload.array("photos", 5), (req, res) => {
res.json({
message: `${req.files.length} dosya yuklendi`,
files: req.files.map((f) => ({ filename: f.filename, size: f.size })),
});
});Memory Storage ve Buffer ile Upload
// Memory storage: dosyayi diske yazmadan bellekte tut
const memoryStorage = multer({ storage: multer.memoryStorage() });
router.post("/upload/process", memoryStorage.single("file"), async (req, res) => {
// req.file.buffer ile dosya icerigine eris
const content = req.file.buffer.toString("utf-8");
const lineCount = content.split("\n").length;
res.json({
originalName: req.file.originalname,
size: req.file.size,
lineCount,
});
});
// Sharp ile gorsel isleme
const sharp = require("sharp");
router.post("/upload/resize", memoryStorage.single("image"), async (req, res) => {
const resized = await sharp(req.file.buffer)
.resize(300, 300, { fit: "cover" })
.webp({ quality: 80 })
.toBuffer();
// S3 veya diske yaz
await fs.writeFile(`uploads/thumb-${Date.now()}.webp`, resized);
res.json({ message: "Gorsel boyutlandirildi", size: resized.length });
});Dosya İzleme (chokidar)
npm install chokidarconst chokidar = require("chokidar");
// --- Klasor izleme ---
const watcher = chokidar.watch("./uploads", {
ignored: /(^|[\/\\])\../, // Gizli dosyalari yoksay
persistent: true,
ignoreInitial: true, // Baslangictaki dosyalari yoksay
depth: 3, // Maksimum derinlik
});
watcher
.on("add", (filePath) => {
console.log(`Yeni dosya: ${filePath}`);
// Ornek: dosyayi isleme kuyruGuna ekle
})
.on("change", (filePath) => {
console.log(`Degisen dosya: ${filePath}`);
})
.on("unlink", (filePath) => {
console.log(`Silinen dosya: ${filePath}`);
})
.on("error", (error) => {
console.error("Izleme hatasi:", error);
});
// Izlemeyi durdur
// watcher.close();
// --- Konfigurasyon dosyasi izleme (hot reload) ---
const configWatcher = chokidar.watch("./config.json", { persistent: true });
let appConfig = {};
configWatcher.on("change", async () => {
try {
const data = await fs.readFile("./config.json", "utf-8");
appConfig = JSON.parse(data);
console.log("Konfigurasyon yeniden yuklendi");
} catch (err) {
console.error("Konfigurasyon okuma hatasi:", err.message);
}
});Summary:
fs/promisesmodern Node.js'te standart. multer ile dosya upload, sharp ile gorsel işleme, chokidar ile dosya izleme. Memory storage küçük dosyalar için uygun, büyük dosyalarda disk storage kullan.
10) Streams
Stream Turleri
const { Readable, Writable, Transform, Duplex, pipeline } = require("stream");
const { pipeline: pipelineAsync } = require("stream/promises");
const fs = require("fs");
const zlib = require("zlib");Readable Stream
// --- Ozel Readable stream ---
class CounterStream extends Readable {
constructor(max) {
super({ objectMode: true }); // objectMode: JS nesneleri gonderebilir
this.max = max;
this.current = 0;
}
_read() {
if (this.current < this.max) {
this.current++;
this.push({ number: this.current, timestamp: Date.now() });
} else {
this.push(null); // Stream bitti
}
}
}
const counter = new CounterStream(5);
counter.on("data", (chunk) => console.log(chunk));
counter.on("end", () => console.log("Stream bitti"));
// --- Readable.from ile iterable'dan stream olustur ---
async function* generateLines() {
for (let i = 1; i <= 100; i++) {
yield `Satir ${i}\n`;
}
}
const lineStream = Readable.from(generateLines());
lineStream.pipe(fs.createWriteStream("output.txt"));Writable Stream
// --- Ozel Writable stream ---
class LogWriter extends Writable {
constructor(filePath) {
super({ objectMode: true });
this.filePath = filePath;
this.buffer = [];
this.flushInterval = setInterval(() => this.flush(), 5000);
}
_write(chunk, encoding, callback) {
this.buffer.push(
`[${new Date().toISOString()}] ${JSON.stringify(chunk)}\n`
);
// Her 100 satirda bir disk'e yaz (batch write)
if (this.buffer.length >= 100) {
this.flush()
.then(() => callback())
.catch(callback);
} else {
callback();
}
}
async flush() {
if (this.buffer.length === 0) return;
const data = this.buffer.join("");
this.buffer = [];
await require("fs/promises").appendFile(this.filePath, data);
}
_final(callback) {
clearInterval(this.flushInterval);
this.flush()
.then(() => callback())
.catch(callback);
}
}
const logger = new LogWriter("app.log");
logger.write({ level: "info", message: "Uygulama basladi" });
logger.write({ level: "warn", message: "Disk dolmak uzere" });
logger.end();Transform Stream
// --- CSV'yi JSON'a ceviren Transform stream ---
class CSVToJSON extends Transform {
constructor() {
super({ objectMode: true });
this.headers = null;
this.remainder = "";
}
_transform(chunk, encoding, callback) {
const data = this.remainder + chunk.toString();
const lines = data.split("\n");
this.remainder = lines.pop(); // Son satir eksik olabilir
for (const line of lines) {
if (!line.trim()) continue;
const values = line.split(",").map((v) => v.trim());
if (!this.headers) {
this.headers = values;
continue;
}
const obj = {};
this.headers.forEach((header, i) => {
obj[header] = values[i];
});
this.push(obj);
}
callback();
}
_flush(callback) {
if (this.remainder.trim()) {
const values = this.remainder.split(",").map((v) => v.trim());
const obj = {};
this.headers.forEach((header, i) => {
obj[header] = values[i];
});
this.push(obj);
}
callback();
}
}
// Kullanim
fs.createReadStream("data.csv")
.pipe(new CSVToJSON())
.on("data", (row) => console.log(row))
.on("end", () => console.log("CSV isleme tamamlandi"));
// --- Basit filtre Transform ---
class FilterStream extends Transform {
constructor(filterFn) {
super({ objectMode: true });
this.filterFn = filterFn;
}
_transform(chunk, encoding, callback) {
if (this.filterFn(chunk)) {
this.push(chunk);
}
callback();
}
}Duplex Stream
// --- Duplex stream: hem okur hem yazar (bagimSiz) ---
class EchoStream extends Duplex {
constructor() {
super();
this.data = [];
}
_read(size) {
if (this.data.length > 0) {
this.push(this.data.shift());
} else {
this.push(null);
}
}
_write(chunk, encoding, callback) {
// Yazilan veriyi buyuk harfe cevir ve okuma tarafina aktar
this.data.push(chunk.toString().toUpperCase());
callback();
}
}Pipeline
// --- pipeline ile guvenli stream zincirleme ---
// pipeline otomatik olarak hata yonetimi ve backpressure saglar
// Dosya sikistirma
async function compressFile(input, output) {
await pipelineAsync(
fs.createReadStream(input),
zlib.createGzip(),
fs.createWriteStream(output)
);
console.log("Sikistirma tamamlandi");
}
// Dosya acma
async function decompressFile(input, output) {
await pipelineAsync(
fs.createReadStream(input),
zlib.createGunzip(),
fs.createWriteStream(output)
);
console.log("Acma tamamlandi");
}
// Coklu transform pipeline
async function processLogFile(inputPath, outputPath) {
await pipelineAsync(
fs.createReadStream(inputPath),
new Transform({
transform(chunk, encoding, callback) {
// Sadece ERROR satirlarini filtrele
const lines = chunk.toString().split("\n");
const errors = lines.filter((line) => line.includes("ERROR"));
callback(null, errors.join("\n") + "\n");
},
}),
zlib.createGzip(),
fs.createWriteStream(outputPath)
);
}Backpressure Yönetimi
// --- Backpressure: Yazan taraf okuyan taraftan hizliysa ---
// YANLIS: Backpressure'i yoksayar, bellek tasar
const readable = fs.createReadStream("huge-file.dat");
const writable = fs.createWriteStream("output.dat");
// readable.on("data", (chunk) => {
// writable.write(chunk); // TEHLIKELI: writable dolabilir
// });
// DOGRU: pipeline kullan (otomatik backpressure)
await pipelineAsync(readable, writable);
// VEYA: Manuel backpressure yonetimi
function copyWithBackpressure(src, dest) {
const readable = fs.createReadStream(src);
const writable = fs.createWriteStream(dest);
readable.on("data", (chunk) => {
const canContinue = writable.write(chunk);
if (!canContinue) {
// Writable buffer dolu, okumayı durdur
readable.pause();
}
});
writable.on("drain", () => {
// Writable buffer bosaldi, okumaya devam et
readable.resume();
});
readable.on("end", () => {
writable.end();
});
}
// --- HTTP response ile stream ---
app.get("/api/download/:filename", (req, res) => {
const filePath = path.join(__dirname, "files", req.params.filename);
const stat = fs.statSync(filePath);
res.setHeader("Content-Length", stat.size);
res.setHeader("Content-Type", "application/octet-stream");
res.setHeader(
"Content-Disposition",
`attachment; filename="${req.params.filename}"`
);
const readStream = fs.createReadStream(filePath);
readStream.pipe(res);
readStream.on("error", (err) => {
console.error("Stream hatasi:", err);
if (!res.headersSent) {
res.status(500).json({ error: "Dosya okunamadi" });
}
});
});
// --- HTTP request body stream (buyuk upload) ---
app.post("/api/upload/stream", (req, res) => {
const filePath = path.join(__dirname, "uploads", `upload-${Date.now()}`);
const writeStream = fs.createWriteStream(filePath);
req.pipe(writeStream);
writeStream.on("finish", () => {
res.json({ message: "Upload tamamlandi", path: filePath });
});
writeStream.on("error", (err) => {
res.status(500).json({ error: "Yazma hatasi" });
});
});Satir Satir Okuma (Büyük Dosyalar)
const readline = require("readline");
async function processLargeFile(filePath) {
const rl = readline.createInterface({
input: fs.createReadStream(filePath),
crlfDelay: Infinity,
});
let lineCount = 0;
for await (const line of rl) {
lineCount++;
// Her satiri isle
if (line.includes("ERROR")) {
console.log(`Satir ${lineCount}: ${line}`);
}
}
console.log(`Toplam satir: ${lineCount}`);
}
// --- Stream ile HTTP response (CSV export) ---
app.get("/api/export/csv", authenticate, async (req, res) => {
res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-Disposition", "attachment; filename=users.csv");
res.write("id,name,email\n");
const cursor = User.find().cursor();
for await (const user of cursor) {
res.write(`${user.id},${user.name},${user.email}\n`);
}
res.end();
});Summary: Readable (kaynak), Writable (hedef), Transform (donusum), Duplex (cift yonlu).
pipeline()backpressure ve hata yonetimini otomatik yapar. Büyük dosyalar için stream kullan, bellek tasmasindan kacin.
11) Environment & Config
dotenv Kurulumu
npm install dotenv# .env (GIT'E EKLEME! .gitignore'a ekle)
NODE_ENV=development
PORT=3000
# Database
MONGODB_URI=mongodb://localhost:27017/myapp
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
# JWT
JWT_SECRET=super-secret-key-change-in-production
JWT_REFRESH_SECRET=another-secret-key
JWT_EXPIRES_IN=15m
# External APIs
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
SMTP_USER=your-user
SMTP_PASS=your-pass
# Frontend
FRONTEND_URL=http://localhost:5173# .env.example (Bu GIT'e eklenir, takimla paylasilir)
NODE_ENV=development
PORT=3000
MONGODB_URI=mongodb://localhost:27017/myapp
JWT_SECRET=change-me
JWT_REFRESH_SECRET=change-me// src/config/index.js
require("dotenv").config();
const config = {
env: process.env.NODE_ENV || "development",
port: parseInt(process.env.PORT, 10) || 3000,
db: {
uri: process.env.MONGODB_URI,
postgresUrl: process.env.DATABASE_URL,
},
jwt: {
secret: process.env.JWT_SECRET,
refreshSecret: process.env.JWT_REFRESH_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || "15m",
},
smtp: {
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT, 10),
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
frontendUrl: process.env.FRONTEND_URL || "http://localhost:5173",
isDev: process.env.NODE_ENV === "development",
isProd: process.env.NODE_ENV === "production",
};
// Zorunlu degiskenleri kontrol et
const requiredVars = ["JWT_SECRET", "MONGODB_URI"];
for (const varName of requiredVars) {
if (!process.env[varName]) {
throw new Error(`Environment degiskeni eksik: ${varName}`);
}
}
module.exports = config;// .gitignore
.env
.env.local
.env.production
node_modules/
dist/
uploads/
coverage/
*.logSummary:
.envgizli tut,.env.examplepaylAS. Zorunlu degiskenleri startup'ta kontrol et.config/index.jsile merkezi yönetim.
12) Error Handling
Custom Error Sinifi
// src/utils/ApiError.js
class ApiError extends Error {
constructor(statusCode, message, errors = []) {
super(message);
this.statusCode = statusCode;
this.errors = errors;
this.isOperational = true; // Beklenen hata mi? (vs programmatic bug)
Error.captureStackTrace(this, this.constructor);
}
static badRequest(message, errors) {
return new ApiError(400, message, errors);
}
static unauthorized(message = "Giris yapmaniz gerekiyor") {
return new ApiError(401, message);
}
static forbidden(message = "Yetkiniz yok") {
return new ApiError(403, message);
}
static notFound(message = "Kaynak bulunamadi") {
return new ApiError(404, message);
}
static conflict(message) {
return new ApiError(409, message);
}
static internal(message = "Sunucu hatasi") {
return new ApiError(500, message);
}
}
module.exports = ApiError;Async Handler Wrapper
// src/utils/asyncHandler.js
// try/catch yazmak yerine controller'lari sar (wrap et)
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
module.exports = asyncHandler;
// Kullanim:
// ONCE (her controller'da try/catch):
// exports.getUser = async (req, res, next) => {
// try {
// const user = await User.findById(req.params.id);
// res.json(user);
// } catch (err) {
// next(err);
// }
// };
// SONRA (asyncHandler ile):
// exports.getUser = asyncHandler(async (req, res) => {
// const user = await User.findById(req.params.id);
// res.json(user);
// });Global Error Handler
// src/middleware/errorHandler.js
const ApiError = require("../utils/ApiError");
function errorHandler(err, req, res, next) {
// Mongoose validation hatasi
if (err.name === "ValidationError") {
const errors = Object.values(err.errors).map((e) => ({
field: e.path,
message: e.message,
}));
err = ApiError.badRequest("Validation hatasi", errors);
}
// Mongoose duplicate key hatasi
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
err = ApiError.conflict(`${field} zaten mevcut`);
}
// Mongoose invalid ObjectId
if (err.name === "CastError") {
err = ApiError.badRequest(`Gecersiz ${err.path}: ${err.value}`);
}
// JWT hatasi
if (err.name === "JsonWebTokenError") {
err = ApiError.unauthorized("Gecersiz token");
}
if (err.name === "TokenExpiredError") {
err = ApiError.unauthorized("Token suresi dolmus");
}
const statusCode = err.statusCode || 500;
const message = err.isOperational ? err.message : "Beklenmeyen sunucu hatasi";
// Logla
if (statusCode >= 500) {
console.error("SERVER ERROR:", err);
}
res.status(statusCode).json({
success: false,
error: message,
...(err.errors?.length && { details: err.errors }),
...(process.env.NODE_ENV === "development" && { stack: err.stack }),
});
}
module.exports = errorHandler;Yakalanmamis Hataları Yakala
// src/index.js
const app = require("./app");
const connectDB = require("./config/database");
// Yakalanmamis promise rejection
process.on("unhandledRejection", (reason, promise) => {
console.error("UNHANDLED REJECTION:", reason);
// Graceful shutdown
server.close(() => {
process.exit(1);
});
});
// Yakalanmamis exception
process.on("uncaughtException", (err) => {
console.error("UNCAUGHT EXCEPTION:", err);
process.exit(1);
});
async function start() {
await connectDB();
const server = app.listen(process.env.PORT || 3000, () => {
console.log(`Server calisiyor: ${process.env.PORT || 3000}`);
});
// Graceful shutdown (SIGTERM / SIGINT)
const shutdown = (signal) => {
console.log(`${signal} alindi, kapatiliyor...`);
server.close(() => {
console.log("HTTP server kapatildi");
process.exit(0);
});
};
process.on("SIGTERM", () => shutdown("SIGTERM"));
process.on("SIGINT", () => shutdown("SIGINT"));
}
start();Summary:
ApiError+asyncHandler+ globalerrorHandler= temiz hata yönetimi.unhandledRejectionveuncaughtExceptionyakala. Graceful shutdown uygula.
13) WebSocket & Real-time
Socket.io Kurulumu
npm install socket.io
# Client tarafinda:
npm install socket.io-client// --- Server tarafinda Socket.io kurulumu ---
const http = require("http");
const express = require("express");
const { Server } = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = new Server(server, {
cors: {
origin: process.env.FRONTEND_URL || "http://localhost:5173",
methods: ["GET", "POST"],
},
pingTimeout: 60000, // Baglanti zaman asimi
pingInterval: 25000, // Ping araligi
maxHttpBufferSize: 1e6, // Maksimum mesaj boyutu (1MB)
});
server.listen(3000, () => {
console.log("Server calisiyor: 3000");
});Temel Bağlantı ve Olaylar
// --- Server ---
io.on("connection", (socket) => {
console.log("Yeni baglanti:", socket.id);
// Mesaj dinle
socket.on("message", (data) => {
console.log("Gelen mesaj:", data);
// Gondericiye cevap
socket.emit("message:response", { status: "alindi" });
});
// Baglanti kesilince
socket.on("disconnect", (reason) => {
console.log(`Baglanti kesildi: ${socket.id}, sebep: ${reason}`);
});
// Hata yakala
socket.on("error", (err) => {
console.error("Socket hatasi:", err);
});
});
// --- Client ---
// import { io } from "socket.io-client";
// const socket = io("http://localhost:3000");
//
// socket.on("connect", () => {
// console.log("Baglanti kuruldu:", socket.id);
// socket.emit("message", { text: "Merhaba!" });
// });
//
// socket.on("message:response", (data) => {
// console.log("Cevap:", data);
// });Namespace
// Namespace: farkli endpoint'ler icin ayri iletisim kanallari
// Her namespace kendi connection event'ine sahiptir
const chatNamespace = io.of("/chat");
const adminNamespace = io.of("/admin");
// /chat namespace
chatNamespace.on("connection", (socket) => {
console.log("Chat'e baglandi:", socket.id);
socket.on("chat:message", (msg) => {
// Sadece /chat namespace'indeki kullanicilara gonder
chatNamespace.emit("chat:message", msg);
});
});
// /admin namespace (ayri auth middleware)
adminNamespace.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.role !== "admin") {
return next(new Error("Yetki yok"));
}
socket.user = decoded;
next();
} catch {
next(new Error("Gecersiz token"));
}
});
adminNamespace.on("connection", (socket) => {
console.log("Admin paneline baglandi:", socket.user.email);
socket.on("admin:stats", async () => {
const stats = await getServerStats();
socket.emit("admin:stats", stats);
});
});
// Client namespace kullanimi:
// const chatSocket = io("http://localhost:3000/chat");
// const adminSocket = io("http://localhost:3000/admin", {
// auth: { token: "jwt-token-here" }
// });Room (Oda)
io.on("connection", (socket) => {
// --- Odaya katil ---
socket.on("room:join", (roomId) => {
socket.join(roomId);
console.log(`${socket.id} odaya katildi: ${roomId}`);
// Odadaki diger kullanicilara bildir
socket.to(roomId).emit("room:user-joined", {
userId: socket.id,
roomId,
});
// Odadaki kullanici sayisini gonder
const room = io.sockets.adapter.rooms.get(roomId);
io.to(roomId).emit("room:count", room ? room.size : 0);
});
// --- Odadan ayril ---
socket.on("room:leave", (roomId) => {
socket.leave(roomId);
socket.to(roomId).emit("room:user-left", { userId: socket.id });
});
// --- Odaya mesaj gonder ---
socket.on("room:message", ({ roomId, message }) => {
// Odadaki herkese (gondericiye de)
io.to(roomId).emit("room:message", {
sender: socket.id,
message,
timestamp: Date.now(),
});
});
});Broadcast
io.on("connection", (socket) => {
// Herkese gonder (gondericiye de)
io.emit("announcement", "Yeni kullanici katildi");
// Herkese gonder (gondericiye HARIC)
socket.broadcast.emit("user:online", socket.id);
// Belirli odaya gonder (gondericiye haric)
socket.to("room-1").emit("room:update", { data: "..." });
// Birden fazla odaya gonder
socket.to("room-1").to("room-2").emit("multi-room", { data: "..." });
// Belirli bir socket'e gonder (private mesaj)
io.to(targetSocketId).emit("private:message", {
from: socket.id,
message: "Merhaba!",
});
});Acknowledgment (Onaylama)
// Server: callback ile onay gonder
io.on("connection", (socket) => {
socket.on("order:create", async (orderData, callback) => {
try {
const order = await Order.create(orderData);
// Client'a basari ile cevap gonder
callback({ status: "ok", order });
} catch (err) {
callback({ status: "error", message: err.message });
}
});
});
// Client: callback ile cevap al
// socket.emit("order:create", { product: "Laptop", qty: 1 }, (response) => {
// if (response.status === "ok") {
// console.log("Siparis olusturuldu:", response.order);
// } else {
// console.error("Hata:", response.message);
// }
// });Socket.io Middleware (Auth)
// Baglanti oncesi authentication middleware
io.use((socket, next) => {
const token = socket.handshake.auth.token;
if (!token) {
return next(new Error("Authentication gerekli"));
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
socket.user = decoded;
next();
} catch {
next(new Error("Gecersiz token"));
}
});
io.on("connection", (socket) => {
console.log(`Kullanici baglandi: ${socket.user.email}`);
// Kullaniciyi kendi odasina ekle (private mesajlar icin)
socket.join(`user:${socket.user.id}`);
});ws Kutuphanesi (Hafif Alternatif)
npm install wsconst WebSocket = require("ws");
const http = require("http");
const server = http.createServer();
const wss = new WebSocket.Server({ server });
wss.on("connection", (ws, req) => {
const ip = req.socket.remoteAddress;
console.log(`Yeni baglanti: ${ip}`);
ws.on("message", (data) => {
const message = JSON.parse(data.toString());
console.log("Gelen:", message);
// Herkese broadcast
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
type: "broadcast",
data: message,
timestamp: Date.now(),
}));
}
});
});
ws.on("close", () => {
console.log("Baglanti kapandi");
});
// Ping/Pong ile canli baglanti kontrolu
ws.isAlive = true;
ws.on("pong", () => { ws.isAlive = true; });
});
// Olmus baglantiları temizle
const heartbeat = setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
wss.on("close", () => clearInterval(heartbeat));
server.listen(3000);Real-time Chat Ornegi
// --- Tam bir chat uygulamasi ---
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = new Server(server);
// Aktif kullanicilar
const activeUsers = new Map();
// Auth middleware
io.use((socket, next) => {
const { username } = socket.handshake.auth;
if (!username) {
return next(new Error("Kullanici adi gerekli"));
}
socket.username = username;
next();
});
io.on("connection", (socket) => {
// Kullaniciyi kaydet
activeUsers.set(socket.id, {
username: socket.username,
joinedAt: Date.now(),
});
// Herkese aktif kullanici listesini gonder
io.emit("users:list", Array.from(activeUsers.values()));
// Genel chat mesaji
socket.on("chat:send", ({ message, room }) => {
const payload = {
id: Date.now().toString(36),
username: socket.username,
message,
timestamp: new Date().toISOString(),
};
if (room) {
io.to(room).emit("chat:receive", payload);
} else {
io.emit("chat:receive", payload);
}
});
// Odaya katil
socket.on("chat:join-room", (room) => {
socket.join(room);
socket.to(room).emit("chat:receive", {
id: Date.now().toString(36),
username: "Sistem",
message: `${socket.username} odaya katildi`,
timestamp: new Date().toISOString(),
system: true,
});
});
// Yazma gostergesi
socket.on("chat:typing", (room) => {
const target = room ? socket.to(room) : socket.broadcast;
target.emit("chat:typing", { username: socket.username });
});
socket.on("chat:stop-typing", (room) => {
const target = room ? socket.to(room) : socket.broadcast;
target.emit("chat:stop-typing", { username: socket.username });
});
// Baglanti kesilince
socket.on("disconnect", () => {
activeUsers.delete(socket.id);
io.emit("users:list", Array.from(activeUsers.values()));
io.emit("chat:receive", {
id: Date.now().toString(36),
username: "Sistem",
message: `${socket.username} ayrildi`,
timestamp: new Date().toISOString(),
system: true,
});
});
});
server.listen(3000, () => {
console.log("Chat server calisiyor: 3000");
});Summary: Socket.io = room + namespace + broadcast + acknowledgment ile guclu real-time iletisim.
wskutuphanesi daha hafif ama daha az özellik sunar. Auth middleware ile baglantiyi koru. Heartbeat ile olmus baglantiları temizle.
14) Caching (Redis)
Redis Baglantisi (ioredis)
npm install ioredis// src/config/redis.js
const Redis = require("ioredis");
const redis = new Redis({
host: process.env.REDIS_HOST || "127.0.0.1",
port: parseInt(process.env.REDIS_PORT, 10) || 6379,
password: process.env.REDIS_PASSWORD || undefined,
db: parseInt(process.env.REDIS_DB, 10) || 0,
maxRetriesPerRequest: 3,
retryStrategy(times) {
const delay = Math.min(times * 50, 2000);
return delay;
},
lazyConnect: true,
});
redis.on("connect", () => {
console.log("Redis baglantisi kuruldu");
});
redis.on("error", (err) => {
console.error("Redis hatasi:", err.message);
});
// Baglanti test
async function connectRedis() {
try {
await redis.connect();
await redis.ping();
console.log("Redis hazir");
} catch (err) {
console.error("Redis baglanti hatasi:", err.message);
process.exit(1);
}
}
module.exports = { redis, connectRedis };Temel Redis Islemleri
const { redis } = require("../config/redis");
// --- String islemleri ---
await redis.set("user:1:name", "Fahri");
await redis.set("session:abc", "data", "EX", 3600); // 1 saat TTL
const name = await redis.get("user:1:name");
// --- Hash islemleri ---
await redis.hset("user:1", {
name: "Fahri",
email: "fahri@example.com",
role: "admin",
});
const user = await redis.hgetall("user:1");
const email = await redis.hget("user:1", "email");
// --- Liste islemleri ---
await redis.lpush("queue:emails", JSON.stringify({ to: "user@test.com" }));
const job = await redis.rpop("queue:emails");
// --- Set islemleri ---
await redis.sadd("online:users", "user:1", "user:2", "user:3");
const isOnline = await redis.sismember("online:users", "user:1");
const onlineCount = await redis.scard("online:users");
await redis.srem("online:users", "user:1");
// --- Sorted Set (siralama, leaderboard) ---
await redis.zadd("leaderboard", 100, "player:1", 250, "player:2", 150, "player:3");
const top3 = await redis.zrevrange("leaderboard", 0, 2, "WITHSCORES");
// --- Key yonetimi ---
await redis.del("user:1:name");
await redis.expire("session:abc", 1800); // TTL guncelle
const ttl = await redis.ttl("session:abc");
const exists = await redis.exists("user:1");Cache-Aside Pattern
// src/utils/cache.js
const { redis } = require("../config/redis");
class CacheService {
// Cache-Aside: Once cache'e bak, yoksa DB'den al ve cache'e yaz
async getOrSet(key, fetchFn, ttlSeconds = 300) {
// 1. Cache'ten oku
const cached = await redis.get(key);
if (cached) {
return JSON.parse(cached);
}
// 2. Cache'te yoksa veriyi getir
const data = await fetchFn();
// 3. Cache'e yaz
if (data) {
await redis.set(key, JSON.stringify(data), "EX", ttlSeconds);
}
return data;
}
// Cache'i gecersiz kil (invalidate)
async invalidate(pattern) {
const keys = await redis.keys(pattern);
if (keys.length > 0) {
await redis.del(...keys);
}
}
// Belirli key'i sil
async delete(key) {
await redis.del(key);
}
// Cache'i tamamen temizle (dikkatli kullan!)
async flush() {
await redis.flushdb();
}
}
module.exports = new CacheService();// Controller'da kullanim
const cache = require("../utils/cache");
exports.getUser = asyncHandler(async (req, res) => {
const { id } = req.params;
const user = await cache.getOrSet(
`user:${id}`,
() => User.findById(id),
600 // 10 dakika cache
);
if (!user) {
throw new ApiError(404, "Kullanici bulunamadi");
}
res.json({ data: user });
});
exports.getUsers = asyncHandler(async (req, res) => {
const { page = 1, limit = 10 } = req.query;
const cacheKey = `users:page:${page}:limit:${limit}`;
const result = await cache.getOrSet(
cacheKey,
async () => {
const skip = (page - 1) * limit;
const [users, total] = await Promise.all([
User.find().skip(skip).limit(Number(limit)),
User.countDocuments(),
]);
return { users, total };
},
120 // 2 dakika cache
);
res.json({
data: result.users,
pagination: {
page: Number(page),
limit: Number(limit),
total: result.total,
},
});
});
// Guncelleme sonrasi cache invalidation
exports.updateUser = asyncHandler(async (req, res) => {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
throw new ApiError(404, "Kullanici bulunamadi");
}
// Ilgili cache'leri temizle
await cache.delete(`user:${req.params.id}`);
await cache.invalidate("users:page:*");
res.json({ data: user });
});Cache Middleware
// src/middleware/cacheMiddleware.js
const { redis } = require("../config/redis");
function cacheResponse(ttlSeconds = 300) {
return async (req, res, next) => {
// Sadece GET isteklerini cache'le
if (req.method !== "GET") return next();
const key = `cache:${req.originalUrl}`;
try {
const cached = await redis.get(key);
if (cached) {
const data = JSON.parse(cached);
return res.json(data);
}
} catch {
// Cache hatasi olursa devam et
}
// Orijinal json metodunu sar
const originalJson = res.json.bind(res);
res.json = (data) => {
// Basarili cevaplari cache'le
if (res.statusCode >= 200 && res.statusCode < 300) {
redis.set(key, JSON.stringify(data), "EX", ttlSeconds).catch(() => {});
}
originalJson(data);
};
next();
};
}
module.exports = cacheResponse;// Route'ta kullanim
const cacheResponse = require("../middleware/cacheMiddleware");
router.get("/posts", cacheResponse(60), postController.getPosts);
router.get("/posts/:id", cacheResponse(300), postController.getPost);Session Store (connect-redis)
npm install express-session connect-redisconst session = require("express-session");
const RedisStore = require("connect-redis").default;
const { redis } = require("./config/redis");
app.use(
session({
store: new RedisStore({
client: redis,
prefix: "sess:", // Redis key prefix
ttl: 86400, // 1 gun (saniye)
}),
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000, // 1 gun (milisaniye)
},
})
);
// Session kullanimi
app.post("/login", async (req, res) => {
const user = await authenticateUser(req.body);
req.session.userId = user.id;
req.session.role = user.role;
res.json({ message: "Giris basarili" });
});
app.get("/profile", (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: "Giris yapmaniz gerekiyor" });
}
res.json({ userId: req.session.userId, role: req.session.role });
});
app.post("/logout", (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: "Cikis yapilamadi" });
}
res.clearCookie("connect.sid");
res.json({ message: "Cikis yapildi" });
});
});Rate Limiting ile Redis
const rateLimit = require("express-rate-limit");
const RedisStore = require("rate-limit-redis").default;
const { redis } = require("./config/redis");
const apiLimiter = rateLimit({
store: new RedisStore({
sendCommand: (...args) => redis.call(...args),
}),
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100, // Pencere basina maksimum istek
standardHeaders: true,
legacyHeaders: false,
message: {
error: "Cok fazla istek gonderdiniz, lutfen bekleyin",
},
});
app.use("/api", apiLimiter);Summary: Redis = hızlı cache + session store + rate limiting. Cache-Aside pattern ile DB yukunu azalt. Cache invalidation unutma!
ioredisile baglan,connect-redisile session sakla.
15) Clustering & Concurrency
Cluster Module
// src/cluster.js
const cluster = require("cluster");
const os = require("os");
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`Primary process ${process.pid} calisiyor`);
console.log(`${numCPUs} worker baslatiliyor...`);
// Her CPU cekirdegi icin bir worker olustur
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Worker olurse yenisini baslat
cluster.on("exit", (worker, code, signal) => {
console.log(
`Worker ${worker.process.pid} oldu (code: ${code}, signal: ${signal})`
);
console.log("Yeni worker baslatiliyor...");
cluster.fork();
});
// Worker'lar arasi iletisim
cluster.on("message", (worker, message) => {
console.log(`Worker ${worker.process.pid}'den mesaj:`, message);
// Tum worker'lara broadcast
for (const id in cluster.workers) {
cluster.workers[id].send(message);
}
});
// Graceful shutdown
process.on("SIGTERM", () => {
console.log("Primary SIGTERM alindi, worker'lar kapatiliyor...");
for (const id in cluster.workers) {
cluster.workers[id].process.kill("SIGTERM");
}
});
} else {
// Worker process: HTTP server baslat
const app = require("./app");
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Worker ${process.pid} port ${PORT}'da calisiyor`);
});
// Primary'den mesaj al
process.on("message", (message) => {
console.log(`Worker ${process.pid} mesaj aldi:`, message);
});
}Worker Threads
// Worker threads: CPU-intensive islemler icin
// Event loop'u bloklamadan agir hesaplamalar yapar
// --- Ana dosya: worker-manager.js ---
const { Worker, isMainThread, parentPort, workerData } = require("worker_threads");
if (isMainThread) {
// Ana thread
function runWorker(data) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, { workerData: data });
worker.on("message", resolve);
worker.on("error", reject);
worker.on("exit", (code) => {
if (code !== 0) {
reject(new Error(`Worker ${code} koduyla durdu`));
}
});
});
}
// Kullanim
async function main() {
console.log("Agir hesaplama basliyor...");
const start = Date.now();
// Paralel worker'lar calistir
const results = await Promise.all([
runWorker({ start: 0, end: 25000000 }),
runWorker({ start: 25000000, end: 50000000 }),
runWorker({ start: 50000000, end: 75000000 }),
runWorker({ start: 75000000, end: 100000000 }),
]);
const totalPrimes = results.reduce((sum, r) => sum + r.count, 0);
console.log(`Toplam asal sayi: ${totalPrimes}`);
console.log(`Sure: ${Date.now() - start}ms`);
}
main();
} else {
// Worker thread
const { start, end } = workerData;
function countPrimes(start, end) {
let count = 0;
for (let i = Math.max(2, start); i < end; i++) {
let isPrime = true;
for (let j = 2; j <= Math.sqrt(i); j++) {
if (i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) count++;
}
return count;
}
const count = countPrimes(start, end);
parentPort.postMessage({ count, start, end });
}Worker Thread Pool
// src/utils/workerPool.js
const { Worker } = require("worker_threads");
const os = require("os");
class WorkerPool {
constructor(workerScript, poolSize = os.cpus().length) {
this.workerScript = workerScript;
this.poolSize = poolSize;
this.workers = [];
this.queue = [];
this.activeWorkers = 0;
}
execute(data) {
return new Promise((resolve, reject) => {
const task = { data, resolve, reject };
if (this.activeWorkers < this.poolSize) {
this._runTask(task);
} else {
this.queue.push(task);
}
});
}
_runTask(task) {
this.activeWorkers++;
const worker = new Worker(this.workerScript, { workerData: task.data });
worker.on("message", (result) => {
task.resolve(result);
this.activeWorkers--;
this._processQueue();
});
worker.on("error", (err) => {
task.reject(err);
this.activeWorkers--;
this._processQueue();
});
}
_processQueue() {
if (this.queue.length > 0 && this.activeWorkers < this.poolSize) {
const nextTask = this.queue.shift();
this._runTask(nextTask);
}
}
}
module.exports = WorkerPool;// Kullanim
const WorkerPool = require("./utils/workerPool");
const pool = new WorkerPool("./workers/imageProcessor.js", 4);
app.post("/api/process-images", async (req, res) => {
const images = req.body.images; // ["img1.jpg", "img2.jpg", ...]
const results = await Promise.all(
images.map((img) => pool.execute({ imagePath: img }))
);
res.json({ processed: results.length });
});Child Process (spawn, exec, fork)
const { spawn, exec, execSync, fork } = require("child_process");
// --- exec: Shell komutu, kucuk ciktilar ---
exec("df -h", (err, stdout, stderr) => {
if (err) {
console.error("Hata:", err.message);
return;
}
console.log("Disk kullanimi:\n", stdout);
});
// --- execSync: Senkron, hizli komutlar ---
const nodeVersion = execSync("node --version", { encoding: "utf-8" }).trim();
console.log("Node:", nodeVersion);
// --- spawn: Buyuk ciktilar, stream tabanlI ---
function runCommand(command, args) {
return new Promise((resolve, reject) => {
const child = spawn(command, args);
let stdout = "";
let stderr = "";
child.stdout.on("data", (data) => { stdout += data; });
child.stderr.on("data", (data) => { stderr += data; });
child.on("close", (code) => {
if (code === 0) {
resolve(stdout);
} else {
reject(new Error(`Komut ${code} ile bitti: ${stderr}`));
}
});
child.on("error", reject);
});
}
// Kullanim
const output = await runCommand("git", ["log", "--oneline", "-5"]);
// --- fork: Node.js child process (IPC ile iletisim) ---
// parent.js
const child = fork("./workers/heavy-task.js");
child.send({ type: "start", data: { items: 10000 } });
child.on("message", (result) => {
console.log("Sonuc:", result);
child.kill();
});
// workers/heavy-task.js
// process.on("message", (msg) => {
// if (msg.type === "start") {
// const result = doHeavyWork(msg.data);
// process.send({ type: "done", result });
// }
// });spawn vs exec vs fork Karsilastirmasi
| Özellik | exec | spawn | fork |
|---|---|---|---|
| Shell kullanimi | Evet | Hayir | Hayir |
| Buffer | Tamamen bellekte | Stream | Stream |
| IPC kanal | Yok | Yok | Otomatik |
| Kullanım | Kisa komutlar | Büyük ciktilar | Node.js child |
| Performans | Düşük | Yüksek | Yüksek |
| Örnek | exec("ls") | spawn("ffmpeg",...) | fork("worker.js") |
Summary: Cluster = çoklu HTTP server instance (load balance). Worker threads = CPU-intensive islemler için paralel hesaplama. fork = Node.js child process (IPC). spawn = büyük ciktili harici komutlar. Production'da PM2 cluster mode kullanmak daha pratik.
16) Güvenlik
helmet.js (HTTP Güvenlik Basliklari)
npm install helmetconst helmet = require("helmet");
// Varsayilan yapilandirma (tum korumalar aktif)
app.use(helmet());
// Ozel yapilandirma
app.use(
helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
imgSrc: ["'self'", "data:", "https:"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
connectSrc: ["'self'", process.env.API_URL],
},
},
crossOriginEmbedderPolicy: false,
crossOriginResourcePolicy: { policy: "cross-origin" },
})
);
// Helmet'in ayarladigi basliklar:
// X-Content-Type-Options: nosniff
// X-Frame-Options: DENY (clickjacking koruması)
// X-XSS-Protection: 0 (tarayici XSS filtresi)
// Strict-Transport-Security (HSTS)
// Content-Security-Policy
// Referrer-Policy
// X-DNS-Prefetch-Control
// X-Download-Options
// X-Permitted-Cross-Domain-PoliciesRate Limiting
npm install express-rate-limitconst rateLimit = require("express-rate-limit");
// Genel API rate limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 dakika
max: 100, // Pencere basina maks istek
standardHeaders: true, // RateLimit-* basliklarini ekle
legacyHeaders: false,
message: {
error: "Cok fazla istek. 15 dakika sonra tekrar deneyin.",
},
});
// Login icin daha siki limit
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 15 dakikada en fazla 5 giris denemesi
skipSuccessfulRequests: true, // Basarili girisleri sayma
message: {
error: "Cok fazla giris denemesi. 15 dakika sonra tekrar deneyin.",
},
});
// Kayit icin limit
const registerLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 saat
max: 3, // Saatte 3 kayit
message: {
error: "Cok fazla kayit denemesi.",
},
});
app.use("/api", apiLimiter);
app.post("/api/auth/login", loginLimiter);
app.post("/api/auth/register", registerLimiter);CORS Yapilandirmasi
const cors = require("cors");
// Detayli CORS yapilandirmasi
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = [
process.env.FRONTEND_URL,
"https://myapp.com",
"https://admin.myapp.com",
];
// origin undefined = ayni origin (server-to-server veya Postman)
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("CORS izni yok"));
}
},
methods: ["GET", "POST", "PUT", "PATCH", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
exposedHeaders: ["X-Total-Count", "X-Page-Count"],
credentials: true, // Cookie gonderimine izin ver
maxAge: 86400, // Preflight cache suresi (1 gun)
};
app.use(cors(corsOptions));Zod ile Gelismis Validation
const { z } = require("zod");
// --- Karmasik sema ornegi ---
const registerSchema = z.object({
body: z.object({
name: z.string()
.min(2, "Isim en az 2 karakter")
.max(50, "Isim en fazla 50 karakter")
.regex(/^[a-zA-ZğüşöçıİĞÜŞÖÇ\s]+$/, "Isim sadece harf icermeli"),
email: z.string()
.email("Gecerli email girin")
.toLowerCase(),
password: z.string()
.min(8, "Sifre en az 8 karakter")
.regex(/[A-Z]/, "En az 1 buyuk harf")
.regex(/[a-z]/, "En az 1 kucuk harf")
.regex(/[0-9]/, "En az 1 rakam")
.regex(/[^A-Za-z0-9]/, "En az 1 ozel karakter"),
passwordConfirm: z.string(),
age: z.number().int().min(18).max(120).optional(),
address: z.object({
street: z.string().optional(),
city: z.string().min(2),
country: z.string().length(2, "ISO 3166-1 alpha-2 kodu girin"),
}).optional(),
tags: z.array(z.string()).max(5, "En fazla 5 etiket").optional(),
website: z.string().url("Gecerli URL girin").optional(),
}).refine((data) => data.password === data.passwordConfirm, {
message: "Sifreler eslesmiyor",
path: ["passwordConfirm"],
}),
params: z.object({}).optional(),
query: z.object({}).optional(),
});
// Genel validation middleware
function validate(schema) {
return (req, res, next) => {
const result = schema.safeParse({
body: req.body,
params: req.params,
query: req.query,
});
if (!result.success) {
const errors = result.error.errors.map((e) => ({
field: e.path.join("."),
message: e.message,
}));
return res.status(400).json({
error: "Validation hatasi",
details: errors,
});
}
req.body = result.data.body;
req.params = result.data.params || req.params;
req.query = result.data.query || req.query;
next();
};
}
router.post("/auth/register", validate(registerSchema), authController.register);SQL/NoSQL Injection Korumasi
npm install express-mongo-sanitize hppconst mongoSanitize = require("express-mongo-sanitize");
const hpp = require("hpp");
// NoSQL injection korumasi
// { "email": { "$gt": "" } } gibi saldirilaRi engeller
app.use(mongoSanitize({
replaceWith: "_", // $ ve . karakterlerini _ ile degistir
allowDots: false,
}));
// HTTP Parameter Pollution korumasi
// ?sort=name&sort=email gibi tekrarlanan parametreleri engeller
app.use(hpp({
whitelist: ["filter", "fields", "sort", "page", "limit"],
}));
// --- SQL Injection icin Prisma/ORM kullan ---
// YANLIS: Raw query
// const user = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
// DOGRU: Parametreli sorgu
// const user = await prisma.user.findUnique({ where: { email } });
// Eger raw query gerekiyorsa:
// const user = await prisma.$queryRaw`SELECT * FROM users WHERE email = ${email}`;JWT Best Practices
// --- JWT guvenlik en iyi uygulamalari ---
const jwt = require("jsonwebtoken");
// 1. Guclu secret kullan (en az 256 bit)
// Ornek: openssl rand -hex 32
const JWT_SECRET = process.env.JWT_SECRET; // En az 64 karakter
// 2. Kisa omurlu access token
const accessToken = jwt.sign(
{ id: user.id, role: user.role },
JWT_SECRET,
{
expiresIn: "15m", // Kisa omur
issuer: "my-api", // Token'i kim olusturdu
audience: "my-app", // Token kimin icin
algorithm: "HS256", // Algoritma belirt
}
);
// 3. Refresh token icin ayri secret
const refreshToken = jwt.sign(
{ id: user.id, tokenVersion: user.tokenVersion },
process.env.JWT_REFRESH_SECRET,
{ expiresIn: "7d" }
);
// 4. Token blacklist (cikis yapinca gecersiz kil)
const tokenBlacklist = new Set(); // Production'da Redis kullan
function isTokenBlacklisted(token) {
return tokenBlacklist.has(token);
}
// 5. Token'da hassas bilgi SAKLAMA
// YANLIS: jwt.sign({ password: "123", creditCard: "..." })
// DOGRU: jwt.sign({ id: user.id, role: user.role })
// 6. Token yenileme sirasinda eski token'i gecersiz kil
async function refreshAccessToken(req, res) {
const oldRefreshToken = req.cookies.refreshToken;
// Eski token'i blacklist'e ekle
tokenBlacklist.add(oldRefreshToken);
// Yeni token olustur
const newTokens = generateTokens(user);
// Yeni refresh token'i cookie'ye yaz
res.cookie("refreshToken", newTokens.refreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
});
res.json({ accessToken: newTokens.accessToken });
}bcrypt ile Sifre Hashleme
const bcrypt = require("bcryptjs");
// --- Sifre hashleme ---
const SALT_ROUNDS = 12; // 10-12 onerilen, yuksek = yavas ama guvenli
async function hashPassword(plainPassword) {
return bcrypt.hash(plainPassword, SALT_ROUNDS);
}
// --- Sifre dogrulama ---
async function verifyPassword(plainPassword, hashedPassword) {
return bcrypt.compare(plainPassword, hashedPassword);
}
// --- TIMING ATTACK korumasi ---
// bcrypt.compare zaten sabit zamanli karsilastirma yapar
// Ek koruma: kullanici bulunamazsa bile hash karsilastirmasi yap
async function login(email, password) {
const user = await User.findByEmail(email);
// Kullanici yoksa bile bcrypt calistir (timing attack engelleme)
const dummyHash = "$2b$12$dummy.hash.for.timing.attack.prevention";
const isValid = await bcrypt.compare(
password,
user?.password || dummyHash
);
if (!user || !isValid) {
throw new ApiError(401, "Email veya sifre hatali");
}
return user;
}Cookie Guvenligi
const cookieParser = require("cookie-parser");
app.use(cookieParser());
// --- Guvenli cookie ayarlari ---
res.cookie("token", value, {
httpOnly: true, // JavaScript ile erisilemez (XSS koruması)
secure: true, // Sadece HTTPS uzerinden
sameSite: "strict", // CSRF koruması (strict/lax/none)
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 gun
path: "/", // Cookie yolu
domain: ".myapp.com", // Alt domainler dahil
signed: true, // Cookie imzalama (cookie-parser secret gerekir)
});
// Signed cookie kullanimi
app.use(cookieParser(process.env.COOKIE_SECRET));
res.cookie("userId", "123", { signed: true });
const userId = req.signedCookies.userId; // "123" veya false (tahrifat)
// Cookie silme
res.clearCookie("token", {
httpOnly: true,
secure: true,
sameSite: "strict",
});Güvenlik Checklist
- [ ] helmet() ile HTTP guvenlik basliklarini ayarla
- [ ] CORS'u sadece izinli domainlere ac
- [ ] Rate limiting uygula (genel + login)
- [ ] Tum input'lari Zod/Joi ile dogrula
- [ ] bcrypt ile sifre hashle (salt rounds >= 10)
- [ ] JWT secret en az 64 karakter
- [ ] Access token kisa omurlu (15dk)
- [ ] Refresh token httpOnly cookie'de
- [ ] Cookie'lerde secure + sameSite + httpOnly
- [ ] express-mongo-sanitize ile NoSQL injection engelle
- [ ] hpp ile parameter pollution engelle
- [ ] Hata mesajlarinda stack trace production'da gizle
- [ ] npm audit ile bagimlilik guvenligi tara
- [ ] .env dosyasi .gitignore'da
- [ ] HTTPS kullan (reverse proxy arkasinda)
- [ ] Dosya upload'da tip ve boyut sinirlaSummary: Güvenlik katmanli olmali: helmet (basliklar) + CORS + rate limiting + validation + auth + sanitization. Her katman bir saldiri turunu engeller. Production'da HTTPS sart.
17) Testing
Jest Kurulumu
npm install -D jest supertest @types/jest// package.json
{
"scripts": {
"test": "jest --coverage --forceExit --detectOpenHandles",
"test:watch": "jest --watch"
},
"jest": {
"testEnvironment": "node",
"coverageDirectory": "coverage",
"collectCoverageFrom": ["src/**/*.js", "!src/index.js"]
}
}Vitest Alternatifi
npm install -D vitest// vitest.config.js
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true, // describe, it, expect global
environment: "node",
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "tests/"],
},
setupFiles: ["./tests/setup.js"],
testTimeout: 10000,
},
});// package.json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}
}Unit Test Ornekleri
// tests/utils/apiError.test.js
const ApiError = require("../../src/utils/ApiError");
describe("ApiError", () => {
it("dogru statusCode ve mesaj olusturur", () => {
const error = new ApiError(404, "Bulunamadi");
expect(error.statusCode).toBe(404);
expect(error.message).toBe("Bulunamadi");
expect(error.isOperational).toBe(true);
});
it("static factory metodlari calisir", () => {
const err = ApiError.notFound("User bulunamadi");
expect(err.statusCode).toBe(404);
expect(err.message).toBe("User bulunamadi");
});
it("varsayilan mesajlar calisir", () => {
expect(ApiError.unauthorized().message).toBe("Giris yapmaniz gerekiyor");
expect(ApiError.forbidden().message).toBe("Yetkiniz yok");
});
});// tests/utils/jwt.test.js
const { generateTokens, verifyToken } = require("../../src/utils/jwt");
// .env test degerleri
process.env.JWT_SECRET = "test-secret";
process.env.JWT_REFRESH_SECRET = "test-refresh-secret";
describe("JWT Utils", () => {
const mockUser = { id: "123", email: "test@test.com", role: "user" };
it("access ve refresh token olusturur", () => {
const tokens = generateTokens(mockUser);
expect(tokens).toHaveProperty("accessToken");
expect(tokens).toHaveProperty("refreshToken");
});
it("token verify eder", () => {
const { accessToken } = generateTokens(mockUser);
const decoded = verifyToken(accessToken, process.env.JWT_SECRET);
expect(decoded.id).toBe(mockUser.id);
expect(decoded.email).toBe(mockUser.email);
});
it("gecersiz token'da hata firlatir", () => {
expect(() => verifyToken("invalid-token", process.env.JWT_SECRET)).toThrow();
});
});Integration Test (Supertest)
// tests/integration/auth.test.js
const request = require("supertest");
const mongoose = require("mongoose");
const app = require("../../src/app");
const User = require("../../src/models/User");
describe("Auth Endpoints", () => {
beforeAll(async () => {
await mongoose.connect(process.env.TEST_MONGODB_URI);
});
afterAll(async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
});
beforeEach(async () => {
await User.deleteMany({});
});
describe("POST /api/auth/register", () => {
const validUser = {
name: "Test User",
email: "test@example.com",
password: "Test1234!",
};
it("basarili kayit yapar (201)", async () => {
const res = await request(app)
.post("/api/auth/register")
.send(validUser)
.expect(201);
expect(res.body).toHaveProperty("accessToken");
expect(res.body.user.email).toBe(validUser.email);
expect(res.body.user).not.toHaveProperty("password");
});
it("eksik alan ile 400 doner", async () => {
const res = await request(app)
.post("/api/auth/register")
.send({ name: "Test" })
.expect(400);
expect(res.body.success).toBe(false);
});
it("ayni email ile 409 doner", async () => {
await request(app).post("/api/auth/register").send(validUser);
const res = await request(app)
.post("/api/auth/register")
.send(validUser)
.expect(409);
expect(res.body.error).toContain("zaten");
});
});
describe("POST /api/auth/login", () => {
beforeEach(async () => {
await request(app)
.post("/api/auth/register")
.send({ name: "Test", email: "test@example.com", password: "Test1234!" });
});
it("basarili giris yapar", async () => {
const res = await request(app)
.post("/api/auth/login")
.send({ email: "test@example.com", password: "Test1234!" })
.expect(200);
expect(res.body).toHaveProperty("accessToken");
});
it("yanlis sifre ile 401 doner", async () => {
await request(app)
.post("/api/auth/login")
.send({ email: "test@example.com", password: "wrong" })
.expect(401);
});
});
describe("GET /api/profile (protected)", () => {
it("token olmadan 401 doner", async () => {
await request(app)
.get("/api/profile")
.expect(401);
});
it("gecerli token ile profil doner", async () => {
const registerRes = await request(app)
.post("/api/auth/register")
.send({ name: "Fahri", email: "fahri@test.com", password: "Test1234!" });
const res = await request(app)
.get("/api/profile")
.set("Authorization", `Bearer ${registerRes.body.accessToken}`)
.expect(200);
expect(res.body.user.email).toBe("fahri@test.com");
});
});
});Mocking
// --- Jest ile mocking ---
// Modul mock'lama
jest.mock("../../src/models/User");
const User = require("../../src/models/User");
describe("UserController", () => {
afterEach(() => {
jest.clearAllMocks();
});
it("getUsers: kullanicilari doner", async () => {
const mockUsers = [
{ id: "1", name: "Fahri", email: "fahri@test.com" },
{ id: "2", name: "Ali", email: "ali@test.com" },
];
// Mock davranisini tanimla
User.find.mockReturnValue({
sort: jest.fn().mockReturnValue({
skip: jest.fn().mockReturnValue({
limit: jest.fn().mockResolvedValue(mockUsers),
}),
}),
});
User.countDocuments.mockResolvedValue(2);
const res = await request(app).get("/api/users").expect(200);
expect(res.body.data).toHaveLength(2);
expect(User.find).toHaveBeenCalled();
});
});
// --- Fonksiyon mock ---
const emailService = require("../../src/services/emailService");
jest.spyOn(emailService, "sendWelcomeEmail").mockResolvedValue(true);
// Test icinde
expect(emailService.sendWelcomeEmail).toHaveBeenCalledWith(
expect.objectContaining({ email: "test@test.com" })
);
// --- Manuel mock (dosya bazli) ---
// __mocks__/nodemailer.js
module.exports = {
createTransport: jest.fn().mockReturnValue({
sendMail: jest.fn().mockResolvedValue({ messageId: "test-id" }),
}),
};
// --- Timer mock ---
jest.useFakeTimers();
it("zamanlayici dogru calisir", () => {
const callback = jest.fn();
setTimeout(callback, 5000);
jest.advanceTimersByTime(5000);
expect(callback).toHaveBeenCalledTimes(1);
});
jest.useRealTimers();Test DB Setup/Teardown
// tests/setup.js
const mongoose = require("mongoose");
const { MongoMemoryServer } = require("mongodb-memory-server");
let mongoServer;
// Tum testlerden once: Bellekte MongoDB baslat
beforeAll(async () => {
mongoServer = await MongoMemoryServer.create();
const uri = mongoServer.getUri();
await mongoose.connect(uri);
});
// Her test sonrasi: Collection'lari temizle
afterEach(async () => {
const collections = mongoose.connection.collections;
for (const key in collections) {
await collections[key].deleteMany({});
}
});
// Tum testlerden sonra: Baglanti kapat ve sunucu durdur
afterAll(async () => {
await mongoose.connection.dropDatabase();
await mongoose.connection.close();
await mongoServer.stop();
});npm install -D mongodb-memory-server// --- Prisma test setup (PostgreSQL) ---
// tests/prisma-setup.js
const { PrismaClient } = require("@prisma/client");
const { execSync } = require("child_process");
const prisma = new PrismaClient();
beforeAll(async () => {
// Test DB'ye migration uygula
execSync("npx prisma migrate deploy", {
env: {
...process.env,
DATABASE_URL: process.env.TEST_DATABASE_URL,
},
});
});
afterEach(async () => {
// Tum tablolari temizle (sira onemli: foreign key)
await prisma.$transaction([
prisma.post.deleteMany(),
prisma.user.deleteMany(),
]);
});
afterAll(async () => {
await prisma.$disconnect();
});
module.exports = prisma;CI Entegrasyonu (GitHub Actions)
# .github/workflows/test.yml
name: Test
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
services:
mongo:
image: mongo:7
ports:
- 27017:27017
redis:
image: redis:7
ports:
- 6379:6379
strategy:
matrix:
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v4
- name: Node.js ${{ matrix.node-version }} kur
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Bagimliliklari kur
run: npm ci
- name: Lint calistir
run: npm run lint
- name: Testleri calistir
run: npm test
env:
NODE_ENV: test
TEST_MONGODB_URI: mongodb://localhost:27017/test
JWT_SECRET: test-secret-key-for-ci
JWT_REFRESH_SECRET: test-refresh-secret-for-ci
REDIS_HOST: localhost
REDIS_PORT: 6379
- name: Coverage raporu yukle
uses: codecov/codecov-action@v4
if: matrix.node-version == '22.x'
with:
file: ./coverage/lcov.info
fail_ci_if_error: falseCoverage Hedefleri
// jest.config.js veya package.json
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
},
"src/utils/": {
"branches": 90,
"functions": 90,
"lines": 90
}
}
}
}# Coverage raporu olustur
npm test -- --coverage
# Raporu goruntuLe
open coverage/lcov-report/index.html
# Belirli dosyayi test et
npx jest tests/auth.test.js
# Belirli describe/it calistir
npx jest -t "basarili kayit"Summary: Unit test = izole fonksiyon testi. Integration test = HTTP endpoint testi (supertest). Mock ile bagimliliklari izole et. mongodb-memory-server ile test DB. CI'da otomatik test ve coverage. Hedef: %80+ coverage.
18) Deployment
PM2 (Process Manager)
npm install -g pm2// ecosystem.config.js
module.exports = {
apps: [
{
name: "my-api",
script: "src/index.js",
instances: "max", // CPU cekirdegi kadar instance
exec_mode: "cluster", // Cluster mode
env: {
NODE_ENV: "development",
PORT: 3000,
},
env_production: {
NODE_ENV: "production",
PORT: 8080,
},
max_memory_restart: "500M",
log_date_format: "YYYY-MM-DD HH:mm:ss",
error_file: "logs/error.log",
out_file: "logs/output.log",
merge_logs: true,
autorestart: true,
watch: false,
max_restarts: 10,
restart_delay: 4000,
},
],
};# PM2 komutlari
pm2 start ecosystem.config.js --env production
pm2 list # Calistirilan uygulamalar
pm2 monit # Canli izleme
pm2 logs my-api # Log'lari gor
pm2 restart my-api # Yeniden baslat
pm2 reload my-api # Zero-downtime restart
pm2 stop my-api # Durdur
pm2 delete my-api # Sil
# Startup (sistem baslangicinda otomatik calistir)
pm2 startup
pm2 saveDocker
# Dockerfile
FROM node:22-alpine AS base
WORKDIR /app
COPY package*.json ./
# --- Dependencies stage ---
FROM base AS deps
RUN npm ci --only=production
# --- Build stage (TypeScript icin) ---
FROM base AS build
RUN npm ci
COPY . .
RUN npm run build
# --- Production stage ---
FROM node:22-alpine AS production
WORKDIR /app
# Guvenlik: root olmayan kullanici
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package*.json ./
USER appuser
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1
CMD ["node", "dist/index.js"]# docker-compose.yml
services:
api:
build: .
ports:
- "8080:8080"
env_file:
- .env.production
depends_on:
mongo:
condition: service_healthy
restart: unless-stopped
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
volumes:
mongo_data:# Docker komutlari
docker compose up -d # Baslat
docker compose logs -f api # Log'lari izle
docker compose down # Durdur
docker compose build --no-cache # Yeniden build etProduction Checklist
- [ ] NODE_ENV=production ayarla
- [ ] Tum environment degiskenleri tanimli
- [ ] Guvenlik basliklarI (helmet)
- [ ] CORS dogru yapilandirilmis
- [ ] Rate limiting aktif
- [ ] Input validation tum endpoint'lerde
- [ ] Error stack trace production'da gizli
- [ ] HTTPS aktif (reverse proxy arkasinda)
- [ ] Loglama yapisi hazir (winston/pino)
- [ ] Healthcheck endpoint mevcut
- [ ] Graceful shutdown implementasyonu
- [ ] npm audit temiz
- [ ] .env dosyasi .gitignore'da
- [ ] Test coverage yeterli (>80%)
- [ ] Dockerfile optimized (multi-stage build)
- [ ] PM2 veya container orchestration hazir
- [ ] Monitoring aracı (PM2 monit, Grafana, etc.)
- [ ] Backup stratejisi belirlenmisSummary: PM2 ile cluster mode + auto-restart. Docker ile izole ortam. Production'da güvenlik, loglama, monitoring ihmal etme.
19) Tips & Best Practices
async/await Kullan (Callback Yerine)
// YANLIS: Callback hell
fs.readFile("a.txt", (err, a) => {
if (err) throw err;
fs.readFile("b.txt", (err, b) => {
if (err) throw err;
fs.writeFile("c.txt", a + b, (err) => {
if (err) throw err;
console.log("Tamam");
});
});
});
// DOGRU: async/await
async function mergeFiles() {
const [a, b] = await Promise.all([
fs.promises.readFile("a.txt", "utf-8"),
fs.promises.readFile("b.txt", "utf-8"),
]);
await fs.promises.writeFile("c.txt", a + b);
console.log("Tamam");
}
// Promise.all ile paralel islemler (birbirinden bagimsiz isler)
const [users, posts, comments] = await Promise.all([
User.find(),
Post.find(),
Comment.find(),
]);
// Promise.allSettled ile hatali islemleri de yakala
const results = await Promise.allSettled([
sendEmail(user1),
sendEmail(user2),
sendEmail(user3),
]);
results.forEach((result, i) => {
if (result.status === "rejected") {
console.error(`Email ${i + 1} gonderilemedi:`, result.reason);
}
});Event Loop Blocking'den Kacin
// YANLIS: Event loop'u bloklar
app.get("/api/hash", (req, res) => {
// Bu islem tum diger istekleri bekletir!
const hash = crypto.pbkdf2Sync("password", "salt", 1000000, 64, "sha512");
res.json({ hash: hash.toString("hex") });
});
// DOGRU: Asenkron versiyon kullan
app.get("/api/hash", async (req, res) => {
const hash = await new Promise((resolve, reject) => {
crypto.pbkdf2("password", "salt", 1000000, 64, "sha512", (err, key) => {
if (err) reject(err);
else resolve(key);
});
});
res.json({ hash: hash.toString("hex") });
});
// DAHA IYI: Agir islemleri Worker Thread'e tasi
// (bkz. Clustering & Concurrency bolumu)
// --- Buyuk JSON parse etme ---
// YANLIS: Tek seferde buyuk JSON parse (event loop bloklar)
// const bigData = JSON.parse(hugeString); // 100MB+ string
// DOGRU: Stream ile satir satir isle
// (bkz. Streams bolumu)
// --- Buyuk dongulerden kacin ---
// YANLIS:
// for (let i = 0; i < 10000000; i++) { /* CPU-intensive */ }
// DOGRU: setImmediate ile event loop'a nefes aldIr
function processChunk(items, index, callback) {
const CHUNK_SIZE = 1000;
const end = Math.min(index + CHUNK_SIZE, items.length);
for (let i = index; i < end; i++) {
// islemi yap
}
if (end < items.length) {
setImmediate(() => processChunk(items, end, callback));
} else {
callback();
}
}Memory Leak Tespit ve Onleme
// --- Memory kullanimi izleme ---
function logMemoryUsage() {
const usage = process.memoryUsage();
console.log({
rss: `${(usage.rss / 1024 / 1024).toFixed(2)} MB`, // Toplam bellek
heapTotal: `${(usage.heapTotal / 1024 / 1024).toFixed(2)} MB`,
heapUsed: `${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`,
external: `${(usage.external / 1024 / 1024).toFixed(2)} MB`,
});
}
// Periyodik izleme
setInterval(logMemoryUsage, 30000);
// --- Yaygin memory leak kaynaklari ---
// 1. Event listener temizlenmemesi
// YANLIS:
// emitter.on("data", handler); // Her istekte yeni listener eklenir
// DOGRU:
// emitter.once("data", handler);
// veya disconnect'te: emitter.removeListener("data", handler);
// 2. Global degiskenlerde veri biriktirme
// YANLIS:
// const cache = [];
// app.get("/api", (req, res) => {
// cache.push(req.body); // Surekli buyur!
// });
// DOGRU: Sinirli boyutlu cache veya Redis kullan
// 3. Kapatilmayan stream/connection
// YANLIS:
// const stream = fs.createReadStream("file.txt");
// // stream.destroy() cagirilmadi!
// DOGRU:
// pipeline kullan veya try/finally ile kapat
// --- Chrome DevTools ile debug ---
// node --inspect src/index.js
// Chrome'da chrome://inspect ac
// Memory tab'inda heap snapshot al
// --- Heap snapshot ile analiz ---
const v8 = require("v8");
const fs = require("fs");
function takeHeapSnapshot() {
const snapshotStream = v8.writeHeapSnapshot();
console.log(`Heap snapshot yazildi: ${snapshotStream}`);
}
// API endpoint ile snapshot al
app.get("/debug/heap", (req, res) => {
if (process.env.NODE_ENV !== "development") {
return res.status(403).json({ error: "Sadece development'ta" });
}
const filename = v8.writeHeapSnapshot();
res.json({ snapshot: filename });
});Stream Backpressure
// Backpressure: veri kaynagi veri tuketicisinden hizli oldugunda olusan basinc
// YANLIS: Backpressure'i yoksayar
// readable.on("data", (chunk) => writable.write(chunk));
// DOGRU: pipeline kullan (her zaman)
const { pipeline } = require("stream/promises");
await pipeline(readable, transform, writable);
// VEYA: Manuel kontrol (ender durumlarda)
readable.on("data", (chunk) => {
const ok = writable.write(chunk);
if (!ok) readable.pause();
});
writable.on("drain", () => readable.resume());Environment Degiskenleri (.env + dotenv)
// --- .env dosyasini mumkun olan en erken yukle ---
// src/index.js (ilk satir)
require("dotenv").config();
// --- Farkli ortamlar icin farkli .env ---
// .env -> varsayilan (development)
// .env.test -> test ortami
// .env.production -> production
// Ortama gore yukle
require("dotenv").config({
path: `.env.${process.env.NODE_ENV || "development"}`,
});
// --- Zorunlu degiskenleri kontrol et (startup'ta) ---
const REQUIRED_ENV = ["JWT_SECRET", "DATABASE_URL", "REDIS_HOST"];
for (const key of REQUIRED_ENV) {
if (!process.env[key]) {
console.error(`HATA: ${key} environment degiskeni tanimlanmamis`);
process.exit(1);
}
}
// --- Environment degiskenlerini type-safe yap (Zod ile) ---
const { z } = require("zod");
const envSchema = z.object({
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
REDIS_HOST: z.string().default("127.0.0.1"),
REDIS_PORT: z.coerce.number().default(6379),
});
const env = envSchema.parse(process.env);
// Artik env.PORT number, env.NODE_ENV validasyonluGenel Best Practices Ozeti
// 1. Her zaman async/await kullan (callback yerine)
// 2. Promise.all ile paralel islemleri hizlandir
// 3. Event loop'u bloklama (buyuk dongu, sync I/O, agir hesaplama)
// 4. Stream kullan (buyuk dosyalar, HTTP response)
// 5. Error handling: asyncHandler + global error handler
// 6. Graceful shutdown uygula (SIGTERM, SIGINT)
// 7. Loglama: console.log yerine winston/pino kullan
// 8. Environment degiskenlerini startup'ta dogrula
// 9. .gitignore: .env, node_modules, coverage, dist
// 10. package-lock.json'i commit et (tekrarlanabilir build)
// 11. npm audit ile guvenlik tara
// 12. TypeScript kullan (type safety)
// 13. Lint + format: eslint + prettier
// 14. Test yaz: unit + integration + %80 coverage
// 15. Docker ile ortam standardize etSummary: async/await kullan, event loop'u bloklama, memory leak'lere dikkat et, stream backpressure'i yonet, .env dosyalarini doğrula. Node.js'te performansin anahtari event loop'u temiz tutmak.
20) Sik Kullanilan Paketler Tablosu
| Paket | Kategori | Açıklama | Kurulum |
|---|---|---|---|
express | Framework | Web framework | npm i express |
cors | Güvenlik | Cross-Origin Resource Sharing | npm i cors |
helmet | Güvenlik | HTTP güvenlik basliklari | npm i helmet |
morgan | Loglama | HTTP request logger | npm i morgan |
winston | Loglama | Gelismis loglama kutuphanesi | npm i winston |
pino | Loglama | Yüksek performansli logger | npm i pino |
compression | Performans | Gzip/Brotli sIkistirma | npm i compression |
express-rate-limit | Güvenlik | Rate limiting | npm i express-rate-limit |
dotenv | Config | Environment degiskenleri | npm i dotenv |
jsonwebtoken | Auth | JWT token islemleri | npm i jsonwebtoken |
bcryptjs | Auth | Sifre hashleme | npm i bcryptjs |
zod | Validation | Schema-based validation | npm i zod |
joi | Validation | Object schema validation | npm i joi |
mongoose | Database | MongoDB ODM | npm i mongoose |
prisma | Database | Type-safe ORM (PostgreSQL, MySQL) | npm i prisma @prisma/client |
ioredis | Cache | Redis client | npm i ioredis |
multer | Upload | Dosya yükleme middleware | npm i multer |
sharp | Gorsel | Gorsel işleme/boyutlandirma | npm i sharp |
nodemailer | Email gonderme | npm i nodemailer | |
socket.io | Real-time | WebSocket iletisimi | npm i socket.io |
bull | Queue | Redis-based is kuyrugu | npm i bull |
cron | Zamanlama | Cron job zamanlayici | npm i cron |
axios | HTTP | HTTP client (server-side requests) | npm i axios |
uuid | Utility | Unique ID oluşturma | npm i uuid |
dayjs | Tarih | Tarih/saat islemleri (hafif) | npm i dayjs |
lodash | Utility | Genel araclar (pick, merge, etc.) | npm i lodash |
swagger-ui-express | Dokumantasyon | API dokumanasyonu | npm i swagger-ui-express |
express-validator | Validation | Express için validation | npm i express-validator |
cookie-parser | Utility | Cookie parse etme | npm i cookie-parser |
hpp | Güvenlik | HTTP Parameter Pollution korumasi | npm i hpp |
express-mongo-sanitize | Güvenlik | NoSQL injection korumasi | npm i express-mongo-sanitize |
chokidar | Utility | Dosya sistemi izleme | npm i chokidar |
connect-redis | Session | Redis session store | npm i connect-redis |
ws | Real-time | Hafif WebSocket kutuphanesi | npm i ws |
vitest | Test | Hızlı test framework | npm i -D vitest |
Tipik Proje Kurulusu
# Yeni Express API projesi baslangic paketi
npm init -y
npm install express cors helmet morgan compression dotenv \
jsonwebtoken bcryptjs zod mongoose cookie-parser
npm install -D nodemon jest supertest eslint prettier21) Hızlı Referans
npm Komutları Tablosu
| Komut | Açıklama |
|---|---|
npm init -y | Yeni proje baslat (varsayılan) |
npm install | Tüm bagimliliklar kur |
npm install <pkg> | Paket ekle (dependencies) |
npm install -D <pkg> | Dev dependency ekle |
npm install -g <pkg> | Global paket kur |
npm uninstall <pkg> | Paket kaldir |
npm update | Paketleri güncelle |
npm outdated | Guncellenecek paketleri gor |
npm audit | Güvenlik taramasi |
npm audit fix | Güvenlik sorunlarini duzelt |
npm run <script> | Script çalıştır |
npm test | Test çalıştır |
npm start | Uygulamayi baslat |
npx <cmd> | Paket kurmadan çalıştır |
npm list --depth=0 | Kurulu paketleri listele |
npm cache clean --force | Cache temizle |
npm pack | Tarball oluştur |
npm version patch | Patch sürüm artir |
npm version minor | Minor sürüm artir |
npm publish | npm'e yayinla |
Node.js Yerlesik Global Nesneler
| Nesne / Fonksiyon | Açıklama |
|---|---|
__dirname | Mevcut dosyanin klasor yolu (CJS) |
__filename | Mevcut dosyanin tam yolu (CJS) |
process.env | Environment degiskenleri |
process.argv | Komut satiri argumanlari |
process.cwd() | Calisma dizini |
process.exit(code) | Uygulamayi sonlandir |
process.pid | İşlem ID |
process.memoryUsage() | Bellek kullanimi |
Buffer.from(str) | Binary data islemleri |
setTimeout / setInterval | Zamanlayicilar |
setImmediate | Event loop'un sonraki check fazinda çalıştır |
console.time(label) | Performans olcumu baslat |
console.timeEnd(label) | Performans olcumu bitir |
console.table(arr) | Tablo formatinda log |
structuredClone(obj) | Derin kopya (deep clone) |
HTTP Durum Kodlari Referansi
| Kod | Anlam | Kullanım |
|---|---|---|
200 | OK | Basarili GET/PUT |
201 | Created | Basarili POST (yeni kaynak) |
204 | No Content | Basarili DELETE |
301 | Moved Permanently | Kalici yonlendirme |
304 | Not Modified | Cache kullan |
400 | Bad Request | Validation hatasi |
401 | Unauthorized | Giris yapilmamis |
403 | Forbidden | Yetki yetersiz |
404 | Not Found | Kaynak bulunamadi |
409 | Conflict | Cakisma (duplicate) |
422 | Unprocessable Entity | Isleme alinabilir ama mantiksal hata |
429 | Too Many Requests | Rate limit asildi |
500 | Internal Server Error | Sunucu hatasi |
502 | Bad Gateway | Upstream sunucu hatasi |
503 | Service Unavailable | Servis kullanım disi |
Express Middleware Sirasi (Önerilen)
// 1. Guvenlik
app.use(helmet());
app.use(cors(corsOptions));
app.use(hpp());
app.use(mongoSanitize());
// 2. Rate Limiting
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
// 3. Loglama
app.use(morgan("combined"));
// 4. Body Parsing
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());
// 5. Sikistirma
app.use(compression());
// 6. Static Dosyalar
app.use(express.static("public"));
// 7. Route'lar
app.use("/api", routes);
// 8. 404 Handler
app.use((req, res) => {
res.status(404).json({ error: "Not Found" });
});
// 9. Global Error Handler (EN SON)
app.use(errorHandler);Faydali Tek Satirlar
// Port'u al veya varsayilan kullan
const PORT = process.env.PORT || 3000;
// Async hatalari yakala (Express 5 oncesi)
const wrap = (fn) => (req, res, next) => fn(req, res, next).catch(next);
// Benzersiz ID olustur (uuid olmadan)
const uid = () => Date.now().toString(36) + Math.random().toString(36).slice(2);
// Object'ten bos alanlari temizle
const clean = (obj) => Object.fromEntries(Object.entries(obj).filter(([, v]) => v != null));
// ms'yi okunabilir formata cevir
const ms = (ms) => `${(ms / 1000).toFixed(2)}s`;
// Basit retry mekanizmasi
async function retry(fn, retries = 3, delay = 1000) {
for (let i = 0; i < retries; i++) {
try { return await fn(); }
catch (err) {
if (i === retries - 1) throw err;
await new Promise((r) => setTimeout(r, delay * (i + 1)));
}
}
}
// Sleep utility
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));Laravel -> Node.js Kavram Eslestirmesi
| Laravel | Node.js / Express |
|---|---|
php artisan serve | npm run dev (nodemon) |
Route::get() | app.get() / router.get() |
| Middleware | app.use() / route middleware |
| Controller | Controller dosyaları (ayni mantik) |
| Eloquent ORM | Mongoose / Prisma / Sequelize |
| Migration | Prisma migrate / Knex migrate |
| FormRequest | Zod / Joi validation middleware |
.env | .env + dotenv paketi (ayni!) |
php artisan tinker | node REPL |
| Blade template | EJS / Pug (veya React SPA) |
| Laravel Mix / Vite | Vite / Webpack |
| Queue (Redis) | Bull / BullMQ |
| Scheduler | node-cron / cron paketi |
| Sanctum / Passport | JWT + bcrypt / Passport.js |
| Storage facade | fs modulu + multer |
| Composer | npm / yarn / pnpm |
composer.json | package.json |
vendor/ | node_modules/ |
| PHPUnit | Jest + Supertest |
Son Soz: Node.js, Laravel bilen bir gelistirici için cok tanidik kavramlar sunar. En büyük fark: JavaScript'in asenkron dogasi ve callback/promise yapisi.
async/awaitkullan, hata yonetimini ihmal etme, güvenlik middleware'lerini uygula. Basarilar!
Ilgili Rehberler
Backend
- Backend Genel Bakış
- Yazilim Mimarisi
- API Development
- Laravel Rehberi
- ASP.NET Core Guide
- Python Rehberi
- AI & LLM