
NodeJSのフレームワークと言えばExpressですが、毎回同じフレームワークを使っていては飽きてしまいます。今回は、もう一つのNodeJSフレームワークであるRestifyを使ってAPIを作ってみましょう。
見出し
はじめに
Restifyは、Production Ready、Debuggable、Semantically Correctを掲げているNodeJSのフレームワークです。簡単に言うとExpressの軽量版のようなフレームワークです。NPM、Netflix、Pinterestなどで利用されています。
それでは、始めましょう。
前提
以下がインストールされている必要があります。
- NodeJS v10
- Yarn
- Docker
詳しくは、「環境」を参照してください。
MongoDBの準備
Dockerで一時的にMongoDBを用意します。
$ docker container run -d --rm --name mongo-docker -p 27018:27017 mongo:3.6.3
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
837afddb96f3 mongo:3.6.3 "docker-entrypoint.s…" 6 seconds ago Up 5 seconds 0.0.0.0:27018->27017/tcp mongo-docker
$ mongo --port 27018
MongoDB shell version v4.0.2
connecting to: mongodb://127.0.0.1:27018/
MongoDB server version: 3.6.3
...
> exit
bye
ローカルでMongoDBを起動している人もいると思うので、ポートは27018としています。
API作成
さっそくAPIを作っていきましょう。
ベースプロジェクトの作成
今回は、本をCRUDするAPIを作ります。まずはベースのプロジェクトを作りましょう。
$ mkdir nodejs-restify-rest-api
$ cd nodejs-restify-rest-api/
$ yarn init -y
$ yarn add restify restify-errors mongoose joi
$ touch app.js
$ mkdir utils
$ touch utils/respond.js
$ mkdir models
$ touch models/Book.js
$ tree -L 2 -I node_modules
.
├── app.js
├── models
│ └── Book.js
├── package.json
├── utils
│ └── respond.js
└── yarn.lock
雛形ができました。
APIの実装
実装しましょう
Book.js
const mongoose = require('mongoose');
const { Schema } = mongoose;
const BookSchema = new Schema({
isbn: String,
title: String,
author: String,
pages: Number,
});
const Book = mongoose.model('books', BookSchema);
module.exports = Book;
respond.js
const respond = (res, next, data, statusCode) => {
res.header('content-type', 'application/json');
res.send(statusCode, data);
return next();
};
module.exports = respond;
app.js
動作確認
それでは、Curlを使って動作確認をしてみましょう。
正しく動いていますね。
バリデーションを追加する
最後に、バリデーションを追加していきましょう。
バリデーションの実装
Restifyのバリデーションのライブラリはあまりメンテナンスがされていないものが多いので、Joiを使って実装することにします。
app.js
const restify = require('restify');
const {
BadRequestError,
NotFoundError,
InternalError,
} = require('restify-errors');
const Joi = require('joi');
const mongoose = require('mongoose');
const respond = require('./utils/respond');
const Book = require('./models/Book');
const PORT = process.env.PORT || 3000;
const MONGO_URL = 'mongodb://localhost:27018/testdb';
mongoose.connect(
MONGO_URL,
{ useNewUrlParser: true },
);
const server = restify.createServer();
server.use(restify.plugins.acceptParser(server.acceptable));
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());
server.get('/books', async (req, res, next) => {
try {
const books = await Book.find({});
return respond(res, next, books, 200);
} catch (error) {
return next(
new InternalError({
toJSON: () => ({
errors: [{ message: 'Database Error' }],
}),
}),
);
}
});
server.get('/books/:id', async (req, res, next) => {
const schema = Joi.object().keys({
id: Joi.string()
.length(24)
.required(),
});
const result = Joi.validate(req.params, schema);
if (result.error) {
return next(
new BadRequestError({
toJSON: () => ({
errors: result.error.details,
}),
}),
);
}
try {
const book = await Book.findById(req.params.id);
if (!book) {
return next(
new NotFoundError({
toJSON: () => ({
errors: [{ message: 'The book NOT found' }],
}),
}),
);
}
return respond(res, next, book, 200);
} catch (error) {
return next(
new InternalError({
toJSON: () => ({
errors: [{ message: 'Database Error' }],
}),
}),
);
}
});
server.post('/books', async (req, res, next) => {
const schema = Joi.object().keys({
isbn: Joi.string()
.length(13)
.required(),
title: Joi.string().required(),
author: Joi.string().required(),
pages: Joi.number().required(),
});
const result = Joi.validate(req.body, schema);
if (result.error) {
return next(
new BadRequestError({
toJSON: () => ({
errors: result.error.details,
}),
}),
);
}
try {
const book = new Book();
book.isbn = req.body.isbn;
book.title = req.body.title;
book.author = req.body.author;
book.pages = req.body.pages;
await book.save();
return respond(res, next, book, 200);
} catch (error) {
return next(
new InternalError({
toJSON: () => ({
errors: [{ message: 'Database Error' }],
}),
}),
);
}
});
server.put('/books/:id', async (req, res, next) => {
const schema = Joi.object().keys({
id: Joi.string()
.length(24)
.required(),
isbn: Joi.string().length(13),
title: Joi.string(),
author: Joi.string(),
pages: Joi.number(),
});
const result = Joi.validate(Object.assign(req.params, req.body), schema);
if (result.error) {
return next(
new BadRequestError({
toJSON: () => ({
errors: result.error.details,
}),
}),
);
}
try {
const book = await Book.findById(req.params.id);
if (!book) {
return next(
new NotFoundError({
toJSON: () => ({
errors: [{ message: 'The book NOT found' }],
}),
}),
);
}
Object.assign(book, req.body);
await book.save();
return respond(res, next, book, 200);
} catch (error) {
return next(
new InternalError({
toJSON: () => ({
errors: [{ message: 'Database Error' }],
}),
}),
);
}
});
server.del('/books/:id', async (req, res, next) => {
const schema = Joi.object().keys({
id: Joi.string()
.length(24)
.required(),
});
const result = Joi.validate(req.params, schema);
if (result.error) {
return next(
new BadRequestError({
toJSON: () => ({
errors: result.error.details,
}),
}),
);
}
try {
const book = await Book.findById(req.params.id);
if (!book) {
return next(
new NotFoundError({
toJSON: () => ({
errors: [{ message: 'The book NOT found' }],
}),
}),
);
}
book.remove();
return respond(res, next, book, 200);
} catch (error) {
return next(
new InternalError({
toJSON: () => ({
errors: [{ message: 'Database Error' }],
}),
}),
);
}
});
server.listen(PORT, () => {
console.log(`Server up on ${PORT}...`);
});
完成です。
動作確認
少しだけバリデーションを確認しましょう。
$ curl -v -H "Accept:application/json" http://localhost:3000/books/12345 | jq
{
"errors": [
{
"message": "\"id\" length must be 24 characters long",
"path": [
"id"
],
"type": "string.length",
"context": {
"limit": 24,
"value": "12345",
"key": "id",
"label": "id"
}
}
]
}
よさそうですね。
最後に
いかがでしたか?Expressだけでなく、RestifyでもAPIが作れるようになったことでしょう。それでは。
環境
- NodeJS: v10.11.0
- Yarn: 1.9.4
- Docker: Docker version 18.06.1-ce, build e68fc7a
- MongoDB: 3.6.3
- restify: 7.2.1
- restify-errors: 6.1.1
- mongoose: 5.3.0
- joi: 13.7.0
- curl: 7.54.0
- jq: jq-1.5


コメントを残す