И снова о Feathers JS. Как мы за 5 минут создали серверное приложение, а теперь разбираемся что же мы создали…

в 15:22, , рубрики: express.js, feathersjs, javascript, Kickidler, node.js, Блог компании Kickidler

И снова о Feathers JS. Как мы за 5 минут создали серверное приложение, а теперь разбираемся что же мы создали… - 1

Итак, в первой части мы использовали потрясающие возможности Feathers.js для того, чтобы за 5 минут создать backend для нашего приложения.

Наша система умеет работать с базами данных и может использовать аутентификацию. Но понятно, что базовых возможностей сгенерированного кода недостаточно для реализации всего необходимого нам функционала.

Поэтому пришла пора «поднять капот и заглянуть в потроха» нашего кода.

Итак, в результате работы генератора, у нас есть следующие файлы:

И снова о Feathers JS. Как мы за 5 минут создали серверное приложение, а теперь разбираемся что же мы создали… - 2

Давайте с ними разбираться.

Каталог public нам не интересен. Так (как и в Express) хранятся статические ресурсы, которые могут быть показаны при подключению к нашему северному приложению через браузер.
Изначально в этом каталоге хранится страница html, сообщающая о том, что вы запустили приложение Feathers.

В каталоге config находятся конфигурационные файлы («а ты думал обнаружить тут епископа?» /Джон Сильвер) для настройки работы нашего приложения. Их два, default используется в процессе разработки, а production — после оной.

Формат этих файлов одинаков:

{
  "host": "localhost",
  "port": 3030,
  "nedb": "../data/",
  "public": "../public/",
  "auth": {
    "token": {
      "secret": "bsnN3z77dVqo+HYUglvAm8pmTpECN159ZYriLpdXNqNMwEURjUV+f2qY2bCt0yuOJPb4rMMTQMJhf4s/LQPzAg=="
    },
    "local": {}
  }
}

Структура этого файла очень проста. Вначале указывается хост и порт, на котором работает сервер. Затем пути к каталогам с данными (в данном случае с файлами базы nedb) и к рассмотренному выше каталогу public. Ну и в самом конце — параметры для аутентификации, с которой нам ещё предстоит познакомиться.

В корне каталога src лежат два интересных файла. Первый и главный (но очень короткий) — index.js.

'use strict';
// Получаем экземпляр нашего приложения Feathers (подробнее рассмотрим чуть дальше)
const app = require('./app'); 

// Получаем номер порта (который взят из файла конфигурации)
const port = app.get('port'); 
// Запускаем наш сервер. Ура, товарищи!
const server = app.listen(port); 

// Как только сервер начнет работать, выводим сообщение об этом
server.on('listening', () =>
  console.log(`Feathers application started on ${app.get('host')}:${port}`)
);

Вся информация о работе с приложением сосредоточена во втором файле — app.js.

Ждёте, что он будет большим и сложным? Вынужден огорчить… Те, кто уже программировал на Express.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');

// Создаем приложение Feathers
const app = feathers();

// Считываем и применяем тот самый файл конфигурации, который рассматривали абзацем выше
app.configure(configuration(path.join(__dirname, '..'))); 

// Подключаем к приложению различные модули
app.use(compress())
// CORS для любых доменов (помните как мы настраивали этот пункт в прошлый раз?)
  .options('*', cors()) 
  .use(cors())
// показывать в браузере иконку при открытии html-страницы
  .use(favicon( path.join(app.get('public'), 'favicon.ico') )) 
  .use('/', serveStatic( app.get('public') )) // использовать каталог public как главный для web-сервера
  .use(bodyParser.json()) 
  .use(bodyParser.urlencoded({ extended: true }))
// подключаем глобальные хуки Feathers, которые будут применяться ко всем запросам
  .configure(hooks())
// подключаем поддержку REST, теперь любой сервис автоматически доступен через этот протокол
  .configure(rest())
// подключаем поддержку Socket.io, теперь любой сервис автоматически доступен через этот протокол             
  .configure(socketio())      
// включаем все имеющиеся в приложении сервисы 
  .configure(services)        
// включаем всё middleware приложения (работает так же), как и в Express
  .configure(middleware); 
module.exports = app;

Основная прелесть в том, что ни один из этих файлов вам не нужно менять. Ну разве что по мелочи :)

Например можно добавить альтернативный логгер. Хотя — стоп! Зачем добавлять его в app.js, если мы автоматом подключили всё middleware и именно туда надо добавить логгер?!

Откроем папку middleware, а в ней файл index.js. Тут комментарии уже не мои, это генератор постарался.

'use strict';
const handler = require('feathers-errors/handler');
const notFound = require('./not-found-handler');
const logger = require('./logger');

module.exports = function() {
  // Add your custom middleware here. Remember, that
  // just like Express the order matters, so error
  // handling middleware should go last.
  const app = this;

  app.use(notFound());
  app.use(logger(app));
  app.use(handler());
};

То есть для того, чтобы добавить свой промежуточный модуль, достаточно:

1. Описать его в соответствующем файле в каталоге middleware.
2. Подключить с помощью require.
3. Добавить в цепочку с помощьюapp.use.

Например простое middleware not-found-handler.js, предназначенное для обработки запросов к несуществующим ресурсам, выглядит вот так:

const errors = require('feathers-errors');

module.exports = function() {
  return function(req, res, next) {
    next(new errors.NotFound('Page not found'));
  };
};

Как видите, каждое промежуточное ПО представляет собой функцию, которая возвращает функцию, в которой вызывается функция next() для перехода к следующему middleware. Да, всё как в express.js, да и модули от неё работают в Feathers.

А вот чего в Express нет, так это хуков. Делятся hooks на глобальные (работающие со всем приложением) и локальные для каждого сервиса. Hooks вызываются до и после вызова методов сервиса. Реализация самого простого хука предлагается нам генератором в файле hooks/index.js

exports.myHook = function(options) {
  return function(hook) {
    console.log('My custom global hook ran. Feathers is awesome!');
  };
};

No comments! Подключать его будем позже.

Уфф! Мы закончили с подготовительной частью и переходим к сердцу Feathers — сервисам.

Сервисы хранятся в каталоге services, состоят из файла index.js и подкаталога hooks, где хранятся специфичные для данного сервиса хуки.

Сервисы бывают обычные и для работы с базами данных. Обычные сервисы — это объекты, которые реализуют несколько методов (или часть из них):

  • find
  • get
  • create
  • update
  • patch
  • remove
  • setup

После того как метод реализован, Feathers будет запускать его в ответ на соответствующий запрос по протоколам REST, Socket.io или Primus. Напомню, прелесть в том, что вам не нужно подключать обработчики этих протоколов — просто реализуйте методы!

Каждый из методов должен возвращать объект класса Promise, например вот так:

get(id, params) {
  return Promise.resolve({
    id,
    read: false,
    text: `Feathers is great!`,
    createdAt: new Date().getTime()
  });

В результате, после запроса типа GET, в браузер будет возвращаться соответствующий объект, упакованный в json (помните, что мы подключали поддержку json в app.js?)

В следующем примере я приведу одновременно и шаблон для создания сервиса (ровно то же создаст для вас генератор) и соответствие между методами сервиса и запросами REST.

const myService = {
  // GET /messages
  find(params [, callback]) {},
  // GET /messages/<id>
  get(id, params [, callback]) {},
  // POST /messages
  create(data, params [, callback]) {},
  // PUT /messages[/<id>]
  update(id, data, params [, callback]) {},
  // PATCH /messages[/<id>]
  patch(id, data, params [, callback]) {},
  // DELETE /messages[/<id>]
  remove(id, params [, callback]) {},
  setup(app, path) {}
}

А для того, чтобы при работе с базой данных, не реализовывать эти методы вручную — генератор создает для вас специальные сервисы. Выглядят они вот так (комментарии мои):

const path = require('path');
// подключаем библиотеку для работы с нужной нам базой (в данном случае NeDB)
const NeDB = require('nedb');
// и библиотеку Feathers для взаимодействия с этой базой
const service = require('feathers-nedb');
// подключим локальные хуки
const hooks = require('./hooks');

module.exports = function(){
  const app = this;

// определим параметры для работы с базой
// каталог возьмем из конфигурационного файла, имя базы по умолчанию = имя сервис
  const db = new NeDB({
    filename: path.join(app.get('nedb'), 'contacts.db'),
// этот параметр означает автоматическую загрузку данных, в противном случае данные нужно 
// будет подгружать вручную
    autoload: true
  });

//определяем параметры БД
  let options = {
    Model: db,
// передается ли информация целиком или постранично
    paginate: {
      default: 5,
      max: 25
    }
  };

// Дальше комментарии генератора весьма полезны

  // Initialize our service with any options it requires
  app.use('/contacts', service(options));

  // Get our initialize service to that we can bind hooks
  const contactsService = app.service('/contacts');

  // Set up our before hooks
  contactsService.before(hooks.before);

  // Set up our after hooks
  contactsService.after(hooks.after);
};

Единственны минус такого подхода в том, что запросы по факту формируются на стороне клиента и напрямую передаются в базу для обработки.

Вы спросите у меня — а как же быть, если нужно проверить запрос от пользователя, изменить его или поправить набор возвращаемых данных;

Ха, отвечу я вам! А для этого у нас есть хуки!

По умолчанию для каждого сервиса создается свой файл index.js, в который вы можете поместить специфичные хуки.

 // подключили глобальные хуки
const globalHooks = require('../../../hooks');
// подключили стандартные хуки Feathers
const hooks = require('feathers-hooks');
// описываем специфичные хуки, которые будут вызываться ДО вызова сервиса
// например для проверки параметров
exports.before = {
  all: [],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};
// описываем специфичные хуки, которые будут вызываться ПОСЛЕ вызова сервиса
// например для изменения полученных из базы результатов
exports.after = {
  all: [],
  find: [],
  get: [],
  create: [],
  update: [],
  patch: [],
  remove: []
};

Хуки определяются как функции. Каждый хук ДО получит следующие параметры:

  • method — имя вызываемого метода
  • type — hook type (before или after)
  • params — параметры, переданные сервису
  • data — данные запроса (для методов create, update и patch)
  • app — объект app
  • id — id (для get, remove, update и patch)

Хуки ПОСЛЕ дополнительно получают еще и параметр result — результат запроса.

Ну и вишенка на торте! Есть готовые хуки, которые позволяют выполнить стандартные операции с данными. Вот, например, хук remove позволяет удалить некоторые поля из результатов запроса или из самих запросов:

app.service('users').before({
  create: hooks.remove('_id'),
  update: hooks.remove('_id'),
  patch: hooks.remove('_id})

Т.е. при использовании запросов create, update и patch из запросов будет удалено поле _id

app.service('users').after(hooks.remove('password', 'salt'));

А тут мы из результата запроса удалим поля password и salt.

Таких буков достаточно много, все они описаны здесь.

Ура! Мы практически закончили с теорией и в третьей части сможем написать что-то сами. Продолжение следует :)

Маленький анонс

Как вы наверное видите из оформления блога, я работаю в компании, которая создает систему учета рабочего времени Kickidler.
Большинство из вас к контролю относится отрицательно и как программист я вас понимаю.
Но поскольку программу хорошо покупают, то выходит, что руководители имеют немного другое мнение.

Поэтому в свое свободное время я решил сделать систему личной продуктивности. Она нужна для того, чтобы мы сами знали на что тратим свою жизнь :) Система не будет ничего никому отсылать, она попробует помочь вам (тем, кто этого действительно хочет) стать немного эффективнее.
Написание этой системы было своего рода экспериментом — она целиком создана на JavaScript, даже агент собирающий информацию. В качестве работает Feathers, на клиенте React, данные хранятся в NeDB.

Так вот, в течение ближайшего месяца у меня в планах представить систему на тестирование. Поддерживаться будут Windows и MacOS X, отчетов будет минимальное количество. Но, я буду ждать ваших отзывов и (самое главное!) предложений о том, какой функционал вам нужен.

Автор: Kickidler

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js