Как создать полноценный REST API на Flask: пошаговое руководство
Для кого эта статья:
- Начинающие и опытные разработчики, желающие освоить создание REST API на Flask
- Студенты и участники курсов по веб-разработке, интересующиеся практическими аспектами разработки API
Специалисты, ищущие советы по улучшению своих навыков и практик в области создания и тестирования веб-приложений
REST API — это не просто модный термин, а основа современной веб-разработки. Когда мой первый проект требовал интеграции с мобильным приложением, я понял истинную мощь Flask для создания легких и функциональных API. За 5 лет работы с этим фреймворком я наблюдал, как разработчики тратят недели на то, что можно реализовать за день. В этом руководстве я расскажу, как избежать типичных ошибок и создать полноценный REST API на Flask с нуля — от установки до тестирования, с рабочими примерами и проверенными практиками. 🚀
Если вы хотите не просто прочитать руководство, а получить структурированные знания по разработке с Flask и другими Python-фреймворками, обратите внимание на курс Обучение Python-разработке от Skypro. Программа построена опытными практиками, включает реальные проекты и личный код-ревью, что позволит вам быстро перейти от создания простых API к комплексным веб-приложениям, востребованным на рынке труда.
Настройка окружения и базовая структура REST API на Flask
Начнем с создания изолированной среды разработки и установки необходимых компонентов для нашего REST API. Правильная настройка окружения — залог успешного и беспроблемного проекта. 🛠️
Создадим виртуальное окружение и установим Flask:
# Создаем виртуальное окружение
python -m venv venv
# Активируем его (для Windows)
venv\Scripts\activate
# Активируем его (для Unix/MacOS)
source venv/bin/activate
# Устанавливаем Flask
pip install flask
После установки создадим базовую структуру проекта. Для небольших API можно использовать один файл, но для масштабируемых решений лучше разделить код на модули:
project/
├── app.py # Основной файл приложения
├── models.py # Определения моделей данных
├── resources.py # Ресурсы API и их обработчики
├── schemas.py # Схемы сериализации/десериализации
└── venv/ # Виртуальное окружение
Теперь создадим минимальное Flask-приложение в файле app.py:
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/api/health', methods=['GET'])
def health_check():
return jsonify({'status': 'ok', 'message': 'API работает'})
if __name__ == '__main__':
app.run(debug=True)
Запустите приложение командой python app.py и откройте в браузере http://localhost:5000/api/health. Вы должны увидеть JSON-ответ, подтверждающий работоспособность вашего API.
Дмитрий Волков, технический руководитель
Два года назад мы столкнулись с задачей быстро разработать API для интеграции с партнерской системой аналитики. Времени было мало, а требования менялись каждый день. Выбрали Flask именно из-за его минимализма и гибкости. Мы настроили базовую структуру за пару часов, что позволило сосредоточиться на бизнес-логике, а не на конфигурации. Ключевым решением стало использование виртуального окружения с фиксированными версиями зависимостей — это спасло нас от проблем при развертывании. Когда проект вырос, эта простая структура масштабировалась вместе с нами, без необходимости переписывать архитектуру.
Для более сложных API рекомендую использовать дополнительные расширения Flask:
| Расширение | Назначение | Команда установки |
|---|---|---|
| Flask-RESTful | Упрощает создание RESTful API | pip install flask-restful |
| Flask-SQLAlchemy | ORM для работы с базами данных | pip install flask-sqlalchemy |
| Flask-Marshmallow | Сериализация/десериализация объектов | pip install flask-marshmallow |
| Flask-JWT-Extended | Аутентификация с использованием JWT | pip install flask-jwt-extended |
Применяя эти расширения, мы можем существенно упростить разработку и повысить качество нашего API.

Создание эндпоинтов и маршрутизация REST-запросов
Эндпоинты — это URI, по которым клиенты взаимодействуют с вашим API. Правильная организация маршрутов делает API интуитивно понятным и легким в использовании. 🧭
В REST архитектуре ресурсы обычно представлены существительными во множественном числе. Рассмотрим пример API для управления книгами:
from flask import Flask, jsonify, request
app = Flask(__name__)
# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]
# Получение всех книг
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify({"books": books})
# Получение книги по ID
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
return jsonify(book)
return jsonify({"message": "Книга не найдена"}), 404
if __name__ == '__main__':
app.run(debug=True)
Обратите внимание на структуру URL: мы используем /api/books для получения списка всех книг и /api/books/<id> для получения конкретной книги. Это соответствует принципам RESTful API.
Для более сложных API удобнее использовать Flask-RESTful, который позволяет организовать код в виде классов-ресурсов:
from flask import Flask
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
# Те же данные, что и раньше
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]
class BookResource(Resource):
def get(self, book_id=None):
if book_id is None:
return {"books": books}
book = next((book for book in books if book["id"] == book_id), None)
if book:
return book
return {"message": "Книга не найдена"}, 404
# Регистрация ресурсов
api.add_resource(BookResource, '/api/books', '/api/books/<int:book_id>')
if __name__ == '__main__':
app.run(debug=True)
Этот подход делает код более модульным и позволяет легко добавлять новые функции.
При разработке REST API важно следовать соглашениям об именовании маршрутов:
- Используйте существительные во множественном числе:
/books,/users - Идентифицируйте ресурсы в URL:
/books/1, а не/get-book?id=1 - Используйте вложенные ресурсы для связанных данных:
/authors/1/books - Включите версионирование API:
/api/v1/books
Следование этим принципам сделает ваш API более предсказуемым для клиентов.
Обработка HTTP-методов: GET, POST, PUT и DELETE во Flask
HTTP-методы определяют операции, которые клиент хочет выполнить с ресурсами. Каждый метод имеет свою семантику и назначение. Правильная обработка этих методов — ключ к созданию полноценного REST API. 📝
Расширим наш пример с книгами, добавив поддержку всех основных HTTP-методов:
from flask import Flask, jsonify, request
app = Flask(__name__)
# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]
# Получение всех книг (GET)
@app.route('/api/books', methods=['GET'])
def get_books():
return jsonify({"books": books})
# Получение книги по ID (GET)
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
return jsonify(book)
return jsonify({"message": "Книга не найдена"}), 404
# Создание новой книги (POST)
@app.route('/api/books', methods=['POST'])
def create_book():
if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400
# Присваиваем новый ID
new_id = max(book["id"] for book in books) + 1 if books else 1
# Создаем новую книгу из полученных данных
new_book = {
"id": new_id,
"title": request.json.get('title', ''),
"author": request.json.get('author', '')
}
books.append(new_book)
return jsonify(new_book), 201
# Обновление книги (PUT)
@app.route('/api/books/<int:book_id>', methods=['PUT'])
def update_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
return jsonify({"message": "Книга не найдена"}), 404
if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400
# Обновляем данные книги
book['title'] = request.json.get('title', book['title'])
book['author'] = request.json.get('author', book['author'])
return jsonify(book)
# Удаление книги (DELETE)
@app.route('/api/books/<int:book_id>', methods=['DELETE'])
def delete_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if not book:
return jsonify({"message": "Книга не найдена"}), 404
books.remove(book)
return jsonify({"message": "Книга удалена"})
if __name__ == '__main__':
app.run(debug=True)
Теперь наш API поддерживает полный набор CRUD-операций (Create, Read, Update, Delete) через соответствующие HTTP-методы.
| HTTP метод | CRUD операция | Пример URL | Код успешного ответа |
|---|---|---|---|
| GET | Read (чтение) | /api/books или /api/books/1 | 200 OK |
| POST | Create (создание) | /api/books | 201 Created |
| PUT | Update (обновление) | /api/books/1 | 200 OK |
| DELETE | Delete (удаление) | /api/books/1 | 200 OK или 204 No Content |
При разработке API обязательно учитывайте идемпотентность методов: повторный вызов GET, PUT или DELETE с теми же параметрами должен давать тот же результат. Метод POST не является идемпотентным, так как каждый вызов создает новый ресурс.
Алексей Соколов, разработчик-архитектор
Вспоминаю проект, где мы реализовали API для системы управления медицинскими назначениями. Первая версия поддерживала только GET и POST запросы — казалось, этого достаточно. Но когда интегрировали мобильное приложение, обнаружили проблему: при плохом соединении клиенты дублировали запросы, создавая множественные записи. После нескольких инцидентов мы полностью переработали API, внедрив правильную обработку PUT и PATCH методов для обновлений и строгую валидацию идентификаторов. Это не только устранило дублирование, но и сделало возможным оффлайн-режим работы клиентов, что повысило удовлетворенность пользователей на 47%.
Для тестирования вашего API удобно использовать инструменты вроде Postman или curl. Например, для создания новой книги через curl:
curl -X POST http://localhost:5000/api/books \
-H "Content-Type: application/json" \
-d '{"title": "Мастер и Маргарита", "author": "Михаил Булгаков"}'
Работа с данными: сериализация и валидация в REST API
Сериализация — процесс преобразования объектов Python в формат, понятный клиентам (обычно JSON). Валидация обеспечивает корректность получаемых данных. Эти процессы критичны для надежного API. 🛡️
Для упрощения этих задач рекомендую использовать библиотеку Marshmallow. Установим необходимые пакеты:
pip install marshmallow flask-marshmallow marshmallow-sqlalchemy
Создадим схему для валидации и сериализации данных книги:
from flask import Flask, jsonify, request
from flask_marshmallow import Marshmallow
from marshmallow import ValidationError
app = Flask(__name__)
ma = Marshmallow(app)
# Схема для валидации и сериализации
class BookSchema(ma.Schema):
class Meta:
fields = ("id", "title", "author")
# Валидация данных
def validate_title(self, title):
if not title or len(title) < 3:
raise ValidationError("Название должно содержать не менее 3 символов")
return title
# Создаем экземпляры схем для одной книги и списка книг
book_schema = BookSchema()
books_schema = BookSchema(many=True)
# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]
@app.route('/api/books', methods=['GET'])
def get_books():
# Сериализация списка книг
return jsonify({"books": books_schema.dump(books)})
@app.route('/api/books/<int:book_id>', methods=['GET'])
def get_book(book_id):
book = next((book for book in books if book["id"] == book_id), None)
if book:
# Сериализация одной книги
return jsonify(book_schema.dump(book))
return jsonify({"message": "Книга не найдена"}), 404
@app.route('/api/books', methods=['POST'])
def create_book():
if not request.json:
return jsonify({"message": "Неверный формат данных"}), 400
try:
# Валидация входных данных
validated_data = book_schema.load(request.json)
# Присваиваем новый ID
new_id = max(book["id"] for book in books) + 1 if books else 1
validated_data['id'] = new_id
books.append(validated_data)
# Сериализуем и возвращаем созданную книгу
return jsonify(book_schema.dump(validated_data)), 201
except ValidationError as err:
return jsonify({"errors": err.messages}), 400
if __name__ == '__main__':
app.run(debug=True)
Преимущества использования схем сериализации:
- Автоматическая валидация входных данных с понятными сообщениями об ошибках
- Контроль над тем, какие поля возвращаются клиенту
- Возможность трансформации данных (например, форматирование дат)
- Разделение логики валидации и бизнес-логики
Для более сложных случаев можно комбинировать Marshmallow с SQLAlchemy, что упрощает работу с базами данных:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///books.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
ma = Marshmallow(app)
class Book(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
author = db.Column(db.String(100), nullable=False)
def __init__(self, title, author):
self.title = title
self.author = author
class BookSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = Book
load_instance = True
# Создаем базу данных
with app.app_context():
db.create_all()
Теперь Marshmallow автоматически создаст схему на основе модели SQLAlchemy, что еще больше упрощает код.
Тестирование и документирование Flask REST API
Тестирование и документирование API — ключевые аспекты, которые часто упускают из виду. Качественные тесты гарантируют стабильную работу, а документация делает API доступным для других разработчиков. 📚
Начнем с написания автоматических тестов для нашего API с использованием стандартного модуля unittest:
import unittest
import json
from app import app
class BookAPITestCase(unittest.TestCase):
def setUp(self):
self.app = app.test_client()
self.app.testing = True
def test_get_books(self):
response = self.app.get('/api/books')
data = json.loads(response.data)
self.assertEqual(response.status_code, 200)
self.assertTrue('books' in data)
self.assertIsInstance(data['books'], list)
def test_get_book(self):
response = self.app.get('/api/books/1')
data = json.loads(response.data)
self.assertEqual(response.status_code, 200)
self.assertEqual(data['id'], 1)
def test_create_book(self):
test_book = {
'title': 'Тестовая книга',
'author': 'Автор Тестов'
}
response = self.app.post('/api/books',
data=json.dumps(test_book),
content_type='application/json')
data = json.loads(response.data)
self.assertEqual(response.status_code, 201)
self.assertEqual(data['title'], 'Тестовая книга')
if __name__ == '__main__':
unittest.main()
Для запуска тестов выполните:
python -m unittest test_api.py
Автоматическое тестирование помогает обнаружить ошибки до того, как их найдут пользователи, и гарантирует, что новые изменения не нарушают существующую функциональность.
Теперь перейдем к документированию API. Одним из лучших инструментов для этого является Swagger UI через расширение Flask-RESTX:
pip install flask-restx
Применим его к нашему API:
from flask import Flask
from flask_restx import Api, Resource, fields
app = Flask(__name__)
api = Api(app, version='1.0', title='Book API',
description='API для управления книгами')
# Определяем пространство имен
ns = api.namespace('books', description='Операции с книгами')
# Определяем модель для документации
book_model = api.model('Book', {
'id': fields.Integer(readonly=True, description='Уникальный идентификатор'),
'title': fields.String(required=True, description='Название книги'),
'author': fields.String(required=True, description='Автор книги')
})
# Имитация базы данных
books = [
{"id": 1, "title": "Фласк для начинающих", "author": "Александр Петров"},
{"id": 2, "title": "Python в действии", "author": "Мария Сидорова"}
]
@ns.route('/')
class BookList(Resource):
@ns.doc('list_books')
@ns.marshal_list_with(book_model)
def get(self):
"""Получить список всех книг"""
return books
@ns.doc('create_book')
@ns.expect(book_model)
@ns.marshal_with(book_model, code=201)
def post(self):
"""Создать новую книгу"""
new_id = max(book["id"] for book in books) + 1 if books else 1
new_book = {
'id': new_id,
'title': api.payload['title'],
'author': api.payload['author']
}
books.append(new_book)
return new_book, 201
@ns.route('/<int:id>')
@ns.response(404, 'Книга не найдена')
@ns.param('id', 'Идентификатор книги')
class Book(Resource):
@ns.doc('get_book')
@ns.marshal_with(book_model)
def get(self, id):
"""Получить книгу по ID"""
for book in books:
if book['id'] == id:
return book
api.abort(404, "Книга с ID {} не найдена".format(id))
if __name__ == '__main__':
app.run(debug=True)
При запуске приложения вы можете открыть Swagger UI в браузере по адресу http://localhost:5000, где увидите интерактивную документацию вашего API.
Вот ключевые моменты для успешного тестирования и документирования API:
- Пишите тесты параллельно с разработкой, а не после нее
- Тестируйте как успешные сценарии, так и обработку ошибок
- Документируйте все эндпоинты, параметры и возможные коды ответов
- Приводите примеры запросов и ответов в документации
- Настройте CI/CD для автоматического запуска тестов при изменении кода
Изучив основы создания REST API на Flask, вы заложили фундамент для разработки профессиональных веб-сервисов. Но истинное мастерство приходит с практикой — используйте полученные знания для создания собственных API, экспериментируйте с различными расширениями и не бойтесь внедрять API в реальные проекты. Помните: даже самые сложные системы начинались с простого эндпоинта, возвращающего "Hello, World!" в формате JSON.