Alternativa de CMS com Keystone.js

Este é o primeiro artigo de uma série sobre o Keystone.js, um framework desenvolvido em Node.js para servir de CMS e também Web Application. Pra começarmos, será necessário nesse primeiro artigo um pouco de teoria pra termos uma noção.

Aos apressados, o link do repo está disponível no github.

Pra começar, não vou falar detalhadamente sobre sua descrição pois no site deles está bem explicado, apenas um resumo sobre, e depois quando formos iniciar o projeto ficará mais claro.

O Keystone.js utiliza o Express, então para muitos a curva de aprendizado será bem pequena e é um ponto bom pois a customização (quando necessária) será tranquila. Na interface admin, utilizam o React.js. Este framework me chamou muito a atenção pelo fato de agilizar MUITA coisa no desenvolvimento, e ao mesmo tempo não deixar o desenvolvedor sem saber o que ocorre por baixo dos panos, os módulos de rotas/models/views são bem organizados.

Atualmente a comunidade Keystone.js está trabalhando na próxima versão (0.4) que terá um rebuild da interface admin com o React e mais novidades.

O projeto que faremos utilizará a versão 0.3.16

A idéia nessa série é desenvolver um CMS (obviamente) onde vamos publicar posts, cadastro e listagem de produtos, e também customizar o Framework (é Javascript!) para termos uma API para posterior consumo.

Showmethecode!

Pré requisitos: Node.js e Mongodb

npm install -g generator-keystone
mkdir projeto-keystone
cd projeto-keystone
yo keystone
node keystone

Após a instalação via NPM, o generator fará algumas perguntas sobre as engines que deseja utilizar e informações sobre seu projeto (exemplo nome), esta parte fica a seu critério. Itens como template engine, pré-processor, taskers…

Screen Shot 2016-03-10 at 3.41.32 PM

Agora você já consegue navegar em http://localhost:3000 pra ter uma noção do que o framework estruturou para você. Nesse momento é possível reparar que já temos um Blog e uma Galeria de imagens (utilizando uma conta temporária da Cloudinary).

Para acessar o admin, navegue em http://localhost:3000/keystone e informe usuário e senha que informou no generator (se não informou nada, é user: user@keystonejs.com pass: admin)

Um pouco sobre as models e rotas

Esta parte é uma mão na roda, dê um check na estrutura de ./models/Post.js e ./models/Gallery.js pra saber do que estamos tratando. O interessante é que no momento de estruturar as models, você não precisa se preocupar em criar o formulário de cadastro/edição no adminUI (o Keystone.js faz isso conforme vc seta as propriedades da model).

Já as rotas ficam em ./routes/index.js para serem registradas e possuímos o arquivo ./routes/middleware.js para interceptar as requests e tratarmos de acordo com a nossa necessidade (veremos mais adiante).

Uma boa prática que adotamos aqui na Ornito é separar a pasta de rotas em ./routes/api/* e ./routes/views/* onde respectivamente incluiremos os arquivos de API  que retornaremos nosso JSON e ServerViews que vamos renderizar pela template engine (Jade foi a escolhida).

Cadastro de Produtos

Como ao subir o keystone pela primeira vez, ele gerou as models de Post e Galeria para nós, não teremos muito trabalho nesta parte (já está pronto), então vamos focar no nosso código agora para criação de produtos.

Nosso fluxo de criação: definir Model, definir Rota, definir View.

Vamos criar o arquivo ./models/Produto.js

// models/Produto.js

var keystone = require('keystone');
var Types = keystone.Field.Types;

/**
 * Model Produto
 * ==========
 */

var Produto = new keystone.List('Produto', {
  map: { name: 'produto' },
  autokey: { path: 'slug', from: 'produto', unique: true }
});

Produto.add({
  produto: { type: String, required: true },
  ativo: { type: Types.Boolean, initial: true, required: true },
  preco: { type: Types.Money, format: '$0.0,00', label: 'Valor do produto', },
  descricao: {
    breve: { type: Types.Textarea, height: 150, label: "Breve descrição" },
    completa: { type: Types.Html, wysiwyg: true, height: 200, label: "Descricao completa" }
  },
  criadoEm: { type: Types.Date, index: true, default: new Date() },
  detalhes: { type: Types.TextArray },
  imagens: { type: Types.CloudinaryImages }
  
});

Produto.schema.virtual('descricao.full').get(function() {
  return this.descricao.completa || this.descricao.breve;
});

Produto.defaultColumns = 'produto, ativo, preco, criadoEm';
Produto.register();

