Синопсис
Эта статья предназначена для начинающих свой путь в разработку на NodeJS, и знакомит новичка с разработкой на этой платформе с использованием фреймворка SailsJS. В статье, будет рассматриваться процесс разработки простого блога, с сопутствующими пояснительными материалами, цель которых описать начальные навыки работы с этим фреймворком — который безусловно является отличной основой для любых проектов на NodeJS. Для лучшего усвоения материала желательно иметь основные представления о языке программирования Javascript, и его серверной реализации NodeJS, а также как минимум первичное представление о схеме MVC которая является основой Sails. Для лучшего понимания фреймворка вы можете почитать документацию на официальном сайте Sails, а также посмотреть касты описывающие работу с Sails достаточно подробно. При написании статьи я старался написать материал как можно более проще и понятнее, опытным пользователям эта статья не расскажет ничего нового, и некоторые приемы могут показаться неэффективными.
Подготовка рабочего окружения
Для начала установим сам фреймворк SailsJS, изначально предполагается что у вас уже установлен пакет NodeJS, и есть доступ в интернет. В моем случае моя ОС — Fedora 20, с вашей стороны это может быть Mac OS X, Ubuntu и другие ОС, в примере мы будет использовать бета версию, для установки SailsJS глобально введите команду
sudo npm install -g sails@beta
После этого нам нужно создать новый проект — в Sails это делается просто и понятно.
sails new sails-blog --linker cd sails-blog/
Поясню — параметр
озаглавливает что мы хотим создать новый проект, далее мы вводим название проекта, параметрnew
делает так, что в нашем проекте будут автоматически подключатся файлы для frontend-а: js, css, images и так далее, а также автоматически компилироваться файлы CoffeeScript и LESS что также очень удобно — но подробнее об этом позже. После этого мы переходим в директорию с сгенерированным проектом.--linker
Подключаем Bootstrap — знакомимся с организацией фронтенда
В Sails весьма удобно организована сторона отвечающая за размещение фронтенда, сервер транслирует все файлы которые располагаются в папке /assets которая находится в корне проекта. Сами же файлы из папки assets имеют примерно такой доступ, объясню на пальцах: предположим вы хотите разместить некую картинку image.png, вы размещаете ее в директории assets/images/ — в таком случае при работающем сервере этот файл будет доступен по адресу Хост/images/image.png. Это базовые сведения, а теперь установим bootstrap скачиваем архив с исходниками на LESS (люблю этот язык стилей).
Распакуйте папку less из архива в assets/styles/ — должно получится такое расположение папки assets/styles/less, далее переименуйте папку less в bootstrap (для удобства), далее после распаковки основной части bootstrap-а, мы должны подключить глифы, для этого скопируйте папку fonts из архива в корень assets (прим. /assets/fonts). Теперь откройте файл /assets/styles/importer.less в вашем любимом текстовом редакторе
соответственно потом испортируем bootstrap добавив в этот файл строку
@import 'bootstrap/bootstrap';
Чтобы подключить глифы в том-же importer.less нужно объявить переменную которая будет указывать путь к папке с глифами, т.к наши глифы располагаются в папке fonts — мы добавляем следующую строку в файл
@icon-font-path: '/fonts/';
Чтобы окончательно установить bootstrap нам осталось только закинуть файл jquery.js и boostrap.js в папку assets/js/dependencies/.
На этом мы закончим первоначальное знакомство с организацией фронтенда и статики в Sails и перейдем непосредственно к разработке самого Блога.
Создаем API Post — первое знакомство с моделями, контроллерами
Для начала создадим комплекс API — состоящий из модели и контроллера, которые мы назовем post по вполне очевидным причинам, для создания комплекса API введем следующую команду:
sails generate api post
Сгенерированные файлы будут находится в одноименный папках в директории api/, Sails по умолчанию создает CRUD API готовое к эксплутации, подробнее можно посмотреть на описывающем Sails видео.
Теперь откроем созданную нами ранее модель Post и начнем писать код, в модели нам нужно указать название атрибута, его тип, и валидатор. Сейчас приведу содержимое нашей модели.
api/models/Post.js
module.exports = { attributes: { title: { type: 'string', maxLength: 120, required: true }, description: { type: 'string', required: true }, content: { type: 'string', required: true } } };
Составляющая нашей модели строиться очень похожей на JSON, что делает ее весьма понятной и удобной, как вы могли понять внутри конструкции atributes мы перечисляем атрибуты модели, в нашем случае нам нужно 3 атрибута — заголовок, короткое описание и контент. У всех 3 тип — строка, у заголовка установлено 2 валидатора: maxLength: максимальная длина строки, required: обязательный ли это атрибут при создании новой записи (в нашем случае обязательный), далее задаем параметры для оставшихся 2 атрибутов, для sails существуют десятки типов, и валидаторов на все случаи жизни (даже валидатор цвета HEX), посмотреть полный список вы можете здесь.
Итак, мы составили нашу первую модель которая будет отвечать за наши записи в БД — и манипуляции с ними. Теперь можем перейти к основным действиям с контроллером — api/controllers/PostController.js.
Манипуляции с контроллерами также происходят в удобном представлении JSON, для начала перечислим что мы хотим чтобы блог умел — и соответственно разделим задачи на элементы контроллера. Наш блог должен уметь отображать список постов с коротким описанием по убыванию (новые посты в начале, старые в конце), уметь разбивать список постов на страницы (пагинация) и страница на которой мы сможем увидеть полный контент отдельного поста с комментариями (disqus). Таким об��азом для себя я разделил эти возможности на 3 атрибута контроллера, и 3 основные функции манипуляции с записями, индексный: отображение последних 10 постов. просмотр: отображение полного контента конкретной запси.
пагинация — разбиение списка, и просмотр списка на определенном срезе списка. Начнем написание кода с функционала записи — добавление, обновление, удаление. Внутри module.exports — будем писать код.
Утилиты
Создание
create: function (req, res) { var params = { description : req.param('description'), content : req.param('content'), title : req.param('title'), } Post.create(params).exec(function (err, post) { res.redirect('/post/watch/' + post.id); if (err) return res.send(500); }); }
В этом коде как вы поняли мы описываем создание новой записи в БД, как я уже говорил в Sails по-умолчанию встроено CRUD API, это значит что каждому подконтроллеру по url можно передать параметры с помощью GET или POST, а Sails уже сможет их обрабатывать.
Post.create — означает 1) что мы собираемся работать с моделью Post а метод create отвечает за создание новой записи, в которую нужно передать список в котором мы указываем атрибут записи, и значение этого атрибута, в нашем случае запись должна генерироваться от передачи ей аргументов в котором мы и используем CRUD, в списке params я указываю в значении передаваемые параметры, если вам не понятно как это делается то объясню на пальцах, чтобы в нашем случае создать запись — мы отправляем POST запрос (например через Postman) с параметрами для title, description, content — на url /post/create принятые на этом url параметры могут вызываться с помощью req.param('параметр') что мы и сделали. 2) В методе exec мы используем анонимную функцию которая принимает в качестве аргументов err — возникшие в процессе создания ошибки, и post — данные с только что созданного нами поста, которые в дальнейшем мы обрабатываем таким образом чтобы если ошибка — он выдавал страницу 500, при успешном создании (когда мы получаем данные поста) мы перенаправляем на страницу с полным описанием (рассмотрим этот контроллер далее) передав в url идентификатор поста.
Следующим вспомогательным подконтроллером мы сделаем подконтроллер обновления данных, который очень удобен если нужно сделать редактирование информации.
Обновление
update: function (req, res) { var Id = req.param('id'); var elem = { description : req.param('description'), content : req.param('content'), title : req.param('title') }; Post.update(Id, elem).exec(function (err) { if (err) return res.send(500); res.redirect('/'); }); }
В этом случае метод update очень похож на метод create — разница в том что первым аргументом мы указываем id записи — которую как и в прошлый раз мы получаем из переданного параметра. Суть этого кода как я думаю вы тоже уже уловили. Последней утилитой мы сделаем удаление записи.
Удаление
delete: function (req, res) { var Id = req.param('id'); Post.destroy(Id).exec(function (err) { if (err) return res.send(500); res.redirect('/post'); }); }
Для удаления записи нам нужно только указать id.
Основная часть + работа с представлениями (views)
Сейчас мы рассмотрим написание основной «лицевой» части нашего приложения, какие это части я описал выше, по традиции начнем с индексной страницы, многие могут сказать что в качестве индекса я мог бы просто назначить 1 страницу среза пагинации, но думаю новичкам будет лучше разжевать лишний раз. и так индекс.
index: function (req, res) { Post.find() .sort('id DESC') .limit(5) .exec(function (err, posts) { if (err) return res.send(500); res.view({ posts: posts }); }); }
Теперь начну пояснения кода — метод find отвечает за поиск записей в модели, в качестве аргументов к нему мы ничего не вызываем — значит под совпадение подходят все записи, после этого мы сортируем записи в порядке убывания — в данном случае я использую id только в качестве примера, если вы собираетесь использовать такие БД как MySQL, Mongo и т.д то вы должны заменить id на createdAt по вполне очевидным причинам, и последней в нашем списке будет произведение первичного сечения списка постов с лимитом в 5 записей. После того как все процедуры с моделью закончены она возвращает нам список постов в нужном порядке, и количестве: так что дальше мы сможем использовать его в нашем представлении. Как вы помните из предыдущих манипуляций мы используем анонимную функцию в методе exec чтобы провести конечную обработку данных. Так что теперь перейдем к ключевой части — методу отображения, за это отвечает метод view в который мы передаем список того что будет доступно нам при составлении представления, в нашем случае это объектный список, для доступа я создаю элемент списка атрибута с названием posts и значением — возвращенным нам анонимной функцией атрибут posts.
Целостный Просмотр
watch: function (req, res) { var Id = req.param('id'); Post.findOne(Id).exec(function (err, post) { if (!post) return res.send(404); if (err) return res.send(500); res.view({ post: post }); }); }
Здесь в качестве аргумента метода findOne мы передаем аргумент идентификатора — который также является запросом, в ответ он выдает нам данные отдельного поста, к которым мы даем доступ из метода view.
Далее рассмотрим контроллер пагинации и настройки путей blueprints и перейдем непосредственно к составлению представления.
page: function (req, res) { var page = req.param('page'); Post.find() .sort('id DESC') .paginate({ page : page, limit: 5 }) .exec(function (err, posts) { if (err) return res.send(500); res.view({ posts: posts }); }); }
Здесь мы делаем практически тоже что и в индексном контроллере, с той разницей что сюда мы добавили метод paginate, который принимает в качестве аргумента JSON в котором мы должны указать лимит записей на страницу, и саму страницу среза которую мы хотим отобразить. Чтобы сделать страницу среза более динамичной — мы создаем переменную page — с запросом который будет задавать страницу — для большего удобства мы сделаем передачу этого аргумента как get запрос — без лишних элементов запроса: напрямую, в конфигурации путей. Для этого откроем файл config/routes.js и начнем правку. Добавьте в module.exports.routes следующее:
'get /post/:page': { controller: 'post', // Контроллер action: 'page' // Действие },
Что тут делается? в принципе все предельно просто мы назначаем путь — вид запроса сначала, url, и передаваемый атрибут - :page — который мы использовали в нашем контроллере (req.param('page')) и упростили вариант его передачи в контроллер (думаю это лучше - /post/page?page=2). Заодно с пагинацией зададим упрощенную схему управления для наших утилитных функций:
'post /post/create': { controller: 'post', action: 'create' }, 'get /post/delete/:id': { controller: 'post', action: 'delete' }, 'post /post/update': { controller: 'post', action: 'update' }
Итак, мы произвели основные манипуляции с контроллером Post и теперь для окончательного вступления в силу нам осталось только написать представление — которое будет лицом приложения. Если кого затруднило составление кода — вот полный вариант контроллера Post с комментариями.
Представления
Представления в Sails строятся автоматически по контроллерам, мы создали контроллер Post — значит папка с представлениями этого контроллера будет находится по адресу views/post/*, а представления имеют имена подконтроллеров в которых есть метод views, Sails поддерживает множество шаблонизаторов включая Jade, Handlebars, HAML и другие но по умолчанию в него встроен EJS так что наши представления будут строится на нем. Создадим папку post в views, и добавим туда файл index.ejs и page.ejs c следующим содержанием:
views/post/index.ejs and views/post/page.ejs
<div class="container text-center"> <h2 class="text-center">MY BLOG APP</h2> <div class="row"> <div class="col-md-1"></div> <div class="col-md-10"> <% _.each(posts, function (post) { %> <div class="panel panel-default"> <div class="panel-body"> <h3 class="text-center"><%= post.title %></h3><hr> <article> <%= post.description %> </article> </div> <div class="panel-footer"> <a href="/post/watch/<%= post.id %>" class="btn btn-info">LEARN MORE</a> </div> </div> <% }) %> </div> <div class="col-md-1"> </div> <ul class="pagination"> <li><a href="/post">1</a></li> <li><a href="/post/2">2</a></li> <li><a href="/post/3">3</a></li> </ul> </div>
_.each() в качестве первого параметра — массив значений, следующий callback который выдает нам данные от единичного элемента массива (чем-то похоже на ng-repeat из angular), далее мы конструируем данные из который должен строиться повтор значений, думаю те кто знакомы с EJS понимают что значения переменных мы заключаем в <%= %> т.к это текст, или в <% %> заключаем функции (если совсем просто объяснять). Так что думаю основной поток информации о EJS вам понятен хотя-бы на интуитивном уровне — если нет то документация в помощь. И последнее представление — это единичное отображение конкретной записи — views/post/watch.ejs
views/post/watch.ejs
<div class="container"> <div class="panel panel-default"> <div class="panel-body text-center"> <h3><%= post.title %></h3><hr> <article> <%= post.content %> </article> </div> </div> </div>
На этом основная часть функционала нашего блога создана — он умеет создавать, редактировать и удалять посты, в него включена пагинация, и просмотр отдельных записей, да пока у нас нет формы в админке которая могла бы создавать записи визуально — но для начала можно протестировать используя Postman, предварительно запустив тестовый сервер командой
sails lift
Надеюсь материал из первой части был интересен и полезен, во второй части будет описываться создание сессий, авторизации, и написание простой админки, если вы не хотите ждать, или неправильно поняли как должен быть написан код то можете посмотреть полный код проекта на github с полными комментариями, описывающими все что там делается.
2 Часть Статьи
Полезные ссылки и материалы используемые при написании:
