Feathers JS — как создать backend для своего приложения всего за 5 минут

0280af442e32497da6626a5272dc7d74.jpg

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.org
            • 20 февраля 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)), но меня больше устраивает вариант, когда сервер управляет данными, а на клиенте с ними работает React
      • 20 февраля 2017 в 21:08

        0

        А вот как на этот вопрос отвечают создатели Feathers: https://docs.feathersjs.com/why/vs/sails.html
        • 20 февраля 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 описания, есть?

© Habrahabr.ru