Эта статья предназначена для начинающих свой путь в разработку на 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
озаглавливает что мы хотим создать новый проект, далее мы вводим название проекта, параметр
--linker
делает так, что в нашем проекте будут автоматически подключатся файлы для frontend-а: js, css, images и так далее, а также автоматически компилироваться файлы CoffeeScript и LESS что также очень удобно — но подробнее об этом позже. После этого мы переходим в директорию с сгенерированным проектом.
Подключаем 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 в вашем любимом текстовом редакторе Sublime Text: этот файл по умолчанию подключен к основному шаблону и постоянно мониторится Grunt — автоматически компилируется в importer.css
соответственно потом испортируем 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 с полными комментариями, описывающими все что там делается.
Полезные ссылки и материалы используемые при написании:
- Базовая документация Waterline
- Подробная документация о запросах Waterline
- Методы запросов Waterline
- Официальная Документация SailsJS
Автор: friktor