Feathers JS — как создать backend для своего приложения всего за 5 минут
Feathers — мало известный (увы!), но при этом очень мощный и удобный фреймворк для создания серверных приложений на Node.js. В его основе лежит гораздо более популярная технология Express.
Но если Express в основном ориентирована на создание web-приложений и генерацию html-кода с использованием различных шаблонизаторов, то Feathers предназначен для создания сервисов (REST, Socket.io и Primus). При этом от разработчика требуется минимум усилий и доработки кода — ведь всё уже написано до нас.
При всей мощи Feathers, пишут о нём крайне мало. Последняя публикация на Хабре о нём была в 2013 году, никаких книг статей и курсов не существует. Сам я наткнулся на него совершенно случайно, когда искал наиболее удобный вариант написания сервера для создающейся сейчас системы персональной эффективности.
От такой несправедливости мне стало горько и я решил написать этот текст о том, как с помощью Feathers за жалкие 5 минут создать действительно работающий сервер, предоставляющий сервисы для того же React.
Итак, включаем секундомер? Нет, чуть позже. Сейчас разберемся с теорией.
В Feathers есть несколько типов объектов:
- middleware — то есть промежуточные обработчики, которые функционируют так же, как и в Express, а значит для нашего рассказа не интересны;
- сервисы — обработчики, которые исполняются на сервере, а данные передают клиента. Для вызова сервисов используются три технологии — REST, Socket.io и Primus. Причем разработчику нет нужды самостоятельно реализовывать передачу данных. Он просто реализует несколько предопределенных методов (find, get, create, update, patch, remove, setup), а обо всем остальном позаботится фреймворк;
- хуки — процедуры, которые автоматически вызываются до и после работы сервисов. Они позволяют проверить/исправить запрос, передаваемый сервису, и изменить данные, возвращаемые клиенту. Хуки можно писать самому, но есть стандартные хуки (например, отрезающие одну колонку из возвращаемого набора данных).
Вот теперь можно и начать работу.
Первым делом устанавливаем пакет feathers-cli:
$ npm install -g feathers-cli
Дальше нужно создать каталог для приложения и перейти в него:
$ mkdir feathers-app
$ cd feathers-app/
Теперь генерируем скелет приложения:
$ feathers generate
Вначале зададим имя и описание для нового приложения, а потом выберем какие API для доступа к нему мы хотим использовать»:
? Project name TestApp
? Description Приложение для быстрой демонстрации возможностей Feathers
? What type of API are you making? (Press to select, to toggle all, to inverse selection)
❯◉ REST
◉ Realtime via Socket.io
◯ Realtime via Primus
Затем нужно определиться будем ли мы разрешать CORS (Cross-Origin Resource Sharing) и если да, то для каких доменов:
? Project name TestApp
? Description Приложение для быстрой демонстрации возможностей Feathers
? What type of API are you making? REST, Realtime via Socket.io
? CORS configuration (Use arrow keys)
❯ Enabled for all domains
Enabled for whitelisted domains
Disabled
Теперь пришла пора выбрать основной тип базы данных. Если у вас их несколько, не волнуйтесь, для каждого конкретного сервиса вы сможете указывать альтернативные типы БД:
? Project name TeatApp
? Description Приложение для быстрой демонстрации возможностей Feathers
? What type of API are you making? (Press to select, to toggle all, to inverse selection)REST, Realtime via Socket.io
? CORS configuration Enabled for all domains
? What database do you primarily want to use? (Use arrow keys)
Memory
MongoDB
MySQL
MariaDB
❯ NeDB
PostgreSQL
SQLite
SQL Server
I will choose my own
Если вы не собираетесь допускать к своему backend всех подряд, то пора подумать об аутентификации. Провайдера можно написать своего или выбрать готового из списка (на самом деле провайдеров аутентификации больше, чем показано, поэтому стоит прокрутить список в поисках нужного):
? Project name TestApp
? Description Приложение для быстрой демонстрации возможностей Feathers
? What type of API are you making? REST, Realtime via Socket.io
? CORS configuration Enabled for all domains
? What database do you primarily want to use? NeDB
? What authentication providers would you like to support? (Press to select, to toggle all, to inverse selection)
◉ local
◯ bitbucket
◯ dropbox
❯◯ facebook
◯ github
◯ googlestagram
И секунд за 10 генератор создаст для нас все необходимые файлы + автоматически загрузит npm пакеты.
Структура каталогов исходного кода следующая:
- config — конфигурационные файлы, описывающие параметры приложения (домен, порт, ключ аутентификации и т.п). default.json используется на этапе разработки, а production.json — в продакшене
- public — этот каталог был напрямую унаследован от Express. Сюда помещаются все статические ресурсы (картинки, html-файлы), которые должны быть переданы в браузер клиента
- test — код для тестирования backend
- source/service — тут будут храниться «сердца» нашего приложения — сервисы, по одному на подкаталог
- source/hooks — глобальные хуки, которые будут применяться ко всем сервисам
- source/middleware — обычное middleware Express, например логи
- source/app.js — основной файл приложения, который подключает сервисы, middleware, хуки, статические ресурсы и прочее. Обычно ручное изменение не требуется
- source/index.js — просто импорт и старт app.js. В большинство случаев трогать этот файл нет смысла, но если вы пишете, например приложение Electron, то изменения вносите именно сюда
Формально приложение готово. Его даже можно запустить с помощью npm start. Из появившейся надписи мы узнаем, что сервер запущен по адресу localhost:3030. Зайдем на этот адрес — увидим пустую страничку с логотипом. Всё.
Дело в том, что мы пока что не создали ни одного сервиса. К счастью, сгенерировать не сложнее приложения.
Вводим в консоли:
$ feathers generate service
Задаем название сервиса (я выбрал contacts — для списка контактов) и выбираем откуда найдитесь сервис будет получать данные:
? What do you want to call your service? contacts
? What type of service do you need? (Use arrow keys)
generic
❯ database
В принципе можно выбрать вариант generic и самостоятельно реализовать с помощью методов сервиса (find, get, create, update, patch, remove, setup) процесс чтения/записи в базу данных. Но зачем? Ведь Feathers готов всё сделать за нас…
Итак, мы выбрали работу с базой, а в качестве базы указали уже привычный NeDB. На вопрос об аутентификации пока ответим отрицательно. Не потому что она не нужна вообще, просто обсудим её позже.
? What do you want to call your service? contacts
? What type of service do you need? database
? For which database? NeDB
? Does your service require users to be authenticated? (y/N)
В результате получаем сообщение о создании трёх файлов:
create src/services/contacts/index.js
create src/services/contacts/hooks/index.js
create test/services/contacts/index.test.js
Первый — собственно сама реализация сервиса, второй — его хуки, третий — код для тестирования работы сервиса.
Вот теперь можно с помощью npm start запустить приложение и посмотреть как оно работает. Вводим в консоли команду
$ curl 'http://localhost:3030/contacts/' -H 'Content-Type: application/json' --data-binary '{ "first_name": "John", "last_name":"Smith", "email":"jsmith@mail.emall"}'
и видим что у нас появился каталог /data (как и было задано в файле конфигурации), в нем создался файл contacts.db, а его содержимое — информация о Джоне Смите.
То есть мы получили вполне работающее приложение, не написав ни единой строчки кода!
И уложились в 5 минут.
Но это плюсы, а ведь как всегда есть и минусы:
- сервис не позволяет валидировать передаваемые ему для сохранения данные
- сервис возвращает данные в исходном виде, без какого-либо преобразования
Всё так. Но эти недостатки легко исправляются с помощью хуков. И во второй части мы потратим еще минут 15, чтобы превратить наш базовый сервер в весьма продвинутый.
…To be continued!
Комментарии (18)
20 февраля 2017 в 16:43
+1↑
↓
То есть мы получили вполне работающее приложение, не написав ни единой строчки кода!
Зато мы нагенерировали немало кода. Но если в своем написаном коде я понимаю, что к чему, то в сгенерированной версии еще предстоит разобраться
20 февраля 2017 в 16:47
+2↑
↓
Этот код очень лаконичный. И очень хорошо структурированный.
Если коллегам будет интересно, могу запланировать третью часть, в которой опишу структуру сгенеренного кода.20 февраля 2017 в 17:14
0↑
↓
А можно пример?
20 февраля 2017 в 17:35
+2↑
↓
Вот код нашего сервиса:'use strict'; const contacts = require('./contacts'); const authentication = require('./authentication'); const user = require('./user'); module.exports = function() { const app = this; app.configure(authentication); app.configure(user); app.configure(contacts); };
А вот — файла app.js:
'use strict'; const path = require('path'); const serveStatic = require('feathers').static; const favicon = require('serve-favicon'); const compress = require('compression'); const cors = require('cors'); const feathers = require('feathers'); const configuration = require('feathers-configuration'); const hooks = require('feathers-hooks'); const rest = require('feathers-rest'); const bodyParser = require('body-parser'); const socketio = require('feathers-socketio'); const middleware = require('./middleware'); const services = require('./services'); const app = feathers(); app.configure(configuration(path.join(__dirname, '..'))); app.use(compress()) .options('*', cors()) .use(cors()) .use(favicon( path.join(app.get('public'), 'favicon.ico') )) .use('/', serveStatic( app.get('public') )) .use(bodyParser.json()) .use(bodyParser.urlencoded({ extended: true })) .configure(hooks()) .configure(rest()) .configure(socketio()) .configure(services) .configure(middleware); module.exports = app;
Мне кажется, тут всё очень просто
20 февраля 2017 в 21:03
0↑
↓
, а чем это лучше чем Sails? sailsjs.org20 февраля 2017 в 21:04
0↑
↓
Я чуть ниже ответил))20 февраля 2017 в 22:55
0↑
↓
Feathers живой и не завязан на тормозной Waterline. Плюс он простой как пять копеек, что сильно выделяет его на фоне подобных фреймворков.
20 февраля 2017 в 18:33
0↑
↓
Во всяких hello world примерчиках, код всегда лаконичный и структурированный. А если сравнить сгенерированный код Feathers JS, с кодом который генерирует LoopBack 3.0, то не такой уж он и структурированный.20 февраля 2017 в 21:05
0↑
↓
Дело в том, что эти примерчики тем и хороши, что их трогать не надо ))
Единственный код, который может и стоит поменять — хуки. Они отвечают и за аутентификацию и за изменение данных.
20 февраля 2017 в 18:37
+2↑
↓
Для всякого CRUD типа админок, да и просто быстро набросать основу — очень прикольно. Давайте продолжение!
20 февраля 2017 в 20:56
0↑
↓
А в чем преимущества перед тем же Sails, где своя ОРМка з адапторами, CRUD, socket и т.д.?
Он тоже так умеет, только комьюнити у него заметно больше, и апдейты почаще…20 февраля 2017 в 21:03
+1↑
↓
Sails позиционирует себя как MVC. Создатели же Feathers хотели, чтобы библиотека была легкой (как перышко), а код простым.
Поэтому они и ограничились реализацией работы с сервисами.
В этом случае (без клиентской логики) тот же ORM является совершенно излишним. С другой стороны, на клиенте могут работать React, Angular 2, AngularJS, Vue.js и т.п., которые самостоятельно разберутся что им делать с данными, которые они получили с сервера (вот примеры).
В любом случае — каждый выбирает себе по душе. Лично меня Feathers покорил своей простотой — после 10 минут чтения документации я написал своей первый сервис. А документацию Sails мучаю уже минут 20 — и пока понимания не сложилось как это все в принципе работает.
Опять же, не хочу обидеть поклонников Sails)), но меня больше устраивает вариант, когда сервер управляет данными, а на клиенте с ними работает React20 февраля 2017 в 21:08
0↑
↓
А вот как на этот вопрос отвечают создатели Feathers: https://docs.feathersjs.com/why/vs/sails.html20 февраля 2017 в 22:39
0↑
↓
Очень странно. Вы гонитесь за простотой минимального уровня входа, вы говорите что код будет простым и лёгким, но при этом делаете require всего что угодно.Sails так же позволяет использовать любой «клиент» для этого существует `--no-frontend`.
Sails doesn’t come with any built-in authentication support
Явная проблема Feathers в том, что даже сравнение которое они делают уже устарело :(слишком медленно для JS community в 2017 году
auth
Я не хочу сказать что Feathers плох, просто хотелось бы видеть сравнение:)
20 февраля 2017 в 23:25
0↑
↓
Сравнений гуглится куча. Типа вот этого.
Но по сути все они субъективны и выбирать нужно сердцем))
Я начал писать на Feathers и понял — моё (точно так же как Angular 2 у меня «не пошел», в отличие от React), при том что у обоих библиотек куча сторонников.
20 февраля 2017 в 23:00
0↑
↓
Предположим, у меня есть некий ресурс. И я хочу получить список ресурсов, которые доступны только текущему пользователю. Критерий доступности может быть самый самый разный. В результате в БД должен уйти некий запрос вида owner = : user or participant = : user и так далее. Какие возможности тут предоставляет feathers?
20 февраля 2017 в 23:14
0↑
↓
Вопрос в том, кто должен отвечать за составление запроса — клиент или сервер.
Если логику реализует клиент, то вот тут написано как конструируется запрос.
Если же сервер, то нужно использовать либо hook, либо переопределить класс Service, как показано здесь.20 февраля 2017 в 23:15 (комментарий был изменён)
0↑
↓
Конечно же сервер, потому что это относится к правам доступа, клиенту это делегировать нельзя. Спасибо за поинт, буду смотреть.
Попутно ещё вопрос: автогенерация sdk для клиента, автогенерация документации, raml/swagger описания, есть?