Полноценный REST API для перфекционистов за 5 минут
Привет Хабр! Меня зовут Владимир, мне 28 лет и я наркоман наркоман. Мой наркотик — простота. На простоту я подсел из-за своего перфекционизма, которым меня наградили при рождении. Врачи говорят, что это взаимосвязано, мол перфекционизм — это стремление к совершенству, а простота позволяет подобраться к этому мифическому совершенству. Чем проще решение, тем меньше ошибок можно допустить, вот я и подсел. Я не стал с ними спорить и вместо того, что бы искать виновников моей истории, решил с этим жить и постараться повысить качество этой самой жизни.
Мир вокруг не идеален, сложную вещь сделать простой — невероятно сложно, поэтому всё чрезмерно усложнено. Людям нравиться чувствовать себя профессионалами, поэтому они оперируют сложными терминами, когда в этом нет необходимости, так они ощущают свою значимость и заполняют пустоту, которая образовалась из-за страха потерянного времени.
Я принципиально всем «выкаю» с маленькой буквы и я убеждён, что это имеет свою цену. Наверняка есть люди, которым не нравится, что их назвали с маленькой буквы и они не ответят на моё письмо.
Так зачем эти сложности?
Когда я упомянул про повышения качества жизни, я имел ввиду некоторые правила, которые помогают быстро принимать решения и не жалеть об этом. Например я не использую сложные вещи постоянно, не общаюсь с людьми, которым нравится всё усложнять. Если вещь, которой вы пользуетесь каждый день — сложна, то это значит только то, что ей не уделили достаточно внимания. Если человек вам не может объяснить даже трудную для понимания тему простым языком, значит он сам не до конца понимает о чём говорит.
Есть ещё кое-что, что имеет не менее важное значение — время. Время — бесценный ресурс, а выражение «Время — деньги» из уст умных людей вызывает у меня улыбку с разочарованием (выглядишь как идиот и чувствуешь себя так же). Я до сих пор не знаю ни одного миллиардера, которому деньги помогли прожить дольше других людей.
Введение
Речь пойдёт об инструменте, который позволит вам построить полноценный и простой в использовании REST API за минимальное количество времени. Называется он — Python Eve.
К сожалению в Интернете очень много инструкций на эту тему, но все они вводят в заблуждение. Начинающие разработчики, начитавшись подобных статей, думают, что REST API это GET/POST/PUT/DELETE. Заказчики думают, что это дело пары часов. А когда они встречаются вместе, происходят магия в виде Express.js/Mongoose/Passport и ещё кучи хлама, который течёт и временами блокирует event-loop. Всё это запускается с помощью какого-нибудь supervisor, потому что иногда падает и надо как-то перезапускать.
И всё бы ничего, но вчера у меня состоялся разговор с хабра-пользователем, который предложил воспользоваться »Express.js, MongoDB, Mongoose, Passport, отладчиком WebStorm’a и головой на плечах». Похожие разговоры случались часто, поэтому я решил написать эту статью и «отсылать» ссылкой на неё.
Полноценный REST API?
Речь не только о реализации архитектурного стиля REST API, но и о протоколе HTTP, о валидации и кэшировании, о HATEOAS (wikipedia), о котором, похоже, вообще предпочитают не вспоминать. Затем нам понадобится фильтрация результатов и сортировка, постраничная навигация и частичное обновление записей. Потом мы задумаемся о целостности данных и условных запросах. Наверняка нам понадобится аутентификация, возможно захотим отображать данные не только в JSON, но и в XML. Это ещё про версионность и вложенные записи я не упомянул. Затем, как это обычно бывает, какой-то #$%$%^ начнёт долбить в наш могучий API с тяжёлым запросом и нам понадобится ограничить частоту запросов.
Даже, если представить, что разработкой такого API займётся невероятно крутой разработчик с 3-мя мониторами, отладчиком WebStorm’a и головой на плечах, он затратит на это не просто много времени, а очень много. Поддержка кодовой базы будет обходиться дорого, а внедрение новых функций будет долгим.
Но мы с вами простоту — любим, а время — уважаем. Так приступим же!
Установка
Это обычный python-пакет, поэтому устанавливается он стандартным способом:
$ pip install eve
Если вас интересуют альтернативные методы установки, можете заглянуть в официальную документацию.
Быстрый старт
Перед тем, как мы начнём «творить» магию, нам понадобится база данных MongoDB. Если у вас её нет, вы можете воспользоваться любым бесплатным сервисом, например MongoLab. Регистрация займёт не больше минуты. После регистрации создайте базу данных и пользователя для этой базы.
Теперь давайте напишем минимальную версию нашего REST API. Для начала создадим главный файл run.py со следующим содержимым:
from eve import Eve
app = Eve()
if __name__ == '__main__':
app.run()
Теперь нам надо создать файл настроек settings.py:
# замените user, password, ds049945.mongolab.com, example на ваши данные доступа к БД.
MONGO_URI = "mongodb://user:password@ds049945.mongolab.com:49945/example"
# По умолчанию Eve запускает API в режиме "read-only" (т.е. поддерживаются только GET запросы),
# мы включаем поддержку методов POST, PUT, PATCH, DELETE.
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
ITEM_METHODS = ['GET', 'PATCH', 'PUT', 'DELETE']
DOMAIN = {
# Описываем ресурс `/users`
'users': {
# Здесь мы описываем модель данных. Для валидации используется модуль Cerberus от автора Eve.
# Вы можете ознакомиться с ним в официальной документации модуля http://docs.python-cerberus.org/en/stable/.
# Либо прочитать заметки в официальной документации EVE http://python-eve.org/validation.html#validation.
'schema': {
'username': {
'type': 'string',
'minlength': 5,
'maxlength': 32,
'required': True,
# уникальное поле (индекс не создаётся, просто значение должно быть уникальным)
'unique': True,
},
'firstname': {
'type': 'string',
'minlength': 1,
'maxlength': 10,
'required': True,
},
'lastname': {
'type': 'string',
'minlength': 1,
'maxlength': 15,
'required': True,
},
'role': {
'type': 'list', # тип: список
'allowed': ["author", "contributor"], # разрешаем использовать значения: "author", "contributor"
},
'location': {
'type': 'dict', # тип: словарь
# описываем "схему" словаря
'schema': {
'address': {'type': 'string'},
'city': {'type': 'string'}
},
},
'born': {
'type': 'datetime',
},
'active': {
'type': 'boolean',
'default': True
}
}
},
# Описываем ресурс `/groups`
'groups': {
# Описываем модель данных (см. выше).
'schema': {
'title': {
'type': 'string',
'minlength': 5,
'maxlength': 32,
'required': True,
'unique': True
},
'users': {
'type': 'list', # тип: список
'default': [], # по умолчанию: пустой список
# описываем "схему" списка
'schema': {
'type': 'objectid', # тип данных: objectid
# ссылаемся на запись в другой коллекции
'data_relation': {
'resource': 'users', # на ресурс `users` (который мы описали выше)
'field': '_id', # на поле `_id`
'embeddable': True
}
}
}
}
}
}
На мой взгляд здесь всё достаточно просто и вопросов возникнуть не должно. Если это не так — добро пожаловать в комментарии. Полный список параметров конфигурации вы можете глянуть в документации.
Всё готово, запускаем:
$ python3.5 run.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
Прелюдия
Вы уже подумали, что сейчас мы начнём безбожно »curlить», но я вынужден вас разочаровать. Мы, со свойственным нам перфекционизмом, воспользуемся инструментом автора, который обладает чувством прекрасного:
Инструмент называется HTTPie и ставится в один клик одну команду:
$ pip install httpie
Игрища и забавы
HTTPie вызывается командой »http». Для того, что бы отправить GET запрос к нашему API, выполним:
$ http http://0.0.0.0:5000/
HTTP/1.0 200 OK
Content-Length: 99
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:13:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_links": {
"child": [
{
"href": "users",
"title": "users"
},
{
"href": "groups",
"title": "groups"
}
]
}
}
Благодаря HATEOAS мы видим, что у нас есть 2 ресурса: users и groups. Заглянем внутрь:
» http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 166
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:20:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 0
{
"_items": [],
"_links": {
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "users",
"title": "users"
}
},
"_meta": {
"max_results": 25,
"page": 1,
"total": 0
}
}
Давайте создадим пользователя johndoe:
$ http http://0.0.0.0:5000/users username=johndoe
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 184
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:22:44 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_error": {
"code": 422,
"message": "Insertion failure: 1 document(s) contain(s) error(s)"
},
"_issues": {
"firstname": "required field",
"lastname": "required field"
},
"_status": "ERR"
}
Первое, на что стоит обратить внимание, это на нашу команду:
$ http http://0.0.0.0:5000/users username=johndoe
HTTPie увидел, что мы отправляем параметр username и превратил его в JSON:
{
"username": "johndoe"
}
Затем отправил нашему API методом POST. Давайте обратим внимание на ошибки:
"_issues": {
"firstname": "required field",
"lastname": "required field"
}
Мы видим сразу весь список ошибок валидации и это здорово. Исправим их и выполним запрос повторно:
$ http http://0.0.0.0:5000/users username=johndoe firstname=John lastname=Doe
HTTP/1.0 201 CREATED
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:34:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 18:34:41 GMT"
}
Ну вот, мы только что успешно создали нового пользователя. Давайте проверим так ли это:
$ http http://0.0.0.0:5000/users
HTTP/1.0 200 OK
Content-Length: 504
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:36:00 GMT
Last-Modified: Sun, 07 Feb 2016 18:34:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1
{
"_items": [
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "24509359443095dd05dece6d0eb7d98cce70b076",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 18:34:41 GMT",
"active": true,
"firstname": "John",
"lastname": "Doe",
"username": "johndoe"
}
],
"_links": {
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "users",
"title": "users"
}
},
"_meta": {
"max_results": 25,
"page": 1,
"total": 1
}
}
Нет никаких сомнений, что это так. Настало время попробовать перезаписать (обратите внимание, не отредактировать, а перезаписать) пользователя. Делается это с помощью метода PUT, который надо указать явно (если не указать, будет выполнен POST):
$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 403 FORBIDDEN
Content-Length: 101
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:43:04 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_error": {
"code": 403,
"message": "An etag must be provided to edit a document"
},
"_status": "ERR"
}
Упс, ошибка. Я выше упоминал о целостности данных. Дело в том, что может произойти ситуация, в которой кто-то уже изменил запись, которую хотите поменять вы. И когда вы её отредактируете, то будете думать, что изменили одну запись, но на самом деле уже совсем другую.
Для того, что бы мы в такой ситуации не оказались, используется идентификатор ETag. В двух словах — это уникальный идентификатор, который генерируется Eve при каждом изменении записи. Используя этот идентификатор мы можем сказать нашему API, что хотим изменить запись только определённой версии и если она с тех была отредактирована, то наши изменения выполнены не будут. Делается это с помощью условного запроса с HTTP заголовком »If-Match»:
$ http put http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 "If-Match":"24509359443095dd05dece6d0eb7d98cce70b076" username=janedoe firstname="Jane" lastname="Doe"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 18:46:56 GMT
ETag: 0138d193174528c205827ba9af25b7b8fb93940e
Last-Modified: Sun, 07 Feb 2016 18:46:56 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 18:46:56 GMT"
}
Обратите внимание каким образом мы передали HTTP заголовок HTTPie. Это не единственное, для чего может использоваться идентификатор ETag и условные запросы. Я здесь не буду останавливаться на этом, но советую вам ознакомиться с этой темой, если вы этого ещё не сделали.
Настало время создать новую группу. Для начала попробуем создать группу с несуществующим пользователем:
$ http http://0.0.0.0:5000/groups title="Friends" users:='["56b77466cf7b352414deb451"]'
HTTP/1.0 422 UNPROCESSABLE ENTITY
Content-Length: 220
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:14:31 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_error": {
"code": 422,
"message": "Insertion failure: 1 document(s) contain(s) error(s)"
},
"_issues": {
"users": {
"0": "value '56b77466cf7b352414deb451' must exist in resource 'users', field '_id'."
}
},
"_status": "ERR"
}
И действительно, пользователя с таким _id не существует. Обратите внимание как мы передаём список пользователей:
users:='["56b77466cf7b352414deb451"]'
Подробнее вы можете почитать в официальной документации к HTTPie, которая такая же качественная, как и сам инструмент.
На этот раз мы укажем правильный _id:
» http http://0.0.0.0:5000/groups title="Friends" users:='["56b78e41cf7b35255aa5a1e6"]'
HTTP/1.0 201 CREATED
Content-Length: 278
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:21:42 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 19:21:41 GMT",
"_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
"_id": "56b79945cf7b35255aa5a1e7",
"_links": {
"self": {
"href": "groups/56b79945cf7b35255aa5a1e7",
"title": "Group"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 19:21:41 GMT"
}
«Усё добра», как говорила моя прабабушка. Проверим:
$ http http://0.0.0.0:5000/groups
HTTP/1.0 200 OK
Content-Length: 488
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:24:50 GMT
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1
{
"_items": [
{
"_created": "Sun, 07 Feb 2016 19:21:41 GMT",
"_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
"_id": "56b79945cf7b35255aa5a1e7",
"_links": {
"self": {
"href": "groups/56b79945cf7b35255aa5a1e7",
"title": "Group"
}
},
"_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
"title": "Friends",
"users": [
"56b78e41cf7b35255aa5a1e6"
]
}
],
"_links": {
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "groups",
"title": "groups"
}
},
"_meta": {
"max_results": 25,
"page": 1,
"total": 1
}
}
Прабабушка оказалась бы права. На этом можно было бы закончить, но мы поступим иначе.
Получим группу с пользователями, которые в неё входят, в развёрнутом виде:
$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}'
HTTP/1.0 200 OK
Content-Length: 646
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:38:27 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 19:21:41 GMT",
"_etag": "c6fc02a0bd4bae92a1310be0748ff8bc971ff209",
"_id": "56b79945cf7b35255aa5a1e7",
"_links": {
"collection": {
"href": "groups",
"title": "groups"
},
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "groups/56b79945cf7b35255aa5a1e7",
"title": "Group"
}
},
"_updated": "Sun, 07 Feb 2016 19:21:41 GMT",
"title": "Friends",
"users": [
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "0138d193174528c205827ba9af25b7b8fb93940e",
"_id": "56b78e41cf7b35255aa5a1e6",
"_updated": "Sun, 07 Feb 2016 18:46:56 GMT",
"active": true,
"firstname": "Jane",
"lastname": "Doe",
"username": "janedoe"
}
]
}
Попросим тоже самое в XML:
$ http http://0.0.0.0:5000/groups/56b79945cf7b35255aa5a1e7/\?embedded='{"users":1}' "Accept":"application/xml"
HTTP/1.0 200 OK
Content-Length: 690
Content-Type: application/xml; charset=utf-8
Date: Sun, 07 Feb 2016 19:43:36 GMT
ETag: c6fc02a0bd4bae92a1310be0748ff8bc971ff209
Last-Modified: Sun, 07 Feb 2016 19:21:41 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
<_created>Sun, 07 Feb 2016 19:21:41 GMT
<_etag>c6fc02a0bd4bae92a1310be0748ff8bc971ff209
<_id>56b79945cf7b35255aa5a1e7
<_updated>Sun, 07 Feb 2016 19:21:41 GMT
Friends
<_created>Sun, 07 Feb 2016 18:34:41 GMT
<_etag>0138d193174528c205827ba9af25b7b8fb93940e
<_id>56b78e41cf7b35255aa5a1e6
<_updated>Sun, 07 Feb 2016 18:46:56 GMT
True
Jane
Doe
janedoe
Попробуем изменить только имя нашего пользователя (без полной перезаписи). Для этого воспользуемся HTTP методом PATCH:
$ http patch http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6 firstname=John "If-Match":"0138d193174528c205827ba9af25b7b8fb93940e"
HTTP/1.0 200 OK
Content-Length: 276
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:46:48 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 19:46:47 GMT"
}
Что сказала бы бабуля?
» http http://0.0.0.0:5000/users/56b78e41cf7b35255aa5a1e6
HTTP/1.0 200 OK
Content-Length: 431
Content-Type: application/json
Date: Sun, 07 Feb 2016 19:50:06 GMT
ETag: 86f3495cf1d6edf301e25563099844bd816c5a3c
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"collection": {
"href": "users",
"title": "users"
},
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
"active": true,
"firstname": "John",
"lastname": "Doe",
"username": "janedoe"
}
«Усё добра». Мне так понравилось, что я бы создавал пользователей пачками:
$ echo '[{"username": "userone", "firstname": "First", "lastname":"Last"},{"username":"usertwo", "firstname":"First", "lastname":"Last"}]' | http http://0.0.0.0:5000/users
HTTP/1.0 201 CREATED
Content-Length: 585
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
{
"_items": [
{
"_created": "Sun, 07 Feb 2016 20:01:33 GMT",
"_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
"_id": "56b7a29dcf7b35255aa5a1e8",
"_links": {
"self": {
"href": "users/56b7a29dcf7b35255aa5a1e8",
"title": "User"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
},
{
"_created": "Sun, 07 Feb 2016 20:01:33 GMT",
"_etag": "378f30b37724139c213a85079185226ab2b209f3",
"_id": "56b7a29dcf7b35255aa5a1e9",
"_links": {
"self": {
"href": "users/56b7a29dcf7b35255aa5a1e9",
"title": "User"
}
},
"_status": "OK",
"_updated": "Sun, 07 Feb 2016 20:01:33 GMT"
}
],
"_status": "OK"
}
Найдём пользователя »John Doe» по его имени:
$ http http://0.0.0.0:5000/users\?where='{"firstname":"John"}'
HTTP/1.0 200 OK
Content-Length: 535
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:05:28 GMT
Last-Modified: Sun, 07 Feb 2016 19:46:47 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 1
{
"_items": [
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
"active": true,
"firstname": "John",
"lastname": "Doe",
"username": "janedoe"
}
],
"_links": {
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "users?where={\"firstname\":\"John\"}",
"title": "users"
}
},
"_meta": {
"max_results": 25,
"page": 1,
"total": 1
}
}
Отсортируем пользователей по их логину в обратном порядке:
$ http http://0.0.0.0:5000/users\?sort\=-username
HTTP/1.0 200 OK
Content-Length: 1203
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:08:08 GMT
Last-Modified: Sun, 07 Feb 2016 20:01:33 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
X-Total-Count: 3
{
"_items": [
{
"_created": "Sun, 07 Feb 2016 20:01:33 GMT",
"_etag": "378f30b37724139c213a85079185226ab2b209f3",
"_id": "56b7a29dcf7b35255aa5a1e9",
"_links": {
"self": {
"href": "users/56b7a29dcf7b35255aa5a1e9",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
"active": true,
"firstname": "First",
"lastname": "Last",
"username": "usertwo"
},
{
"_created": "Sun, 07 Feb 2016 20:01:33 GMT",
"_etag": "6f397b570ef12769d372c902fa6149bb7e9eaf89",
"_id": "56b7a29dcf7b35255aa5a1e8",
"_links": {
"self": {
"href": "users/56b7a29dcf7b35255aa5a1e8",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 20:01:33 GMT",
"active": true,
"firstname": "First",
"lastname": "Last",
"username": "userone"
},
{
"_created": "Sun, 07 Feb 2016 18:34:41 GMT",
"_etag": "86f3495cf1d6edf301e25563099844bd816c5a3c",
"_id": "56b78e41cf7b35255aa5a1e6",
"_links": {
"self": {
"href": "users/56b78e41cf7b35255aa5a1e6",
"title": "User"
}
},
"_updated": "Sun, 07 Feb 2016 19:46:47 GMT",
"active": true,
"firstname": "John",
"lastname": "Doe",
"username": "janedoe"
}
],
"_links": {
"parent": {
"href": "/",
"title": "home"
},
"self": {
"href": "users?sort=-username",
"title": "users"
}
},
"_meta": {
"max_results": 25,
"page": 1,
"total": 3
}
}
Ну и наконец-то удалим всех пользователей:
$ http delete http://0.0.0.0:5000/users
HTTP/1.0 204 NO CONTENT
Content-Length: 0
Content-Type: application/json
Date: Sun, 07 Feb 2016 20:10:06 GMT
Server: Eve/0.6.1 Werkzeug/0.10.4 Python/3.5.0
Я бы не стал включать данную возможность в production. Указывается это в параметре RESOURCE_METHODS (стоит убрать из списка DELETE):
RESOURCE_METHODS = ['GET', 'POST', 'DELETE']
Заключение
Мы рассмотрели не все возможности Eve, но даже c учётом этого — получили законченный вариант REST API.
В данной статье я хотел обратить внимание на то, что настоящий RESTful сервис это гораздо больше, чем пара модулей для Node.js и в большинстве случаев нет необходимости разрабатывать такие вещи с нуля, а тем более писать статьи о том, как это сделать за один час. Достаточно взглянуть на историю развития проекта, что бы лишний раз убедиться в том, что пары часов/дней/недель недостаточно даже для хорошей команды.
Nicola Iarocci, автор Eve, создал прекрасный инструмент для быстрого развёртывания REST API, уделил этому достаточно внимания и сохранил простоту использования.
Ссылки
Подписывайтесь на меня в Twitter, я рассказываю о работе в стартапе, своих ошибках и правильных решениях, о python и всём, что касается веб-разработки.
P.S. Я ищу разработчиков в стартап, подробности у меня в профиле.