Написание простого блога на 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

<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 Часть Статьи

Полезные ссылки и материалы используемые при написании:
  • +14
  • 20,3k
  • 6
Поделиться публикацией
Ой, у вас баннер убежал!

Ну. И что?
Реклама
Комментарии 6
    0
    Хороший полезный пост, хоть с орфографией и грамматикой и беда. Спасибо автору.
      0
      При написании завершающей статьи, постараюсь быть более аккуратным
      0
      Полезная статья, спасибо.
      Хотелось бы еще понять как можно использовать Sails.js с Angular.js
      так чтобы в случае прямого перехода по урлу страничка генерировалась на сервере, а в других случаях на клиенте (одностраничное приложение)
        0
        Я когда изучал Angular в их туториале, я просто настроил бэкенд на json API из ajax запросов (простой вариант), тогда получается что ты обрабатываешь непосредственно API. Остальное дело техники history api.)
          0
          в случае использование express я просто на любой запрос делаю:
          app.get('*', function(req, res) {
          	res.sendfile(__dirname + '/index.html');
          });
          

          т.е. всегда возвращаю одну и туже страницу с тегом <ng-view></ng-view> для того, что бы Angular смог сам обработать текущий URL в соответствии с роутингом на стороне клиента и подгрузить соответствующий partial(вьюшку)
            0
            эмм, я знаю что angular сам по себе просто как клиентское приложение может управлять путями (ng-route), зачем примешивать сюда сервер, делаешь например субдомен с api (api.domain.com), а приложение angular просто как развернутую статику в корне (domain.com) например с помощью yeoman (generator-angular), но это подходит лишь если у тебя вся обработка планируется на клиенте (только для современных браузеров, о ишаке вообще молчу). Но если ты любишь извращаться то sails.config.routes в помощь. Но sails не чистый express, тут много удобных тонкостей которые сделают из приложения Т-90.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое