こんにちは。フロントエンドエンジニアのハルです。
今回は、keystone.jsで個人ブログを作った後、ポートフォリオを載せる機能を追加する想定でカスタマイズしてみたいと思います。
目次 [閉じる]
ベースの作成
ベースの作成の説明は、Qiitaで紹介した記事を投稿しました。
node.jsのCMSのkeystone.jsに触れてみようこちらを参考にしてください。
今回の設定項目
- pugとlessを選択
- galleryをNoにした
- cloudinaryはEnterキーを押してスキップした
$ yo keystone
? What is the name of your project? sample-works
? Would you like to use Pug, Nunjucks, Twig or Handlebars for templates? [pug
| nunjucks | twig | hbs] pug
? Which CSS pre-processor would you like? [less | sass | stylus] less
? Would you like to include a Blog? Yes
? Would you like to include an Image Gallery? No
? Would you like to include a Contact Form? Yes
? What would you like to call the User model? User
? Enter an email address for the first Admin user: hoge@fuga.com
? Enter a password for the first Admin user:
Please use a temporary password as it will be saved in plain text and change
it after the first login. admin
? Would you like to create a new directory for your project? No
? ------------------------------------------------
Would you like to include Email configuration in your project?
We will set you up with an email template for enquiries as well
as optional mailgun integration No
? ------------------------------------------------
KeystoneJS integrates with Cloudinary for image upload, resizing and
hosting. See http://keystonejs.com/docs/configuration/#services-cloudinary
for more info.
CloudinaryImage fields are used by the blog template.
You can skip this for now (we'll include demo account details)
Please enter your Cloudinary URL:
? ------------------------------------------------
Finally, would you like to include extra code comments in
your project? If you're new to Keystone, these may be helpful. Yes
ここまでで、ブログ機能は整っていますので、ポートフォリオ機能を追加していきます。
管理画面作成
こちらを参考にhttp://keystonejs.com/docs/database/#listsしながらやってみます。
models/Work.js
var keystone = require('keystone');
var Types = keystone.Field.Types;
// ListでWorkを作成、オプションはドキュメントを参照
var Work = new keystone.List('Work', {
map: { name: 'title' },
autokey: { path: 'slug', from: 'title', unique: true },
defaultSort: '-createdAt',
});
Work.add({
title: { label: 'タイトル', type: String, required: true },
manMonth: { label: '制作日数', type: Number },
content: { label: '説明文', type: Types.Html, wysiwyg: true, height: 400 },
message: { label: '制作ポイント', type: Types.Textarea, height: 200 },
image: { label: '画像', type: Types.CloudinaryImage },
publishedDate: { label: '投稿日', type: Date, default: Date.now },
});
Work.register();
models/Work.jsファイルを作成します。
new keystone.List()でリストを作成し、第一引数にはWorkというキーを指定します。第二引数にオプションを指定します。
基本的にはデフォルトであるブログ機能のPostを参考にしているので必要最低限です。
Work.add()の部分で、入力項目を設定しています。『 label: ○○ 』で管理画面表示用の名前を設定できます。
typeで何を入力するかを指定し、それにあったフォームが用意されます。今回は、シンプルなものを使用しました。
- type: Stringで1行入力用の欄が用意できます
- type: Numberで数字を入力できる欄が用意できます
- type: Types.Html, wysiwyg: trueでWordPressのビュジュアルエディターのような複数行テキスト欄が用意できます
- type: Types.Textareで複数行入力できる欄が用意できます
- type: Types.CloudinaryImageで 画像設定が用意できます
- type: Dateで日付設定が用意できます
keystone.js
// Configure the navigation bar in Keystone's Admin UI
keystone.set('nav', {
posts: ['posts', 'post-categories'],
works: 'works', // 追記: 管理画面の順番設定とラベル設定
enquiries: 'enquiries',
users: 'users',
});
すでにある、keystone.jsファイルを編集します。
これは任意ですが、管理画面トップ時に項目一覧のラベルと順番を指定できます。指定がない場合ラベルがotherになります。
ここで動作確認します。keystoneを実行します。
$ node keystone
管理画面にログインしてみると、Worksが追加されているかと思います。

この時点でWorksの『 + 』ボタンから追加もできるので、いくつか追加してみてください。

一覧作成
次に一覧画面を作成していきます。
routes/middleware.js
exports.initLocals = function (req, res, next) {
res.locals.navLinks = [
{ label: 'Home', key: 'home', href: '/' },
{ label: 'Blog', key: 'blog', href: '/blog' },
{ label: 'Works', key: 'works', href: '/works' }, // 追記: メニューへ追加
{ label: 'Contact', key: 'contact', href: '/contact' },
];
res.locals.user = req.user;
next();
};
『 routes/middleware.js 』を編集します。コメントがある行を追記することで、メニューの追加を行っています。
/routes/index.js
// 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('/works', routes.views.works); // 追記: 一覧のルーティング設定
app.all('/contact', routes.views.contact);
};
コメントの行を追記することで、一覧ページ用のルーティング設定を行っています。『 /works 』にアクセスがあった場合『 routes/views/ 』の『 works.js 』ファイルを指定しています。
routes/views/works.js
var keystone = require('keystone');
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// ローカルの設定
locals.section = 'works';
// mongodbから全件取得し worksへセットする
view.query('works', keystone.list('Work').model.find());
// 使用するテンプレートの指定
view.render('works');
};
mongodbからWorkを全件取得し、値を受け渡しています。『 view.render() 』でテンプレートファイルの指定をしています。
templates/views/works.pug
extends ../layouts/default
block content
.container
h1
| 制作物
.row
.col-md-12.col-lg-12
.row
each work in works
.col-md-4.col-lg-4
.thumbnail
if work.image.exists
img.img-responsive(src=work._.image.fit(640,640))
.caption
h3
!= work.title
p
a.btn.btn-success.btn-block(href='/works/' + work.slug)
| 詳細へ
ここは表示部分のコードです。pugを指定したので、pugの記法だとこのようになります。worksにデータが渡ってきているので『 each work in works 』としてループし、『 work.title 』でタイトルを取得しています。
画像は『 work.image.exists 』で保存されている画像があるかを確認し『 work._.image.fit(640,640) 』は、『._(ドットとアンダースコア)』をつけることでアンダースコアメソッドというものが用意されている便利機能が使えるようになります。
そして『 .image 』で画像のデータにアクセスして便利機能の『 .fit() 』で好きな画像サイズで画像を取得できます。
リンクはこの後の詳細で固有の情報のslugを元に、mongodbから詳細情報を取得するので『 '/works/' + work.slug 』とリンクを作成しています。
また、ここで動作確認します。さきほどの実行中だったものは止めて、再度、keystoneを実行します。
$ node keystone
下記URLにアクセスすると……。
http://localhost:3000/works
登録したものが表示されたかと思います。

