REST API dizayni


ULASHISH

RESTful API-larni tushunish

REST (Representational State Transfer) tarmoq ilovalarini loyihalash uchun arxitektura uslubi bo‘lib, veb-xizmatlar uchun standartga aylangan.

RESTful API-lar ilovalarni integratsiya qilish va turli tizimlar o‘rtasida aloqa o‘rnatish uchun moslashuvchan, yengil usulni taqdim etadi.

Asosiy tushunchalar:

  • Resurslar: Hamma narsa resurs (foydalanuvchi, mahsulot, buyurtma)
  • Vakilliklar: Resurslar bir nechta ko‘rinishga ega bo‘lishi mumkin (JSON, XML va h.k.)
  • Stateless: Har bir so‘rov barcha kerakli ma’lumotlarni o‘z ichiga oladi
  • Yagona interfeys: Resurslarga kirish va ularni boshqarishning izchil usuli

RESTful API’lar URL’lar ko‘rinishida ifodalangan resurslar ustida CRUD amallarini (Create, Read, Update, Delete) bajarish uchun HTTP so‘rovlaridan foydalanadi.

REST stateless (ichki state saqlamaydigan) (stateless) hisoblanadi, ya’ni mijozdan serverga yuborilgan har bir so‘rov so‘rovni tushunish va qayta ishlash uchun zarur bo‘lgan barcha ma’lumotlarni o‘z ichiga olishi kerak.

SOAP yoki RPC dan farqli o‘laroq, REST protokol emas, balki HTTP, URI, JSON va XML kabi mavjud veb-standartlardan foydalanadigan me’moriy uslubdir.


Asosiy REST tamoyillari

Ushbu tamoyillarni tushunish samarali RESTful API-larni loyihalash uchun juda muhimdir.

Ular sizning API kengayishi, xizmat ko‘rsatishi va ulardan foydalanish osonligini ta’minlaydi.

Amaldagi asosiy tamoyillar:

  • Resursga asoslangan: E’tiborni harakatlardan ko‘ra resurslarga qarating
  • Stateless: Har bir so‘rov mustaqil va o‘zi uchun kerakli hamma narsaga ega
  • Keshlanadi: Javoblar keshlanishini belgilaydi
  • Yagona interfeys: Izchil resurs identifikatsiyasi va manipulyatsiyasi
  • Qatlamli tizim: Mijoz asosiy arxitektura haqida bilishi shart emas

REST arxitekturasining asosiy tamoyillariga quyidagilar kiradi:

  1. Mijoz-server arxitekturasi: Mijoz va server o‘rtasidagi tashvishlarni ajratish
  2. Stateless: so‘rovlar o‘rtasida serverda mijoz konteksti saqlanmaydi
  3. Keshlash: Javoblar keshlanadigan yoki keshlanmaydigan qilib belgilanishi kerak
  4. Qatlamli tizim: Mijoz to‘g‘ridan-to‘g‘ri oxirgi serverga ulanganligini aniqlay olmaydi
  5. Yagona interfeys: resurslar so‘rovlarda identifikatsiya qilinadi, resurslar ustidagi amallar taqdimotlar (representations) orqali bajariladi, xabarlar o‘zini-o‘zi tavsiflaydi, shuningdek HATEOAS (Hypertext As The Engine Of Application State — gipermatn ilova holatini boshqaruvchi vosita sifatida) qo‘llaniladi.

HTTP usullari va ulardan foydalanish

RESTful API’lari resurslar ustida operatsiyalarni bajarish uchun standart HTTP usullaridan foydalanadi.

Har bir usul o‘ziga xos semantikaga ega va ulardan to‘g‘ri foydalanish kerak.

Potentsiya va xavfsizlik:

  • Xavfsiz usullar: GET, HEAD, OPTIONS (resurslarni o‘zgartirmaslik kerak)
  • Idempotent usullar: GET, PUT, DELETE (bir nechta bir xil so‘rovlar = bir xil ta’sir)
  • Idempotent bo‘lmagan: POST, PATCH (bir necha marta chaqirilganda turli natijalar berishi mumkin)

Har doim operatsiya maqsadiga mos keladigan eng aniq usuldan foydalaning.

Metod Harakat Misol
GET Retrieve resource(s) GET /api/users
POST Yangi resurs yarating POST /api/users
PUT Resursni to‘liq yangilang PUT /api/users/123
PATCH Resursni qisman yangilang PATCH /api/users/123
DELETE Resursni o‘chirish O‘CHIRISH /api/users/123

Misol: Turli xil HTTP usullaridan foydalanish

const express = require('express');
const app = express();

// Middleware for parsing JSON
app.use(express.json());

let users = [
  { id: 1, name: 'John Doe', email: 'john@example.com' },
  { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
];

// GET - Retrieve all users
app.get('/api/users', (req, res) => {
  res.json(users);
});

// GET - Retrieve a specific user
app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ message: 'User not found' });
  res.json(user);
});

// POST - Create a new user
app.post('/api/users', (req, res) => {
  const newUser = {
    id: users.length + 1,
    name: req.body.name,
    email: req.body.email
  };
  users.push(newUser);
  res.status(201).json(newUser);
});

// PUT - Update a user completely
app.put('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  if (!user) return res.status(404).json({ message: 'User not found' });

  user.name = req.body.name;
  user.email = req.body.email;

  res.json(user);
});

// DELETE - Remove a user
app.delete('/api/users/:id', (req, res) => {
  const userIndex = users.findIndex(u => u.id === parseInt(req.params.id));
  if (userIndex === -1) return res.status(404).json({ message: 'User not found' });

  const deletedUser = users.splice(userIndex, 1);
  res.json(deletedUser[0]);
});

app.listen(8080, () => {
  console.log('REST API server running on port 8080');
});


RESTful API tuzilishi va dizayni

Yaxshi ishlab chiqilgan API uni intuitiv va foydalanishni osonlashtiradigan izchil naqshlarga amal qiladi. Yaxshi API dizayni ishlab chiquvchilar tajribasi va uzoq muddatli xizmat ko‘rsatish uchun juda muhimdir.

Dizayn masalalari:

  • Resurs nomlash: Fe’llarni emas, otlarni ishlating (masalan, /users emas /getUsers )
  • Ko‘plik: To‘plamlar uchun ko‘plikdan foydalaning (/users/123 emas /user/123 )
  • Ierarxiya: O‘zaro munosabatlarni ko‘rsatish uchun manbalarni joylashtirish ( /users/123/orders )
  • Filtrlash/Saralash: Ixtiyoriy amallar uchun so‘rov parametrlaridan foydalaning
  • Versiya yaratish strategiyasi: API versiyalarini boshidan rejalashtirish (masalan, /v1/users va /v2/users ).

Yaxshi tuzilgan API quyidagi konventsiyalarga amal qiladi:

  • Resurslar uchun otlardan foydalaning: /users, /products, /orders (emas /getUsers)
  • To‘plamlar uchun ko‘plikdan foydalaning: /user o‘rniga /users
  • Aloqalar uchun Nest resurslari: /users/123/orders
  • Filtrlash uchun so‘rov parametrlaridan foydalaning: /products?category=electronics&min_price=100
  • URL manzillarini izchil saqlang: Konventsiyani tanlang (kabob qutisi, camelCase) va unga rioya qiling

Misol: Yaxshi tuzilgan API marshrutlari

// Good API structure
app.get('/api/products', getProducts);
app.get('/api/products/:id', getProductById);
app.get('/api/products/:id/reviews', getProductReviews);
app.get('/api/users/:userId/orders', getUserOrders);
app.post('/api/orders', createOrder);

// Filtering and pagination
app.get('/api/products?category=electronics&sort=price&limit=10&page=2');

Node.js va Express bilan REST API yaratish

Express.js bilan Node.js RESTful API yaratish uchun ajoyib asos yaratadi.

Quyidagi bo‘limlarda eng yaxshi amaliyotlar va amalga oshirish naqshlari ko‘rsatilgan.

Asosiy komponentlar:

  • Express Router: Marshrutlarni tashkil qilish uchun
  • O‘rta dastur: O‘zaro bog‘liqlik uchun
  • Boshqaruvchilar: So‘rov mantig‘ini qayta ishlash uchun
  • Modellar: Ma’lumotlarga kirish va biznes mantiqi uchun
  • Xizmatlar: Murakkab biznes mantiqi uchun

Express.js Node.js da REST API yaratish uchun eng mashhur ramka hisoblanadi.

Bu yerda asosiy loyiha tuzilishi:

Loyiha tuzilishi

- app.js # Main application file
- routes/ # Route definitions
  - users.js
  - products.js
- controllers/ # Request handlers
  - userController.js
  - productController.js
- models/ # Data models
  - User.js
  - Product.js
- middleware/ # Custom middleware
  - auth.js
  - validation.js
- config/ # Configuration files
  - db.js
  - env.js
- utils/ # Utility functions
  - errorHandler.js

Misol: Express Routerni sozlash

// routes/users.js
const express = require('express');
const router = express.Router();
const { getUsers, getUserById, createUser, updateUser, deleteUser } = require('../controllers/userController');

router.get('/', getUsers);
router.get('/:id', getUserById);
router.post('/', createUser);
router.put('/:id', updateUser);
router.delete('/:id', deleteUser);

module.exports = router;
// app.js
const express = require('express');
const app = express();
const userRoutes = require('./routes/users');

app.use(express.json());
app.use('/api/users', userRoutes);

app.listen(8080, () => {
  console.log('Server is running on port 8080');
});

Nazoratchilar va modellar

Marshrutlar, kontrollerlar va modellar o‘rtasidagi tashvishlarni ajratish kodni tashkil etish va barqarorlikni yaxshilaydi:

Misol: Controller Implementation

// controllers/userController.js
const User = require('../models/User');

const getUsers = async (req, res) => {
  try {
    const users = await User.findAll();
    res.status(200).json(users);
  } catch (error) {
    res.status(500).json({ message: 'Error retrieving users', error: error.message });
  }
};

const getUserById = async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }
    res.status(200).json(user);
  } catch (error) {
    res.status(500).json({ message: 'Error retrieving user', error: error.message });
  }
};

const createUser = async (req, res) => {
  try {
    const user = await User.create(req.body);
    res.status(201).json(user);
  } catch (error) {
    res.status(400).json({ message: 'Error creating user', error: error.message });
  }
};

module.exports = { getUsers, getUserById, createUser };

API versiyasi

Versiyalash mavjud mijozlarni buzmasdan API-ni rivojlantirishga yordam beradi.

Umumiy yondashuvlarga quyidagilar kiradi:

  • URI yo‘li versiyasi: /api/v1/users
  • So‘rov parametri: /api/users?version=1
  • Maxsus sarlavha: X-API-versiyasi: 1
  • Sarlavhani qabul qilish: Qabul qilish: application/vnd.myapi.v1+json

Misol: URI yo‘li versiyasi

const express = require('express');
const app = express();

// Version 1 routes
const v1UserRoutes = require('./routes/v1/users');
app.use('/api/v1/users', v1UserRoutes);

// Version 2 routes with new features
const v2UserRoutes = require('./routes/v2/users');
app.use('/api/v2/users', v2UserRoutes);

app.listen(8080);

Tasdiqlashni so‘rash

Ma’lumotlar yaxlitligi va xavfsizligini ta’minlash uchun har doim kiruvchi so‘rovlarni tasdiqlang.

Joi yoki express-validator kabi kutubxonalar yordam berishi mumkin:

Misol: Joi bilan tekshirishni so‘rash

const express = require('express');
const Joi = require('joi');
const app = express();

app.use(express.json());

// Validation schema
const userSchema = Joi.object({
  name: Joi.string().min(3).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(18).max(120)
});

app.post('/api/users', (req, res) => {
  // Validate request body
  const { error } = userSchema.validate(req.body);
  if (error) {
    return res.status(400).json({ message: error.details[0].message });
  }

  // Process valid request
  // ...
  res.status(201).json({ message: 'User created successfully' });
});

app.listen(8080);

Xato bilan ishlash

API iste’molchilariga aniq fikr-mulohazalarni taqdim etish uchun izchil xatolarni qayta ishlashni amalga oshiring:

Misol: Xatolarni markazlashtirilgan hal qilish

// utils/errorHandler.js
class AppError extends Error {
  constructor(statusCode, message) {
    super(message);
    this.statusCode = statusCode;
    this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
    this.isOperational = true;

    Error.captureStackTrace(this, this.constructor);
  }
}

module.exports = { AppError };

// middleware/errorMiddleware.js
const errorHandler = (err, req, res, next) => {
  err.statusCode = err.statusCode || 500;
  err.status = err.status || 'error';

  // Different error responses for development and production
  if (process.env.NODE_ENV === 'development') {
    res.status(err.statusCode).json({
      status: err.status,
      message: err.message,
      stack: err.stack,
      error: err
    });
  } else {
    // Production: don't leak error details
    if (err.isOperational) {
      res.status(err.statusCode).json({
        status: err.status,
        message: err.message
      });
    } else {
      // Programming or unknown errors
      console.error('ERROR 💥', err);
      res.status(500).json({
        status: 'error',
        message: 'Something went wrong'
      });
    }
  }
};

module.exports = { errorHandler };

// Usage in app.js
const { errorHandler } = require('./middleware/errorMiddleware');
const { AppError } = require('./utils/errorHandler');

// This route throws a custom error
app.get('/api/error-demo', (req, res, next) => {
  next(new AppError(404, 'Resource not found'));
});

// Error handling middleware (must be last)
app.use(errorHandler);

API hujjatlari

APIni qabul qilish uchun yaxshi hujjatlar zarur.

Swagger/OpenAPI kabi vositalar koddan hujjatlarni avtomatik ravishda yaratishi mumkin:

Misol: Swagger Documentation

const express = require('express');
const swaggerJsDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const app = express();

// Swagger configuration
const swaggerOptions = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: 'User API',
      version: '1.0.0',
      description: 'A simple Express User API'
    },
    servers: [
      {
        url: 'http://localhost:8080',
        description: 'Development server'
      }
    ]
  },
  apis: ['./routes/*.js'] // Path to the API routes folders
};

const swaggerDocs = swaggerJsDoc(swaggerOptions);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs));

/**
* @swagger
* /api/users:
* get:
* summary: Returns a list of users
* description: Retrieve a list of all users
* responses:
* 200:
* description: A list of users
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* email:
* type: string
*/
app.get('/api/users', (req, res) => {
  // Handler implementation
});

app.listen(8080);

Sinov API

Sinov API ishonchliligi uchun juda muhimdir.

Jest, Mocha yoki Supertest kabi kutubxonalardan foydalaning:

Misol: Jest va Supertest bilan API testi

// tests/users.test.js
const request = require('supertest');
const app = require('../app');

describe('User API', () => {
  describe('GET /api/users', () => {
    it('should return all users', async () => {
      const res = await request(app).get('/api/users');
      expect(res.statusCode).toBe(200);
      expect(Array.isArray(res.body)).toBeTruthy();
    });
  });
  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com'
      };
      const res = await request(app)
        .post('/api/users')
        .send(userData);

      expect(res.statusCode).toBe(201);
      expect(res.body).toHaveProperty('id');
      expect(res.body.name).toBe(userData.name);
    });
    it('should validate request data', async () => {
      const invalidData = {
        email: 'not-an-email'
      };
      const res = await request(app)
        .post('/api/users')
        .send(invalidData);

      expect(res.statusCode).toBe(400);
    });
  });
});

Eng yaxshi amaliyotlar xulosasi

  • REST tamoyillariga amal qiling va tegishli HTTP usullaridan foydalaning
  • Oxirgi nuqtalar uchun doimiy nomlash qoidalaridan foydalaning
  • Resursga asoslangan URL manzillar bilan API-ni mantiqiy ravishda tuzing
  • Javoblarda tegishli holat kodlarini qaytaring
  • Aniq xabarlar bilan xatolarni to‘g‘ri hal qilishni amalga oshiring
  • Katta ma’lumotlar to‘plamlari uchun sahifalashdan foydalaning
  • Orqaga qarab muvofiqlikni saqlash uchun API versiyasini qiling
  • Xavfsizlik bilan bog‘liq muammolarni oldini olish uchun barcha kiritilgan ma’lumotlarni tasdiqlang
  • API-ni hujjatlashtiring
  • Ishonchliligini ta’minlash uchun Ko‘p tomonlama testlarni yozing
  • Barcha ishlab chiqarish API’lari uchun HTTPS dan foydalaning
  • Suiiste’molning oldini olish uchun stavkani cheklashni amalga oshiring


W3Schools Pathfinder

Yutuqlaringizni kuzating – bu bepul!