Já criamos a model, então podemos navegar pelo admin, e http://localhost:3000/keystone nos levará para o cadastro de um produto. Preencha as informações do produto.

Agora temos que configurar a rota em ./routes/views/produtos.js , ./routes/index.js e ./routes/middleware.js

// routes/views/produtos.js

var keystone = require('keystone');

exports = module.exports = function(req, res) {
	
	var view = new keystone.View(req, res);
	var locals = res.locals;
	
	// Set locals
	locals.section = 'produtos';
	
	// Load the galleries by sortOrder
	view.query('produtos', keystone.list('Produto').model.find().sort('sortOrder'));
	
	// Render the view
	view.render('produtos');
	
};

A rota views/produtos.js é a responsável por consultar nossa base no mongodb e retornar os produtos existentes.

// routes/index.js

var keystone = require('keystone');
var middleware = require('./middleware');
var importRoutes = keystone.importer(__dirname);

// Common Middleware
keystone.pre('routes', middleware.initLocals);
keystone.pre('render', middleware.flashMessages);

// Import Route Controllers
var routes = {
	views: importRoutes('./views')
};

// Setup Route Bindings
exports = module.exports = function(app) {
	
	// Views
	app.get('/', routes.views.index);
	app.get('/blog/:category?', routes.views.blog);
	app.get('/blog/post/:post', routes.views.post);
	app.get('/gallery', routes.views.gallery);
	app.all('/contact', routes.views.contact);

	app.get('/produtos', routes.views.produtos);

};

Em routes/index.js registramos igual a um app comum em express com app.get() e apontamos para nossa rota do produtos, a qual vamos consultar os produtos na base.

// routes/middleware.js

var _ = require('underscore');


/**
	Initialises the standard view locals
*/

exports.initLocals = function(req, res, next) {
	
	var locals = res.locals;
	
	locals.navLinks = [
		{ label: 'Home',		key: 'home',		href: '/' },
		{ label: 'Blog',		key: 'blog',		href: '/blog' },
		{ label: 'Gallery',		key: 'gallery',		href: '/gallery' },
		{ label: 'Contact',		key: 'contact',		href: '/contact' },
		{ label: 'Produtos',		key: 'produtos',		href: '/produtos' }
	];
	
	locals.user = req.user;
	
	next();
	
};


/**
	Fetches and clears the flashMessages before a view is rendered
*/

exports.flashMessages = function(req, res, next) {
	
	var flashMessages = {
		info: req.flash('info'),
		success: req.flash('success'),
		warning: req.flash('warning'),
		error: req.flash('error')
	};
	
	res.locals.messages = _.any(flashMessages, function(msgs) { return msgs.length; }) ? flashMessages : false;
	
	next();
	
};


/**
	Prevents people from accessing protected pages when they're not signed in
 */

exports.requireUser = function(req, res, next) {
	
	if (!req.user) {
		req.flash('error', 'Please sign in to access this page.');
		res.redirect('/keystone/signin');
	} else {
		next();
	}
	
};

Em routes/middeware.js nós estamos apenas estruturando os navLinks para exibirmos o link de produtos na home.

E agora vamos exibir nossos produtos em ./templates/views/produtos.jade

//- templates/views/produtos.jade

extends ../layouts/default

block intro
	.container
		h1 Nossos produtos
	
block content
	.container
		if produtos.length
			each item in produtos
				h2= item.produto
					.pull-right.text-muted R$ #{item.preco}

				p= item.descricao.breve
				
				.row.gallery-images
					each image in item.imagens
						.col-xs-6.col-sm-4.col-md-3.gallery-image: img(src=image.limit(300,300)).img-rounded
		else
			h3.text-muted Ainda não temos produtos cadastrados 🙁

Navegue em http://localhost:3000/produtos e verá o resultado :)

Screen Shot 2016-03-11 at 6.10.11 PM

 

Bom, este primeiro artigo foi um pouco extenso e não foi possível cobrir TODOS os detalhes. Se você se interessou pelo Framework, é um MUST dar uma olhada na documentação sobre tipos de dados, formatos, middlewares, serviços já integrados e tudo mais.

Fiz aqui uma pequena imersão ao framework e mostrar o que ele pode fazer com pouco tempo e dedicação. No próximo artigo vamos ao detalhe do produto, e também começar com nossa API (para produtos e posts).

Repo: https://github.com/victorkurauchi/post-keystone

Até mais.

@victorkurauchi

 

 

 

 

 

Source: Ornito

Recent Posts

Leave a Comment