詳細
routs/index.js
// 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('/works/:work', routes.views.work);// 追記: 詳細のルーティング設定
app.get('/works', routes.views.works); // 一覧のルーティング設定
app.all('/contact', routes.views.contact);
};
コメントの追記となっている部分を追記することで、詳細のルーティング設定を行っています。『/worls/xxx』でアクセスがあった場合、『 routes/views/ 』 『 works.js 』を指定しています。
さっきと違うのは『:work』でパラメータを受け取るように記述しています。
routs/views/work.js
var keystone = require('keystone');
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// ローカルの設定
locals.section = 'works';
// req.params.workでパラメーターを受けっと他ものを格納
locals.filters = {
work: req.params.work,
};
// 格納先の用意
locals.data = {
works: [],
};
// mongodbからslugで一致するものを取得しデータを受け渡し
view.on('init', function (next) {
var q = keystone.list('Work').model.findOne({
slug: locals.filters.work,
});
q.exec(function (err, result) {
locals.data.work = result;
next(err);
});
});
// 使用するテンプレートの指定
view.render('work');
};
詳細では、slugの情報が入っているパラメーターを受け取りそのパラメーターを使って、mongodbからslugで一致するデータを取得しテンプレートにデータを受け渡しています。
テンプレートファイルは『 work 』と指定しているのでwork.pugを追加します。
templates/views/work.pug
extends ../layouts/default
block content
.container
.row
.col-md-12.col-lg-12
article
p
a(href="/works")
| ← Back to works
hr
header
h1 #{data.work.title}
.row
.col-md-6.col-lg-6
if data.work.image
img(src=data.work._.image.fit(750,450)).img-responsive
.col-md-6.col-lg-6
ul.list-group
li.list-group-item.active
strong
| 制作情報
li.list-group-item
strong
| 制作時間:
| #{data.work.manMonth}日
li.list-group-item
strong
| 制作ポイント:
!= data.work.message
li.list-group-item
!= data.work.content
最後に詳細の表示部分です。一覧のとき同様、各項目を表示しています。Bootstrapが読み込まれてるのでいい感じにクラス名を指定しつつ表示させてみました。
詳細が見れるか確認してみます。実行中だったものは止めて、再度、keystoneを実行します。
$ node keystone
先ほど作っておいた、Worksの一覧ページから『 詳細へ 』ボタンをクリックして詳細を見ます。登録したものが表示されたかと思います。

やったぜ! 見られました。
まとめ
公式ドキュメントとすでにあるPostを見ながら手探りでやってみましたが、慣れればWordPressのように簡単にカスタマイズができそうでした。
手探りしながらだったので雑な記事になってしまいましたが、今後もKeystone.jsを触っていきたいと思います。
最終的には自分好みにカスタマイズしたBlogサイトを公開したいです。
LIGはWebサイト制作を支援しています。ご興味のある方は事業ぺージをぜひご覧ください。