Error Middleware — 4 parametrli, 404 handler

~15 daq · Express · Middleware

Muammodan boshlaylik

Har bir route da xatolarni alohida boshqarish:

app.get('/users/:id', async (req, res) => {
  try {
    const user = await db.findUser(req.params.id);
    res.json(user);
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server xatosi' }); // takror
  }
});

app.get('/products', async (req, res) => {
  try {
    const products = await db.getProducts();
    res.json(products);
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: 'Server xatosi' }); // yana takror
  }
});

50 ta route bo'lsa — 50 ta joyda bir xil catch bloki. Xato xabarini o'zgartirmoqchi bo'lsangiz — 50 ta joyni o'zgartirish kerak.

Error middleware bu muammoni hal qiladi: xatolarni bir markaziy joyda boshqarish.


Error Middleware nima?

Oddiy middleware uchta argument oladi: req, res, next. Error middleware esa to'rtta: err, req, res, next. Birinchi argument — xato ob'ekti.

Express bu ikkalasini qanday farqlaydi? Faqat parametrlar soniga qarab. To'rtta parametr bo'lsa — error middleware. Uchtasi bo'lsa — oddiy middleware. Shuning uchun next ishlatmasangiz ham — yozilishi shart.

Qanday ishlaydi? Istalgan joyda next(err) chaqirilsa — Express barcha oddiy middleware va route larni o'tkazib yuborib, to'g'ridan-to'g'ri error middleware ga o'tadi. Xuddi "favqulodda chiqish" kabi — oddiy yo'lni tashlab, maxsus yo'ldan boradi.

So'rov → middleware → route → next(err)
                                  ↓
                         (boshqa route lar o'tkazib yuboriladi)
                                  ↓
                          Error Middleware
                                  ↓
                             Javob yuboriladi

Eng oddiy error middleware

app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Server xatosi' });
});

Bu funksiya faqat next(err) chaqirilganda ishlaydi — oddiy so'rovlarda emas.

Muhim qoida: error middleware barcha route lardan keyin yoziladi. Oldin yozilsa — route lardan kelgan xatolar unga yetmaydi.


Route da xatoni uzatish

Endi har bir route da catch bloki yozish o'rniga — next(err) bilan markaziy error middleware ga uzatiladi:

app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await db.findUser(req.params.id);
    if (!user) return res.status(404).json({ message: 'Topilmadi' });
    res.json(user);
  } catch (err) {
    next(err); // error middleware ga uzatish — boshqa hech narsa yozmasdan
  }
});

app.get('/products', async (req, res, next) => {
  try {
    const products = await db.getProducts();
    res.json(products);
  } catch (err) {
    next(err); // bir xil — markaziy joyga uzatiladi
  }
});

// Barcha xatolar shu yerda boshqariladi:
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ message: 'Server xatosi' });
});

Endi xato xabarini o'zgartirmoqchi bo'lsangiz — faqat bir joyni o'zgartirasiz.


Turli xatolar uchun turli javob

Hamma xato bir xil emas. Ma'lumotlar bazasiga ulanib bo'lmasa — 500. Foydalanuvchi topilmasa — 404. Token yaroqsiz bo'lsa — 401.

Buning uchun maxsus xato sinfi yaratiladi. G'oya oddiy: xato ob'ektiga statusCode qo'shiladi, error middleware uni o'qib to'g'ri status qaytaradi.

class AppError extends Error {
  constructor(message, statusCode) {
    super(message);
    this.statusCode = statusCode;
  }
}

Endi route da:

app.get('/users/:id', async (req, res, next) => {
  try {
    const user = await db.findUser(+req.params.id);
    if (!user) throw new AppError('Foydalanuvchi topilmadi', 404);
    res.json(user);
  } catch (err) {
    next(err);
  }
});

Error middleware esa statusCode bor-yo'qligiga qarab farqlaydi:

app.use((err, req, res, next) => {
  // AppError — biz ataylab chiqargan xato
  if (err.statusCode) {
    return res.status(err.statusCode).json({ message: err.message });
  }

  // Kutilmagan xato — ma'lumotlar bazasi, tarmoq va h.k.
  console.error(err.stack); // faqat kutilmagan xatolarni log qilamiz
  res.status(500).json({ message: 'Server xatosi' });
});

Bu farq muhim: AppError — biz bilgan holat (topilmadi, ruxsat yo'q). Kutilmagan xato — biz bilmagan holat (server muammosi). Ikkinchisi log qilinadi, birinchisi emas.


404 handler — topilmagan route

Foydalanuvchi /api/userss (xato yozilgan) ga so'rov yubordi. Hech bir route mos kelmaydi. Express standart bo'yicha HTML sahifa qaytaradi — API uchun bu noto'g'ri.

404 handler — barcha route lardan keyin, error middleware dan oldin yoziladi. Bu oddiy middleware — next(err) emas, to'g'ridan-to'g'ri javob yuboradi:

// Barcha route lar...

// 404 — hech bir route mos kelmadi:
app.use((req, res) => {
  res.status(404).json({
    message: `${req.method} ${req.url} — bunday endpoint yo'q`,
  });
});

// Error middleware — eng oxirida:
app.use((err, req, res, next) => {
  if (err.statusCode) {
    return res.status(err.statusCode).json({ message: err.message });
  }
  console.error(err.stack);
  res.status(500).json({ message: 'Server xatosi' });
});

asyncHandler — try/catch ni qisqartirish

Har bir asinxron route da try/catch + next(err) yozish zerikarli. Yordamchi funksiya bu ishni avtomatik qiladi:

const asyncHandler = fn => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

Bu funksiya nima qiladi? Asinxron funksiyani o'rab oladi. Xato chiqsa — avtomatik next(err) chaqiradi. Siz try/catch yozmasangiz ham bo'ladi:

// try/catch siz:
app.get('/users/:id', asyncHandler(async (req, res) => {
  const user = await db.findUser(+req.params.id);
  if (!user) throw new AppError('Topilmadi', 404);
  res.json(user);
}));

Xato chiqsa — asyncHandler uni tutib, next(err) ga uzatadi. Error middleware ishlaydi.


Tez-tez uchraydigan xatolar

Error middleware ni route lardan oldin qo'yish:

Bu eng ko'p uchraydigan xato. Error middleware route lardan oldin bo'lsa — route lardan kelgan xatolar unga yetmaydi, chunki Express uni oddiy middleware deb qabul qiladi.

next parametrini yozmaslik:

app.use((err, req, res) => { ... }); // 3 parametr — Express bu ni error middleware deb tanımaydi!

Express parametrlar soniga qarab farqlaydi. next ishlatilmasa ham — yozilishi shart.

Asinxron xatoni next ga uzatmaslik:

app.use(async (req, res, next) => {
  const data = await fetchData(); // xato chiqsa — Express ushlamaydi
  next();
});

Asinxron middleware da try/catch + next(err) yoki asyncHandler kerak.


Xulosa

Error middleware — to'rtta parametrli (err, req, res, next) funksiya. Barcha route lardan keyin yoziladi. next(err) chaqirilganda ishlaydi.

Foyda: xatolarni bir markaziy joyda boshqarish. 50 ta route da bir xil catch bloki o'rniga — bitta error middleware.

AppError sinfi — turli xatolar uchun turli status kodlar. 404 handler — topilmagan route lar uchun. asyncHandler — try/catch ni qisqartirish uchun.


Keyingi dars

12-dars: Router — alohida fayllarga bo'lish, modullar