[Из песочницы] Написание простого блога на SailsJS: наглядная практика для начинающих (Часть 1)
Эта статья предназначена для начинающих свой путь в разработку на 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
MY BLOG APP
views/post/watch.ejs
<%= post.title %>
sails lift Надеюсь материал из первой части был интересен и полезен, во второй части будет описываться создание сессий, авторизации, и написание простой админки, если вы не хотите ждать, или неправильно поняли как должен быть написан код то можете посмотреть полный код проекта на github с полными комментариями, описывающими все что там делается.
Полезные ссылки и материалы используемые при написании: