Перейти к содержимому

Лекция 5. REST API: проектирование веб-сервисов

Раздел 2. Разработка интернет-приложений (backend, Python/FastAPI) Длительность пары: ~1 ч 30 мин


План лекции

  1. Что такое API
  2. Архитектурный стиль REST и его принципы
  3. Ресурсы и проектирование URI
  4. HTTP-методы и сопоставление с CRUD
  5. Коды состояния HTTP
  6. Формат данных и согласование контента

1. Что такое API

API (Application Programming Interface) — интерфейс программирования приложений, набор правил и соглашений, по которым одни программные компоненты обращаются к другим.

В контексте веб-приложений API — это набор эндпойнтов (точек входа), через которые клиент запрашивает данные и выполняет операции на сервере. Клиенту не нужно знать, как сервер устроен внутри: он работает только с описанным «контрактом».

Ключевые характеристики веб-API:

  • Стандартизированный формат обмена данными (чаще всего JSON);
  • Протокол HTTP для передачи запросов и ответов;
  • Чётко определённый контракт — формат запросов и ответов известен заранее;
  • Независимость от платформы клиента — API одинаково работает с веб-сайтом, мобильным приложением или другим сервисом.

Где применяется:

  • мобильные приложения получают данные с сервера;
  • веб-фронтенд (SPA) обращается к backend-сервисам;
  • микросервисы обмениваются данными друг с другом;
  • интеграция с внешними системами (платежи, карты, соцсети).

Аналогия: API — это «меню в ресторане». Вы выбираете блюдо по названию и не видите, что происходит на кухне. Меню — контракт, кухня — реализация.


2. Архитектурный стиль REST

REST (Representational State Transfer) — архитектурный стиль для проектирования распределённых систем, предложенный Роем Филдингом в 2000 году в его диссертации.

Важно понимать: REST — это не протокол и не стандарт, а набор ограничений (constraints). API, который им соответствует, называют RESTful. Соблюдение этих ограничений делает сервис простым, масштабируемым и предсказуемым.

Шесть принципов REST

2.1. Клиент-серверная архитектура

Чёткое разделение ответственности: клиент отвечает за интерфейс пользователя, сервер — за хранение и обработку данных. Они слабо связаны и могут развиваться независимо друг от друга.

2.2. Stateless (отсутствие состояния)

Каждый запрос содержит всю информацию, необходимую для его обработки (например, токен авторизации). Сервер не хранит состояние клиента между запросами. Это упрощает горизонтальное масштабирование: любой запрос может обработать любой экземпляр сервера.

2.3. Кешируемость

Ответы должны явно указывать, можно ли их кешировать (заголовки Cache-Control, ETag). Кеширование снижает нагрузку на сервер и ускоряет отклик для клиента.

2.4. Единообразие интерфейса (uniform interface)

Главный принцип REST. Взаимодействие со всеми ресурсами происходит единообразно:

  • ресурсы идентифицируются через URI;
  • операции выполняются стандартными HTTP-методами;
  • ответ содержит всё необходимое для дальнейшей работы с ресурсом.

2.5. Слоистая система (layered system)

Между клиентом и сервером могут стоять промежуточные слои — прокси, балансировщики нагрузки, кеши. Каждый компонент «видит» только соседний слой и не знает о всей цепочке.

2.6. Код по требованию (опционально)

Сервер может передавать клиенту исполняемый код (например, JavaScript). Единственный необязательный принцип.


3. Ресурсы и проектирование URI

Понятие ресурса

Ресурс — любая сущность, которую можно идентифицировать и адресовать: пользователь, книга, заказ, коллекция книг. В REST вся работа строится вокруг ресурсов, а URI — это их адрес.

/users/123 — конкретный пользователь
/users — коллекция пользователей
/books/456 — конкретная книга
/users/123/orders — заказы пользователя 123

Правила проектирования URI

ПравилоХорошоПлохо
Существительные, а не глаголыGET /usersGET /getUsers
Множественное число для коллекций/books/book
Иерархия для связанных ресурсов/users/123/orders/userOrders?userId=123
Тире для разделения слов/user-profiles/user_profiles, /userProfiles
Без расширений файлов/users/users.json
Версионирование в пути/api/v1/users/api/users

Ключевая идея: действие выражается HTTP-методом, а не словом в URI. Глагол delete не нужен — для удаления есть метод DELETE.

✅ DELETE /api/v1/books/456
❌ GET /api/v1/books/456/delete

Параметры запроса (query string) применяют для фильтрации, сортировки и пагинации, но не для идентификации ресурса:

GET /api/v1/books?author=Толстой&sort=year&page=2

4. HTTP-методы и CRUD

REST опирается на стандартные HTTP-методы. Каждый метод соответствует операции над ресурсом. Базовый набор операций называют CRUD (Create, Read, Update, Delete).

Сопоставление: метод → операция → код успеха

МетодCRUD-операцияНад чемИдемпотентностьТипичный код успеха
GETReadресурс / коллекцияда200 OK
POSTCreateколлекциянет201 Created
PUTUpdate (полная замена)ресурсда200 OK / 204 No Content
PATCHUpdate (частично)ресурснет200 OK
DELETEDeleteресурсда204 No Content

Идемпотентность и безопасность

  • Идемпотентность — повторное выполнение запроса даёт тот же результат. GET, PUT, DELETE идемпотентны; POST и PATCH — нет.
  • Безопасный метод — не изменяет состояние сервера. Безопасны только GET, HEAD, OPTIONS.

Примеры на FastAPI

from fastapi import FastAPI, HTTPException, status
from pydantic import BaseModel
app = FastAPI(title="Books API", version="1.0.0")
class BookCreate(BaseModel):
title: str
author: str
year: int
class Book(BookCreate):
id: int
books: dict[int, Book] = {}
counter = 0
# READ — список
@app.get("/api/v1/books", response_model=list[Book])
def list_books():
return list(books.values())
# READ — один ресурс
@app.get("/api/v1/books/{book_id}", response_model=Book)
def get_book(book_id: int):
book = books.get(book_id)
if book is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Book not found")
return book
# CREATE
@app.post("/api/v1/books", response_model=Book,
status_code=status.HTTP_201_CREATED)
def create_book(data: BookCreate):
global counter
counter += 1
book = Book(id=counter, **data.model_dump())
books[counter] = book
return book
# UPDATE — полная замена
@app.put("/api/v1/books/{book_id}", response_model=Book)
def replace_book(book_id: int, data: BookCreate):
if book_id not in books:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Book not found")
book = Book(id=book_id, **data.model_dump())
books[book_id] = book
return book
# DELETE
@app.delete("/api/v1/books/{book_id}",
status_code=status.HTTP_204_NO_CONTENT)
def delete_book(book_id: int):
if books.pop(book_id, None) is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Book not found")
return None

PUT и PATCH отличаются объёмом данных: PUT заменяет ресурс целиком (все поля обязательны), PATCH обновляет только переданные поля. Для PATCH обычно делают схему с необязательными полями:

class BookUpdate(BaseModel):
title: str | None = None
author: str | None = None
year: int | None = None
@app.patch("/api/v1/books/{book_id}", response_model=Book)
def update_book(book_id: int, data: BookUpdate):
book = books.get(book_id)
if book is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
detail="Book not found")
updated = book.model_copy(update=data.model_dump(exclude_unset=True))
books[book_id] = updated
return updated

5. Коды состояния HTTP

Код состояния — это короткий сигнал клиенту о результате запроса. Правильно выбранный код важен не меньше, чем тело ответа.

Группы кодов

ГруппаЗначение
2xxУспех
3xxПеренаправление
4xxОшибка на стороне клиента
5xxОшибка на стороне сервера

Наиболее частые коды

КодНазваниеКогда использовать
200OKУспешные GET, PUT, PATCH
201CreatedРесурс создан (POST)
204No ContentУспех без тела ответа (DELETE)
400Bad RequestНекорректный запрос (например, битый JSON)
401UnauthorizedТребуется аутентификация
403ForbiddenАутентифицирован, но нет прав
404Not FoundРесурс не найден
409ConflictКонфликт (например, дубликат)
422Unprocessable EntityОшибка валидации данных
500Internal Server ErrorНепредвиденная ошибка сервера

Различайте 401 и 403: 401 — «я не знаю, кто вы» (нет/неверный токен); 403 — «я знаю, кто вы, но вам сюда нельзя».

Частая ошибка: возвращать 200 OK с телом {"error": "..."}. Так делать нельзя — код должен отражать результат. Ошибку «не найдено» сопровождают кодом 404, а не 200.

В FastAPI код задаётся параметром status_code в декораторе или передаётся в HTTPException. Валидацию входных данных по Pydantic-схеме FastAPI выполняет автоматически и при ошибке отдаёт 422.


6. Формат данных и согласование контента

JSON

JSON (JavaScript Object Notation) — самый распространённый формат обмена в REST API.

Преимущества: читается человеком, легко парсится, поддерживается всеми языками, компактен.

Запрос на создание:

POST /api/v1/users
Content-Type: application/json
{
"name": "Иван Иванов",
"email": "ivan@example.com",
"age": 25
}

Ответ:

{
"id": 123,
"name": "Иван Иванов",
"email": "ivan@example.com",
"age": 25,
"created_at": "2026-01-15T10:30:00Z"
}

Согласование контента (content negotiation)

Клиент и сервер договариваются о формате данных через HTTP-заголовки:

  • Content-Type — формат тела запроса, который отправляет клиент (application/json);
  • Accept — формат, который клиент готов принять в ответе.

Если сервер не умеет отдавать запрошенный в Accept формат, он отвечает 406 Not Acceptable. FastAPI по умолчанию сериализует ответы в JSON и сам проставляет Content-Type: application/json.

Согласованная структура ответов

Полезно придерживаться единого формата ответов во всём API.

Список с метаданными:

{
"data": [
{"id": 1, "name": "Иван"},
{"id": 2, "name": "Пётр"}
],
"meta": {"total": 2, "page": 1, "per_page": 10}
}

Ошибка в едином формате:

{
"error": {
"code": "VALIDATION_ERROR",
"message": "Поле email обязательно для заполнения",
"details": {"field": "email", "reason": "required"}
}
}

В FastAPI структуру ответа задают через Pydantic-модели и параметр response_model. Это даёт два эффекта сразу: сервер гарантирует форму ответа и автоматически документирует её в OpenAPI (страница /docs).

class UserResponse(BaseModel):
id: int
name: str
email: str
@app.get("/api/v1/users/{user_id}", response_model=UserResponse)
def get_user(user_id: int):
...

Краткие итоги

  • API — контракт взаимодействия программ; веб-API обычно работает поверх HTTP и обменивается JSON.
  • REST — архитектурный стиль (не протокол) с шестью принципами: клиент-сервер, stateless, кешируемость, единообразие интерфейса, слоистость, код по требованию.
  • Ресурсы адресуются через URI: существительные, множественное число, иерархия; действие выражается HTTP-методом, а не словом в адресе.
  • HTTP-методы сопоставляются с CRUD: POST→Create, GET→Read, PUT/PATCH→Update, DELETE→Delete.
  • Коды состояния обязательно отражают результат: 200/201/204 — успех, 4xx — вина клиента, 5xx — вина сервера.
  • JSON — основной формат; форматы согласуются заголовками Content-Type и Accept.
  • FastAPI упрощает всё перечисленное: маршруты на методах, status_code, Pydantic-схемы, response_model и автодокументация OpenAPI.

Вопросы для самопроверки

  1. Что такое API и какие основные характеристики у веб-API?
  2. Почему REST называют архитектурным стилем, а не протоколом?
  3. Перечислите шесть принципов REST. Какой из них необязателен?
  4. Что означает свойство stateless и как оно помогает масштабированию?
  5. Сформулируйте основные правила проектирования URI. Почему в URI не должно быть глаголов?
  6. Сопоставьте HTTP-методы с операциями CRUD.
  7. Чем отличается PUT от PATCH?
  8. Что такое идемпотентность? Какие методы идемпотентны, а какие — нет?
  9. Какие коды состояния стоит вернуть при создании ресурса, при удалении и при отсутствии ресурса?
  10. В чём разница между кодами 401 и 403? Между 400 и 422?
  11. За что отвечают заголовки Content-Type и Accept?
  12. Как response_model в FastAPI связан с форматом ответа и документацией?